1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-27 00:55:06 +03:00

Enhancing vault integration

Added persistent storage

Auto-create vault and awx via playbooks

Create a new pattern for custom containers where we can do initialization

Auto-install roles needed for plumbing via the Makefile
This commit is contained in:
John Westcott IV 2023-06-26 18:18:57 -04:00 committed by John Westcott IV
parent ac4ef141bf
commit 94183d602c
15 changed files with 218 additions and 49 deletions

View File

@ -532,6 +532,9 @@ docker-compose-sources: .git/hooks/pre-commit
$(EXTRA_SOURCES_ANSIBLE_OPTS)
docker-compose: awx/projects docker-compose-sources
ansible-galaxy install --ignore-certs -r tools/docker-compose/ansible/requirements.yml;
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/initialize_containers.yml \
-e enable_vault=$(VAULT);
$(DOCKER_COMPOSE) -f tools/docker-compose/_sources/docker-compose.yml $(COMPOSE_OPTS) up $(COMPOSE_UP_OPTS) --remove-orphans
docker-compose-credential-plugins: awx/projects docker-compose-sources
@ -583,7 +586,7 @@ docker-clean:
-$(foreach image_id,$(shell docker images --filter=reference='*/*/*awx_devel*' --filter=reference='*/*awx_devel*' --filter=reference='*awx_devel*' -aq),docker rmi --force $(image_id);)
docker-clean-volumes: docker-compose-clean docker-compose-container-group-clean
docker volume rm -f tools_awx_db tools_grafana_storage tools_prometheus_storage $(docker volume ls --filter name=tools_redis_socket_ -q)
docker volume rm -f tools_awx_db tools_vault_1 tools_grafana_storage tools_prometheus_storage $(docker volume ls --filter name=tools_redis_socket_ -q)
docker-refresh: docker-clean docker-compose

View File

@ -505,32 +505,45 @@ Run a HashiVault container alongside of AWX.
VAULT=true make docker-compose
```
Go to `http://localhost:1234` sign in with method "Token".
You can find the initialization data at `tools/docker-compose/_sources/secrets/vault_init.yml`,
This includes the unseal keys and a root token.
You can find the generated token at `tools/docker-compose/_sources/secrets/vault_password.yml`,
this is a root token, and it should not need a corresponding username.
Note that the token will be different on each restart, as it is re-generated by the playbook,
and the container does not use a persistent volume.
You will need to unseal the HashiVault each time the container is started.
The easiest way to do that is to run:
```bash
ansible-playbook tools/docker-compose/ansible/unseal_vault.yml
```
This will perform the unseal and also display the root token for login.
As a demo, click "Enable new engine +", click "KV" and Next.
In the "Path" enter "my_engine" and click "Enable Engine".
Click on the name of the engine and then "Create secret +".
In the "Path for this secret" enter "my_root/my_folder" and in the "Secret Data" put "my_key" for key and **"my_value"** for value.
For demo purposes, Vault will be auto-configured to include a Key Value (KV) vault called `my_engine` along with a secret called `my_key` in `/my_engine/my_root/my_folder`.
The secret value is `this_is_the_secret_value`.
Then go to AWX and create a new HashiVault credential with the generated token.
Then go to any other arbitrary credential and click the key icon on an input to use a credential lookup plugin.
In the "External Secret Management System" menu, first select the already-created HashiVault credential.
To create a secret connected to this vault in AWX you can run the following playbook:
```bash
export CONTROLLER_USERNAME=<your username>
export CONTROLLER_PASSWORD=<your password>
ansible-playbook tools/docker-compose/ansible/plumb_vault.yml
```
Then in the "Metadata" menu, put in this data which is important for the integration:
- Name of Secret Backend: "my_engine"
- Path to Secret: "data/my_root/my_folder"
- Key Name: "my_key"
This will create the following items in your AWX instance:
* A credential called `Vault Lookup Cred` tied to the vault instance.
* A custom credential type called `Vault Custom Cred Type`.
* A credential called `Credential From Vault` which is of the created type using the `Vault Lookup Cred` to get the password.
After this, apply the credential to a job template that writes the data in a debug task.
In the job output, you should see **my_value**.
The custom credential type adds a variable when used in a playbook called `the_secret_from_vault`.
If you have a playbook like:
```
---
- name: Show a vault secret
hosts: localhost
connection: local
gather_facts: False
tasks:
- debug:
var: the_secret_from_vault
```
(NOTE: the "arbitrary credential" could be a new custom credential type that injects to extra vars
which is used in corresponding playbook that prints hostvars, but this doc assumes you know how to do that)
And run it through AWX with the credential `Credential From Vault` tied to it, the debug should result in `this_is_the_secret_value`
The extremely non-obvious input is the fact that the fact prefixes "data/" unexpectedly.
This was discovered by inspecting the secret with the vault CLI, which may help with future troubleshooting.

