4

I would like to use Ansible to

  1. Generate and encrypt an SSH key pair on the target
  2. Add the SSH public key to GitHub
  3. Clone a private GitHub repository

I explicitly do not want to forward any SSH keys from the control node to the target (never understood the point of doing this) or even worse, copy SSH keys from the control node to the target. I also don't want to keep the private key on the target in plain text. All answers to other questions I've seen always suggest one of these three ways, all of which are bad.

Instead, I want to generate an own pair of SSH keys on each target and of course encrypt the private key so that it doesn't lie around on the target in plain text.

I've so far not managed to do this. Can anyone help?

Here is what I've tried so far (assume all variables used exist):

- name: Generate target SSH key for accessing GitHub
  command: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N '{{ github_ssh_key_encryption_password }}'"
    
- name: Fetch local target SSH public key
  command: "cat {{ github_ssh_key_path }}.pub"
  register: ssh_pub_key

- name: Authorize target SSH public key with GitHub
  github_key:
    name: Access key for target "{{ target_serial }}"
    pubkey: "{{ ssh_pub_key.stdout }}"
    token: "{{ github_access_token }}"

- name: Clone private git repository
  git:
    repo: git@github.com:my_org/private_repo.git
    clone: yes
    dest: /path/to/private_repo

The problem with the encrypted key is that I then get a "permission denied" error from GitHub. I probably need to add the key to ssh-agent, but I haven't been able to figure out how. Or is there an alternative?

The above works fine if I do not encrypt the SSH private key, i.e. if I do

- name: Generate target SSH key for accessing GitHub
  command: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N ''"

instead of the command above, but of course I don't want that.

Alex
  • 3,316
  • 4
  • 26
  • 52
  • I might be wrong, but I think it isn't very uncommon to store only SSH keys encrypted with the remaining file system unencrypted (beyond the key passphrase). If you have these requirements, maybe the problem is with the target machine as a whole? For example, you may want to look into a LUKS/full disk encryption setup. Please note however that if anyone has physical access to the machine while it is running or can SSH into it, this still won't save you. Maybe it would also help if you elaborated what exact attack scenario you're trying to defend yourself from. – E. T. Jan 14 '21 at 12:19
  • 1
    Thanks for chiming in @E.T.! Just so I understand you correctly: Do you mean it isn't very UNcommon or it isn't very COMMON? In other words, are you arguing the whole file system should be encrypted anyway and then it doesn't matter much whether or not the SSH key is encrypted separately within it? I don't have a particular attack scenario in mind, but it just doesn't seem good to have an unencrypted SHH private key lying around even if the file system is encrypted. There are still scenarios where an authorized user works on the decrypted file system and accidentally executes malware. – Alex Jan 14 '21 at 13:50
  • Yes, I meant *not very common. I deleted my other comments since after some thought I made your question seem less reasonable than it is. SSH key passphrases are somewhat common, after all. However, if ssh-agent has the passphrase permanently then that might undo the security gain somewhat - but there are ways to use this that are useful (like only temporarily unlocking the passphrase via ansible remotely), so certainly an interesting question. – E. T. Jan 14 '21 at 14:07

1 Answers1

0
- name: Generate target SSH key for accessing GitHub
  ansible.builtin.command: 
    cmd: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N '{{ github_ssh_key_encryption_password }}'"
    creates: "{{ github_ssh_key_path }}"
    
- name: Fetch local target SSH public key
  ansible.builtin.slurp: "{{ github_ssh_key_path }}.pub"
  register: ssh_pub_key

- name: Authorize target SSH public key with GitHub
  github_deploy_key:
    name: Access key for target "{{ target_serial }}"
    key: "{{ ssh_pub_key.content }}"
    owner: "{{ my_org }}"
    read_only: true
    repo: "{{ private_repo }}"
    token: "{{ github_access_token }}"

- name: Clone private git repository
  git:
    repo: "git@github.com:{{ my_org }}/{{ private_repo }}.git"
    clone: true
    dest: "/path/to/{{ private_repo }}"

Use community.general.github_deploy_key to instantiate a deploy key with read_only: true (default) so that the ssh key is tied only to the single GitHub repo. This way it can only be used to get (and not modify) the specific repo that you are cloning to the system anyways. If they can read the plain text private key on the host, they can read the contents of the repo locally. Technically, this does potentially expose more. Future versions of the repo (since the initial pull by the ansible playbook) could be accessed directly from GitHub. If the initial pull restricted what was pulled (e.g. depth: 1 or single-branch: true) more history or branch structure than exists on the local file system could be pulled with the key. Use ansible.builtin.slurp to read the contents of the public key without resorting to command (better idempotence reporting).

Use creates parameter with ansible.builtin.command module to not recreate the key each time.

Use true/false to avoid Norway problem related ambiguity with yes/no

While this does not answer the tactical question, it may answer the strategic question, depending on the risk budget.

If the private key being encrypted is a hard requirement, it looks like shelling out to ssh-add after launching ssh-agent will be required, and highly host OS dependent for exact syntax details (e.g. how to give ssh-add the passphrase, as discussed here How to make ssh-add read passphrase from a file?). Further, the implementation details vary depending on the ongoing access you want the host to have with the key:

  • Should it only be usable when this ansible playbook unlocks the key, or should the passphrase be saved to a system keychain (highly host OS dependent)?
  • Should ssh-add implement a destination constraint (-h) when adding the keys to the agent?
  • Should the playbook add GitHub as a known host in the ssh config?
    • ansible_user ssh config, or system-wide?
    • public GitHub.com, or local instance?
  • Should ssh-agent start automatically (e.g. on reboot or login), or through manual/ansible intervention?
  • Do you even want to keep the key on server after completing the clone?
    • No decreases idempotency, but arguably increases security (or alternatively securty theater).
    • If not, you will also want to state: absent the github_deploy_key, too.

It does not look like there are (yet) ansible modules to manage the remote host ssh-agent state or keys. ssh agent forwarding seems to be widely accepted by the community and accomplishes most objectives (keeping the authorized key from being persistently stored on the remote host, only allowing use of the key while the agent is forwarded, etc.), so I expect no such module will be forthcoming.

dmacduff
  • 51
  • 3