DEBOPS.SECRET(5)

NAME
debops.secret - Manage sensitive data in the filesystem

SYNOPSIS

debops run service/secret [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

debops.secret role enables you to have a separate directory on the Ansible Controller (different than the playbook directory and inventory directory) which can be used as a handy "workspace" for other roles.

Some usage examples of this role in DebOps <https://debops.org/> include:

•   password lookups, either from current role, or using known location of passwords from other roles, usually dependencies (for example debops.mariadb role can manage an user account in the database with random password and other role can lookup that password to include in a generated configuration file);

•   secure file storage, for example for application keys generated on remote hosts (debops.boxbackup role retrieves client keys for backup purposes), for that reason secret directory should be protected by an external means, for example encrypted filesystem (currently there is no encryption provided by default);

•   secure workspace (debops.boxbackup role, again, uses the secret directory to create and manage Root CA for backup servers – client and server certificates are automatically downloaded to Ansible Controller, signed and uploaded to destination hosts);

•   simple centralized backup (specific roles like debops.sshd, debops.pki and debops.monkeysphere have separate task lists that are invoked by custom playbooks to allow backup and restoration of ssh host keys and SSL certificates. Generated .tar.gz files are kept on Ansible Controller in secret directory).

GETTING STARTED

By default all you need to do to use the debops.secret role is to include it in your common playbook at the beginning:

---
- name: Example playbook
  hosts: all
  become: True
  roles:
    - role: secret

That will allow all your roles in this and subsequent plays to access the secret variable and use it consistently.

Unfortunately, it doesn't work well when you use Ansible with --tags parameter, which might omit your common play, thus not setting secret* variables at all and changing your passwords to empty values, modifying config files incorrectly, basically not honoring the idempotency principle.

The solution to that problem is to either include debops.secret role in all your plays (similar to the one above), or include it as a dependency in roles that require it:

---
dependencies:
  - role: secret

This will ensure that roles utilizing secret variable will be able to access it correctly and you don't need to remember to include debops.secret role in all your playbooks.

  Usage examples

Example password lookup with password written to a variable. You can define this variable anywhere Ansible variables can be defined, but if you want to give playbook users the ability to overwrite it in inventory, you should define it in role/defaults/main.yml:

---
mariadb__root_password: '{{ lookup("password", secret + "/credentials/" +
                            ansible_fqdn + "/mariadb/root/password length=20") }}'

When this variable is set in role/defaults/main.yml, you can easily overwrite it in your inventory, like this:

---
mariadb__root_password: 'correct horse battery staple'

You can also change the password directly in the secret directory, in this case in secret/credentials/hostname/mysql/root/password and Ansible should update the password on the remote server (if the role is written to support this).

Example file download task from remote host to Ansible controller, stored in secret directory:

---
- name: Download file to secret directory
  fetch:
    src: '/etc/fstab'
    flat: True
    dest: '{{ secret + "/storage/" + ansible_fqdn + "/etc/fstab" }}'

Example file upload task from Ansible Controller to remote host with file from secret directory:

---
- name: Copy file from secret directory
  copy:
    dest: '/etc/fstab'
    owner: 'root'
    group: 'root'
    mode: '0644'
    src: '{{ secret + "/storage/ + ansible_fqdn + "/etc/fstab" }}"

DETAILED GUIDES

  How local secrets work

Here's a default project directory layout kept in a git repository:

~/Projects/
`-- data-center/
    |-- .git/
    `-- ansible/
        |-- inventory/
        |   |-- group_vars/
        |   |-- host_vars/
        |   `-- hosts
        |
        `-- secret/
            |-- credentials/
            `-- storage/

If you use the debops-padlock script to create encrypted EncFS storage for your secrets, directory layout will be slightly different:

~/Projects/
`-- data-center/
    |-- .git/
    `-- ansible/
        |-- .encfs.secret/        <- encrypted secrets
        |   |-- U8dfMgfgg48vj/
        |   |-- fk5fkg5NN/
        |   `-- padlock*          <- unlock/lock script
        |
        |-- inventory/
        |   |-- group_vars/
        |   |-- host_vars/
        |   `-- hosts
        |
        `-- secret/               <- plaintext secrets

While the project is "at rest", secrets are encrypted inside EncFS directory, and they don't show up in the secret/ directory. When you use the debops script to run the playbook, the padlock script unlocks the encrypted directory and secrets are available again in the secret/ directory for ansible-playbook to use.

  How to use LDAP variables

DebOps relies on the ldap_attr and ldap_entry Ansible modules to perform LDAP management on local or remote LDAP servers. In various DebOps roles, they are used to perform certain tasks in a shared environment.

Some of the above tasks require admin privileges on the LDAP server. To provide access to it in a secure manner, debops.secret role keeps a set of variables meant to be used with these tasks.

For security reasons, LDAP-related tasks should be delegated to localhost (Ansible Controller) or the LDAP server itself. The host that runs these tasks requires python-ldap library. Modules will access LDAP directly, without using SSH redirection, so TLS encryption is strongly recommended. You should also take care to not log these tasks to avoid leaking the LDAP administrator password in logs.

Example usage of LDAP secret variables:

- name: Create an entry in LDAP database
  ldap_entry:
    dn:          '{{ secret__ldap_ou_people_dn }}'
    objectClass: [ 'organizationalUnit', 'top' ]
    state:       'present'
    server_uri:  '{{ secret__ldap_server_uri }}'
    start_tls:   '{{ secret__ldap_start_tls }}'
    bind_dn:     '{{ secret__ldap_bind_dn }}'
    bind_pw:     '{{ secret__ldap_bind_pw }}'
  become:        '{{ secret__ldap_become }}'
  delegate_to:   '{{ secret__ldap_delegate_to }}'
  no_log: '{{ debops__no_log | d(True) }}'
- name: Add attribute to an LDAP entry
  ldap_attr:
    dn:         'uid=user,{{ secret__ldap_ou_people_dn }}'
    name:       '{{ item.key }}'
    values:     '{{ item.value }}'
    state:      'exact'
    server_uri: '{{ secret__ldap_server_uri }}'
    start_tls:  '{{ secret__ldap_start_tls }}'
    bind_dn:    '{{ secret__ldap_bind_dn }}'
    bind_pw:    '{{ secret__ldap_bind_pw }}'
  become:       '{{ secret__ldap_become }}'
  delegate_to:  '{{ secret__ldap_delegate_to }}'
  with_dict:
    uid:          '{{ user_username }}'
    userPassword: '{{ user_password }}'
  no_log: '{{ debops__no_log | d(True) }}'

Of course for this to work, debops.secret needs to be included in the playbook, either as a role, or a role dependency. You can change the values of secret__ldap_* variables in inventory as you need.

If you use debops.slapd role to configure an LDAP server, it will automatically copy the admin account password to a location defined in secret__ldap_admin_password variable to be accessed by the debops.secret role as needed.

AUTHOR
Maciej Delmanowski

COPYRIGHT
2014-2024, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others