View File

@ -0,0 +1,2 @@
---
sources_dest: '../_sources'

View File

@ -0,0 +1,10 @@
---
- name: Run any pre-hooks for other container
hosts: localhost
gather_facts: false
tasks:
- name: Initialize vault
include_role:
name: vault
tasks_from: initialize
when: enable_vault | bool

View File

@ -0,0 +1,8 @@
---
- name: Plumb AWX for Vault
hosts: localhost
gather_facts: False
tasks:
- include_role:
name: vault
tasks_from: plumb

View File

@ -0,0 +1,5 @@
---
collections:
- awx.awx
- flowerysong.hvault
- community.docker

View File

@ -1,5 +1,4 @@
---
sources_dest: '../_sources'
compose_name: 'docker-compose.yml'
awx_image: 'ghcr.io/ansible/awx_devel'
pg_port: 5432

View File

@ -101,10 +101,6 @@
include_tasks: ldap.yml
when: enable_ldap | bool
- name: Include Vault tasks if enabled
include_tasks: vault.yaml
when: enable_vault | bool
- name: Render Docker-Compose
template:
src: docker-compose.yml.j2

View File

@ -1,20 +0,0 @@
---
- name: create vault secret file and scope into ansible-runtime
block:
- ansible.builtin.stat:
path: "{{ sources_dest }}/secrets/{{ item }}.yml"
register: vault_secret
loop:
- vault_password
- ansible.builtin.template:
src: "secrets.yml.j2"
dest: "{{ sources_dest }}/secrets/{{ item.item }}.yml"
mode: "0600"
loop: "{{ vault_secret.results }}"
loop_control:
label: "{{ item.item }}"
- include_vars: "{{ sources_dest }}/secrets/{{ item.item }}.yml"
loop: "{{ vault_secret.results }}"
no_log: true

View File

@ -235,16 +235,18 @@ services:
{% endif %}
{% if enable_vault|bool %}
vault:
image: hashicorp/vault:latest
image: hashicorp/vault:1.14
container_name: tools_vault_1
command: server
hostname: vault
ports:
- "1234:1234"
environment:
VAULT_DEV_ROOT_TOKEN_ID: "{{ vault_password }}"
VAULT_DEV_LISTEN_ADDRESS: "0.0.0.0:1234"
VAULT_LOCAL_CONFIG: '{"storage": {"file": {"path": "/vault/file"}}, "listener": [{"tcp": { "address": "0.0.0.0:1234", "tls_disable": true}}], "default_lease_ttl": "168h", "max_lease_ttl": "720h", "ui": true}'
cap_add:
- IPC_LOCK
volumes:
- 'hashicorp_vault_data:/vault/file'
{% endif %}
volumes:
@ -260,6 +262,10 @@ volumes:
name: tools_ldap_1
driver: local
{% endif %}
{% if enable_vault|bool %}
hashicorp_vault_data:
name: tools_vault_1
{% endif %}
{% if enable_prometheus|bool %}
prometheus_storage:
name: tools_prometheus_storage

View File

@ -0,0 +1,2 @@
---
vault_file: "{{ sources_dest }}/secrets/vault_init.yml"

View File

