Search code examples
ansibleapi-keyapi-security

Managing remotely-generated API keys with Ansible


I'm using ansible to provision a particular service, and before I can interact with it I must first generate an API key. But I can't predefine that key in my playbook (as a secret) - it is generated by the server, returned to me once, and will never be exposed again (the typical scnenario for API keys).

I could ask the server to generate a new API key every time I run that playbook, or tags in that playbook, but that is very slow. Ideally I should save that API key locally and reuse it.

My approach has been to simply write it to a local file in my user directory (~/.ansible/custom/api_key.txt) so it has some protection. That works but feels kind of dirty.

Does ansible have an official / robust way to handle this scenario?


Solution

  • This is kind of a broad question. There are a variety of ways of handling secrets in your playbooks; this article describes several options, and there are a variety of articles online that cover similar topics.

    A simple option might be to encrypt the API key to a GPG public key for which the private key is only available when you're logged in and able to provide the passphrase.

    Here's a simple (i.e. not particularly robust) example:

    - hosts: localhost
      gather_facts: false
    
      tasks:
        # Check if the file in which we cache the API key exists.
        # If not, fetch the API key from the API and store it in
        # a GPG-encrypted file.
        - when: apikey_file is not file
          block:
            # This is just a dummy task to give us a string; you would of
            # course replace this with the logic to acquire an API key.
            - name: Get key from API
              command: echo secret.key
              register: apikey
    
            # Encrypt the key using the public key for the identity
            # stored in apikey_gpg_id.
            - name: Write API key to file
              command: >-
                gpg -o "{{ apikey_file }}" -e -r "{{ apikey_gpg_id }}"
              args:
                stdin: "{{ apikey.stdout }}"
    
    - hosts: localhost
      gather_facts: false
    
      tasks:
        # Decrypt the API key file to stdout. This requires us to type in
        # the passphrase (which may be cached for some amount of time in your
        # GPG agent).
        - name: Read API key from file
          command: >-
            gpg -d "{{ apikey_file }}"
          register: apikey
    
        # Show what we got from the previous task.
        - debug:
            var: apikey.stdout
    

    This presumes that the variables apikey_file and apikey_gpg_id are defined in advance -- I've put them in group_vars/all.yaml, but they could also be defined in your inventory or elsewhere depending on how your project is structured.