diff --git a/Makefile b/Makefile index 6d3411edc933..e3c392231dfa 100644 --- a/Makefile +++ b/Makefile @@ -538,7 +538,8 @@ 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) \ - -e vault_tls=$(VAULT_TLS); + -e vault_tls=$(VAULT_TLS) \ + -e enable_ldap=$(LDAP); $(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 diff --git a/tools/docker-compose/README.md b/tools/docker-compose/README.md index d7babd62047b..dbd1a3a00a83 100644 --- a/tools/docker-compose/README.md +++ b/tools/docker-compose/README.md @@ -538,13 +538,15 @@ To create a secret connected to this vault in AWX you can run the following play ```bash export CONTROLLER_USERNAME= export CONTROLLER_PASSWORD= -ansible-playbook tools/docker-compose/ansible/plumb_vault.yml +ansible-playbook tools/docker-compose/ansible/plumb_vault.yml -e enable_ldap=false ``` This will create the following items in your AWX instance: * A credential called `Vault Lookup Cred` tied to the vault instance. +* A credential called `Vault UserPass 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. +* A credential called `Credential From HashiCorp Vault via Token Auth` which is of the created type using the `Vault Lookup Cred` to get the secret. +* A credential called `Credential From HashiCorp Vault via UserPass Auth` which is of the created type using the `Vault Userpass Lookup Cred` to get the secret. The custom credential type adds a variable when used in a playbook called `the_secret_from_vault`. If you have a playbook like: @@ -559,7 +561,46 @@ If you have a playbook like: var: the_secret_from_vault ``` -And run it through AWX with the credential `Credential From Vault` tied to it, the debug should result in `this_is_the_secret_value` +And run it through AWX with the credential `Credential From Vault via Token Auth` tied to it, the debug should result in `this_is_the_secret_value`. If you run it through AWX with the credential `Credential From Vault via Userpass Auth`, the debug should result in `this_is_the_userpass_secret_value`. + +### HashiVault with LDAP + +If you wish to have your OpenLDAP container connected to the Vault container, you will first need to have the OpenLDAP container running alongside AWX and Vault. + + +```bash + +VAULT=true LDAP=true make docker-compose + +``` + +Similar to the above, you will need to unseal the vault before we can run the other needed playbooks. + +```bash + +ansible-playbook tools/docker-compose/ansible/unseal_vault.yml + +``` + +Now that the vault is unsealed, we can plumb the vault container now while passing true to enable_ldap extra var. + + +```bash + +export CONTROLLER_USERNAME= + +export CONTROLLER_PASSWORD= + +ansible-playbook tools/docker-compose/ansible/plumb_vault.yml -e enable_ldap=true + +``` + +This will populate your AWX instance with LDAP specific items. + +- A vault LDAP Lookup Cred tied to the LDAP `awx_ldap_vault` user called `Vault LDAP Lookup Cred` +- A credential called `Credential From HashiCorp Vault via LDAP Auth` which is of the created type using the `Vault LDAP Lookup Cred` to get the secret. + +And run it through AWX with the credential `Credential From HashiCorp Vault via LDAP Auth` tied to it, the debug should result in `this_is_the_ldap_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. diff --git a/tools/docker-compose/ansible/roles/sources/defaults/main.yml b/tools/docker-compose/ansible/roles/sources/defaults/main.yml index 94e0a8f31d98..9362be4ee883 100644 --- a/tools/docker-compose/ansible/roles/sources/defaults/main.yml +++ b/tools/docker-compose/ansible/roles/sources/defaults/main.yml @@ -34,6 +34,7 @@ ldap_cert_subject: "/C=US/ST=NC/L=Durham/O=awx/CN=" enable_vault: false vault_tls: false hashivault_cert_dir: '{{ sources_dest }}/vault_certs' +hashivault_vars_file: '../vault/defaults/main.yml' hashivault_server_cert_subject: "/C=US/ST=NC/L=Durham/O=awx/CN=tools-vault-1" hashivault_server_cert_extensions: - "subjectAltName = DNS:tools_vault_1, DNS:localhost" diff --git a/tools/docker-compose/ansible/roles/sources/tasks/ldap.yml b/tools/docker-compose/ansible/roles/sources/tasks/ldap.yml index ea46ec3afa6c..1e0185a0885f 100644 --- a/tools/docker-compose/ansible/roles/sources/tasks/ldap.yml +++ b/tools/docker-compose/ansible/roles/sources/tasks/ldap.yml @@ -7,12 +7,15 @@ - "{{ ldap_cert_dir }}" - "{{ ldap_diff_dir }}" +- name: include vault vars + include_vars: "{{ hashivault_vars_file }}" + - name: General LDAP cert command: 'openssl req -new -x509 -days 365 -nodes -out {{ ldap_public_key_file }} -keyout {{ ldap_private_key_file }} -subj "{{ ldap_cert_subject }}"' args: creates: "{{ ldap_public_key_file }}" - name: Copy ldap.diff - copy: - src: "ldap.ldif" + ansible.builtin.template: + src: "ldap.ldif.j2" dest: "{{ ldap_diff_dir }}/ldap.ldif" diff --git a/tools/docker-compose/ansible/roles/sources/files/ldap.ldif b/tools/docker-compose/ansible/roles/sources/templates/ldap.ldif.j2 similarity index 85% rename from tools/docker-compose/ansible/roles/sources/files/ldap.ldif rename to tools/docker-compose/ansible/roles/sources/templates/ldap.ldif.j2 index 4812fff01ef7..9deaf836cd61 100644 --- a/tools/docker-compose/ansible/roles/sources/files/ldap.ldif +++ b/tools/docker-compose/ansible/roles/sources/templates/ldap.ldif.j2 @@ -84,3 +84,16 @@ objectClass: top objectClass: groupOfNames member: cn=awx_ldap_org_admin,ou=users,dc=example,dc=org +{% if enable_ldap|bool and enable_vault|bool %} +dn: cn={{ vault_ldap_username }},ou=users,dc=example,dc=org +changetype: add +mail: vault@example.org +sn: LdapVaultAdmin +cn: {{ vault_ldap_username }} +objectClass: top +objectClass: person +objectClass: organizationalPerson +objectClass: inetOrgPerson +userPassword: {{ vault_ldap_password }} +givenName: awx +{% endif %} diff --git a/tools/docker-compose/ansible/roles/vault/defaults/main.yml b/tools/docker-compose/ansible/roles/vault/defaults/main.yml index f535e16ad65b..58e0153b7f1e 100644 --- a/tools/docker-compose/ansible/roles/vault/defaults/main.yml +++ b/tools/docker-compose/ansible/roles/vault/defaults/main.yml @@ -1,7 +1,12 @@ --- vault_file: "{{ sources_dest }}/secrets/vault_init.yml" admin_password_file: "{{ sources_dest }}/secrets/admin_password.yml" -vault_cert_dir: '{{ sources_dest }}/vault_certs' +vault_cert_dir: "{{ sources_dest }}/vault_certs" vault_server_cert: "{{ vault_cert_dir }}/server.crt" vault_client_cert: "{{ vault_cert_dir }}/client.crt" vault_client_key: "{{ vault_cert_dir }}/client.key" +ldap_ldif: "{{ sources_dest }}/ldap.ldifs/ldap.ldif" +vault_ldap_username: "awx_ldap_vault" +vault_ldap_password: "vault123" +vault_userpass_username: "awx_userpass_admin" +vault_userpass_password: "userpass123" diff --git a/tools/docker-compose/ansible/roles/vault/tasks/initialize.yml b/tools/docker-compose/ansible/roles/vault/tasks/initialize.yml index 0d4ab8e3d38f..6168d5497f12 100644 --- a/tools/docker-compose/ansible/roles/vault/tasks/initialize.yml +++ b/tools/docker-compose/ansible/roles/vault/tasks/initialize.yml @@ -92,6 +92,128 @@ validate_certs: false token: "{{ Initial_Root_Token }}" + - name: Configure the vault ldap auth + block: + - name: Create ldap auth mount + flowerysong.hvault.write: + path: "sys/auth/ldap" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + data: + type: "ldap" + register: vault_auth_ldap + changed_when: vault_auth_ldap.result.errors | default([]) | length == 0 + failed_when: + - vault_auth_ldap.result.errors | default([]) | length > 0 + - "'path is already in use at ldap/' not in vault_auth_ldap.result.errors | default([])" + + - name: Create ldap engine + flowerysong.hvault.engine: + path: "ldap_engine" + type: "kv" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + + - name: Create a ldap secret + flowerysong.hvault.kv: + mount_point: "ldap_engine/ldaps_root" + key: "ldap_secret" + value: + my_key: "this_is_the_ldap_secret_value" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + + - name: Configure ldap auth + flowerysong.hvault.ldap_config: + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + url: "ldap://ldap:1389" + binddn: "cn=awx_ldap_vault,ou=users,dc=example,dc=org" + bindpass: "vault123" + userdn: "ou=users,dc=example,dc=org" + deny_null_bind: "false" + discoverdn: "true" + + - name: Create ldap access policy + flowerysong.hvault.policy: + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + name: "ldap_engine" + policy: + ldap_engine/*: [create, read, update, delete, list] + sys/mounts:/*: [create, read, update, delete, list] + sys/mounts: [read] + + - name: Add awx_ldap_vault user to auth_method + flowerysong.hvault.ldap_user: + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + state: present + name: "{{ vault_ldap_username }}" + policies: + - "ldap_engine" + when: enable_ldap | bool + + - name: Create userpass engine + flowerysong.hvault.engine: + path: "userpass_engine" + type: "kv" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + + - name: Create a userpass secret + flowerysong.hvault.kv: + mount_point: "userpass_engine/userpass_root" + key: "userpass_secret" + value: + my_key: "this_is_the_userpass_secret_value" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + + - name: Create userpass access policy + flowerysong.hvault.policy: + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + name: "userpass_engine" + policy: + userpass_engine/*: [create, read, update, delete, list] + sys/mounts:/*: [create, read, update, delete, list] + sys/mounts: [read] + + - name: Create userpass auth mount + flowerysong.hvault.write: + path: "sys/auth/userpass" + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + data: + type: "userpass" + register: vault_auth_userpass + changed_when: vault_auth_userpass.result.errors | default([]) | length == 0 + failed_when: + - vault_auth_userpass.result.errors | default([]) | length > 0 + - "'path is already in use at userpass/' not in vault_auth_userpass.result.errors | default([])" + + - name: Add awx_userpass_admin user to auth_method + flowerysong.hvault.write: + vault_addr: "{{ vault_addr_from_host }}" + validate_certs: false + token: "{{ Initial_Root_Token }}" + path: "auth/userpass/users/{{ vault_userpass_username }}" + data: + password: "{{ vault_userpass_password }}" + policies: + - "userpass_engine" + always: - name: Stop the vault community.docker.docker_compose: diff --git a/tools/docker-compose/ansible/roles/vault/tasks/plumb.yml b/tools/docker-compose/ansible/roles/vault/tasks/plumb.yml index 1e804fb672de..0e87daef6fa5 100644 --- a/tools/docker-compose/ansible/roles/vault/tasks/plumb.yml +++ b/tools/docker-compose/ansible/roles/vault/tasks/plumb.yml @@ -38,7 +38,6 @@ controller_host: "{{ awx_host }}" controller_username: admin controller_password: "{{ admin_password }}" - validate_certs: false injectors: extra_vars: @@ -51,28 +50,26 @@ secret: true register: custom_vault_cred_type -- name: Create a credential of the custom type +- name: Create a credential of the custom type for token auth awx.awx.credential: credential_type: "{{ custom_vault_cred_type.id }}" controller_host: "{{ awx_host }}" controller_username: admin controller_password: "{{ admin_password }}" - validate_certs: false - name: Credential From Vault + name: Credential From HashiCorp Vault via Token Auth inputs: {} organization: Default - register: custom_credential + register: custom_credential_via_token -- name: Use the Vault Credential For the new credential +- name: Use the Token Vault Credential For the new credential awx.awx.credential_input_source: input_field_name: password - target_credential: "{{ custom_credential.id }}" + target_credential: "{{ custom_credential_via_token.id }}" source_credential: "{{ vault_cred.id }}" controller_host: "{{ awx_host }}" controller_username: admin controller_password: "{{ admin_password }}" - validate_certs: false metadata: auth_path: "" @@ -80,3 +77,100 @@ secret_key: "my_key" secret_path: "/my_root/my_folder" secret_version: "" + +- name: Create a HashiCorp Vault Credential for LDAP + awx.awx.credential: + credential_type: HashiCorp Vault Secret Lookup + name: Vault LDAP Lookup Cred + organization: Default + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + inputs: + api_version: "v1" + default_auth_path: "ldap" + kubernetes_role: "" + namespace: "" + url: "{{ vault_addr_from_container }}" + username: "{{ vault_ldap_username }}" + password: "{{ vault_ldap_password }}" + register: vault_ldap_cred + when: enable_ldap | bool + +- name: Create a credential from the Vault LDAP Custom Cred Type + awx.awx.credential: + credential_type: "{{ custom_vault_cred_type.id }}" + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + name: Credential From HashiCorp Vault via LDAP Auth + inputs: {} + organization: Default + register: custom_credential_via_ldap + when: enable_ldap | bool + +- name: Use the Vault LDAP Credential the new credential + awx.awx.credential_input_source: + input_field_name: password + target_credential: "{{ custom_credential_via_ldap.id }}" + source_credential: "{{ vault_ldap_cred.id }}" + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + metadata: + auth_path: "" + secret_backend: "ldap_engine" + secret_key: "my_key" + secret_path: "ldaps_root/ldap_secret" + secret_version: "" + when: enable_ldap | bool + +- name: Create a HashiCorp Vault Credential for UserPass + awx.awx.credential: + credential_type: HashiCorp Vault Secret Lookup + name: Vault UserPass Lookup Cred + organization: Default + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + inputs: + api_version: "v1" + default_auth_path: "userpass" + kubernetes_role: "" + namespace: "" + url: "{{ vault_addr_from_container }}" + username: "{{ vault_userpass_username }}" + password: "{{ vault_userpass_password }}" + register: vault_userpass_cred + +- name: Create a credential from the Vault UserPass Custom Cred Type + awx.awx.credential: + credential_type: "{{ custom_vault_cred_type.id }}" + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + name: Credential From HashiCorp Vault via UserPass Auth + inputs: {} + organization: Default + register: custom_credential_via_userpass + +- name: Use the Vault UserPass Credential the new credential + awx.awx.credential_input_source: + input_field_name: password + target_credential: "{{ custom_credential_via_userpass.id }}" + source_credential: "{{ vault_userpass_cred.id }}" + controller_host: "{{ awx_host }}" + controller_username: admin + controller_password: "{{ admin_password }}" + validate_certs: false + metadata: + auth_path: "" + secret_backend: "userpass_engine" + secret_key: "my_key" + secret_path: "userpass_root/userpass_secret" + secret_version: ""