@ -0,0 +1,62 @@
---
- name: See if vault has been initialized
ansible.builtin.stat:
path: "{{ vault_file }}"
register: vault_secret_file_info
- block:
- name: Start the vault
community.docker.docker_compose:
state: present
services: vault
project_src: "{{ sources_dest }}"
- name: Run the initialization
community.docker.docker_container_exec:
command: vault operator init
container: tools_vault_1
env:
VAULT_ADDR: "http://127.0.0.1:1234"
register: vault_initialization
- name: Write out initialization file
copy:
# lines 1-4 are the keys, 6 is the root token
content: |
{{ vault_initialization.stdout_lines[0] | regex_replace('Unseal Key ', 'Unseal_Key_') }}
{{ vault_initialization.stdout_lines[1] | regex_replace('Unseal Key ', 'Unseal_Key_') }}
{{ vault_initialization.stdout_lines[2] | regex_replace('Unseal Key ', 'Unseal_Key_') }}
{{ vault_initialization.stdout_lines[3] | regex_replace('Unseal Key ', 'Unseal_Key_') }}
{{ vault_initialization.stdout_lines[4] | regex_replace('Unseal Key ', 'Unseal_Key_') }}
{{ vault_initialization.stdout_lines[6] | regex_replace('Initial Root Token', 'Initial_Root_Token') }}
dest: "{{ vault_file }}"
- name: Unlock the vault
include_role:
name: vault
tasks_from: unseal.yml
- name: Create an engine
flowerysong.hvault.engine:
path: "my_engine"
type: "kv"
vault_addr: "http://localhost:1234"
token: "{{ Initial_Root_Token }}"
register: engine
- name: Create a secret
flowerysong.hvault.kv:
mount_point: "my_engine/my_root"
key: "my_folder"
value:
my_key: "this_is_the_secret_value"
vault_addr: "http://localhost:1234"
token: "{{ Initial_Root_Token }}"
always:
- name: Stop the vault
community.docker.docker_compose:
state: absent
project_src: "{{ sources_dest }}"
when: not vault_secret_file_info.stat.exists

View File

@ -0,0 +1,56 @@
---
- name: Load vault keys
include_vars:
file: "{{ vault_file }}"
- name: Create a HashiCorp Vault Credential
awx.awx.credential:
credential_type: HashiCorp Vault Secret Lookup
name: Vault Lookup Cred
organization: Default
inputs:
api_version: "v1"
cacert: ""
default_auth_path: "approle"
kubernetes_role: ""
namespace: ""
role_id: ""
secret_id: ""
token: "{{ Initial_Root_Token }}"
url: "http://tools_vault_1:1234"
register: vault_cred
- name: Create a custom credential type
awx.awx.credential_type:
name: Vault Custom Cred Type
kind: cloud
injectors:
extra_vars:
the_secret_from_vault: "{{ '{{' }} password {{ '}}' }}"
inputs:
fields:
- type: "string"
id: "password"
label: "Password"
secret: true
register: custom_vault_cred_type
- name: Create a credential of the custom type
awx.awx.credential:
credential_type: "{{ custom_vault_cred_type.id }}"
name: Credential From Vault
inputs: {}
organization: Default
register: custom_credential
- name: Use the Vault Credential For the new credential
awx.awx.credential_input_source:
input_field_name: password
target_credential: "{{ custom_credential.id }}"
source_credential: "{{ vault_cred.id }}"
metadata:
auth_path: ""
secret_backend: "my_engine"
secret_key: "my_key"
secret_path: "/my_root/my_folder"
secret_version: ""

View File

@ -0,0 +1,14 @@
---
- name: Load vault keys
include_vars:
file: "{{ vault_file }}"
- name: Unseal the vault
flowerysong.hvault.seal:
vault_addr: "http://localhost:1234"
state: unsealed
key: "{{ item }}"
loop:
- "{{ Unseal_Key_1 }}"
- "{{ Unseal_Key_2 }}"
- "{{ Unseal_Key_3 }}"

View File

@ -0,0 +1,13 @@
---
- name: Run tasks post startup
hosts: localhost
gather_facts: False
tasks:
- name: Unseal the vault
include_role:
name: vault
tasks_from: unseal
- name: Display root token
debug:
var: Initial_Root_Token