You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
7.1 KiB
Markdown
261 lines
7.1 KiB
Markdown
|
5 months ago
|
# Ansible Vault SSH Key Management Guide
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
This document explains how to securely manage SSH private keys using Ansible Vault and the copy module with decryption capabilities, including proper playbook configuration to handle decryption before fact gathering.
|
||
|
|
|
||
|
|
## Important: Playbook Configuration
|
||
|
|
|
||
|
|
### Critical Setup Requirements
|
||
|
|
```yaml
|
||
|
|
---
|
||
|
|
- name: Deploy with encrypted SSH key
|
||
|
|
hosts: all
|
||
|
|
gather_facts: false # ⚠️ ESSENTIAL - prevents early SSH connection attempts
|
||
|
|
vars:
|
||
|
|
ssh_private_key_file: key/deploy # Path to your encrypted key file
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
# Your decryption and deployment tasks here
|
||
|
|
```
|
||
|
|
|
||
|
|
### Why `gather_facts: false` is Mandatory
|
||
|
|
|
||
|
|
**Without `gather_facts: false`:**
|
||
|
|
- Ansible attempts to connect to target hosts immediately
|
||
|
|
- It tries to use SSH with the default SSH agent configuration
|
||
|
|
- Fails because the encrypted key isn't decrypted yet
|
||
|
|
- Playbook stops before reaching your decryption task
|
||
|
|
|
||
|
|
**With `gather_facts: false`:**
|
||
|
|
- Ansible skips the initial fact gathering phase
|
||
|
|
- Allows your decryption task to run first
|
||
|
|
- You control when and how the SSH key is used
|
||
|
|
|
||
|
|
## Step 1: Encrypt SSH Private Key with Ansible Vault
|
||
|
|
|
||
|
|
### Encrypt an existing SSH key:
|
||
|
|
```bash
|
||
|
|
# Method 1: Interactive password prompt
|
||
|
|
ansible-vault encrypt key/deploy --ask-vault-pass
|
||
|
|
|
||
|
|
# Method 2: Using a password file
|
||
|
|
ansible-vault encrypt key/deploy --vault-password-file vault_pass.txt
|
||
|
|
|
||
|
|
# Method 3: Using vault ID
|
||
|
|
ansible-vault encrypt key/deploy --vault-id deploy@vault_pass.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
### Create a new encrypted SSH key:
|
||
|
|
```bash
|
||
|
|
ansible-vault create key/deploy --vault-password-file vault_pass.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
### Verify encrypted content:
|
||
|
|
```bash
|
||
|
|
ansible-vault view key/deploy --vault-password-file vault_pass.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
## Step 2: Complete Ansible Playbook Example
|
||
|
|
|
||
|
|
### Full playbook structure:
|
||
|
|
```yaml
|
||
|
|
---
|
||
|
|
- name: Deploy using encrypted SSH key
|
||
|
|
hosts: all
|
||
|
|
gather_facts: false # CRITICAL: Must be disabled
|
||
|
|
vars:
|
||
|
|
ssh_private_key_file: key/deploy
|
||
|
|
decrypted_key_path: /tmp/ansible_deploy_key
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
- name: Decrypt SSH private key
|
||
|
|
copy:
|
||
|
|
src: "{{ ssh_private_key_file }}"
|
||
|
|
dest: "{{ decrypted_key_path }}"
|
||
|
|
decrypt: yes
|
||
|
|
mode: '0600'
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
run_once: true
|
||
|
|
|
||
|
|
- name: Enable fact gathering with decrypted key
|
||
|
|
setup:
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
|
||
|
|
- name: Use the decrypted key for deployment
|
||
|
|
ansible.builtin.shell: |
|
||
|
|
ssh -i "{{ decrypted_key_path }}" \
|
||
|
|
-o StrictHostKeyChecking=no \
|
||
|
|
-o UserKnownHostsFile=/dev/null \
|
||
|
|
deploy@{{ inventory_hostname }} 'deployment_command'
|
||
|
|
args:
|
||
|
|
executable: /bin/bash
|
||
|
|
|
||
|
|
- name: Remove decrypted SSH key (cleanup)
|
||
|
|
file:
|
||
|
|
path: "{{ decrypted_key_path }}"
|
||
|
|
state: absent
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
always: yes
|
||
|
|
```
|
||
|
|
|
||
|
|
## Step 3: Running the Playbook
|
||
|
|
|
||
|
|
### Execution methods:
|
||
|
|
```bash
|
||
|
|
# With password file
|
||
|
|
ansible-playbook playbook.yml --vault-password-file vault_pass.txt
|
||
|
|
|
||
|
|
# Interactive password prompt
|
||
|
|
ansible-playbook playbook.yml --ask-vault-pass
|
||
|
|
|
||
|
|
# With vault ID
|
||
|
|
ansible-playbook playbook.yml --vault-id deploy@vault_pass.txt
|
||
|
|
|
||
|
|
# With inventory file
|
||
|
|
ansible-playbook -i hosts.ini playbook.yml --vault-password-file vault_pass.txt
|
||
|
|
```
|
||
|
|
|
||
|
|
## Step 4: Advanced Error Handling & Security
|
||
|
|
|
||
|
|
### Robust playbook with proper error handling:
|
||
|
|
```yaml
|
||
|
|
---
|
||
|
|
- name: Secure deployment with encrypted SSH key
|
||
|
|
hosts: all
|
||
|
|
gather_facts: false
|
||
|
|
vars:
|
||
|
|
ssh_private_key_file: key/deploy
|
||
|
|
decrypted_key_path: "/tmp/ansible_deploy_key_{{ ansible_date_time.epoch }}"
|
||
|
|
|
||
|
|
tasks:
|
||
|
|
- name: Ensure encrypted key file exists
|
||
|
|
stat:
|
||
|
|
path: "{{ ssh_private_key_file }}"
|
||
|
|
register: key_file
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
|
||
|
|
- name: Fail if encrypted key is missing
|
||
|
|
fail:
|
||
|
|
msg: "Encrypted SSH key file {{ ssh_private_key_file }} not found"
|
||
|
|
when: not key_file.stat.exists
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
|
||
|
|
- name: Decrypt SSH private key
|
||
|
|
copy:
|
||
|
|
src: "{{ ssh_private_key_file }}"
|
||
|
|
dest: "{{ decrypted_key_path }}"
|
||
|
|
decrypt: yes
|
||
|
|
mode: '0600'
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
run_once: true
|
||
|
|
|
||
|
|
- name: Verify decrypted key permissions
|
||
|
|
file:
|
||
|
|
path: "{{ decrypted_key_path }}"
|
||
|
|
mode: '0600'
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
|
||
|
|
- name: Gather facts using decrypted key (if needed)
|
||
|
|
setup:
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
|
||
|
|
- name: Perform deployment tasks
|
||
|
|
block:
|
||
|
|
- name: Execute remote deployment
|
||
|
|
ansible.builtin.shell: |
|
||
|
|
ssh -i "{{ decrypted_key_path }}" \
|
||
|
|
-o ConnectTimeout=30 \
|
||
|
|
-o StrictHostKeyChecking=no \
|
||
|
|
deploy@{{ inventory_hostname }} 'your_deployment_script'
|
||
|
|
args:
|
||
|
|
executable: /bin/bash
|
||
|
|
register: deployment_result
|
||
|
|
|
||
|
|
- name: Display deployment output
|
||
|
|
debug:
|
||
|
|
var: deployment_result.stdout
|
||
|
|
|
||
|
|
rescue:
|
||
|
|
- name: Handle deployment failure
|
||
|
|
debug:
|
||
|
|
msg: "Deployment failed - check SSH connectivity and permissions"
|
||
|
|
|
||
|
|
always:
|
||
|
|
- name: Always remove decrypted key
|
||
|
|
file:
|
||
|
|
path: "{{ decrypted_key_path }}"
|
||
|
|
state: absent
|
||
|
|
delegate_to: localhost
|
||
|
|
become: false
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Best Practices
|
||
|
|
|
||
|
|
### 1. File Security:
|
||
|
|
```bash
|
||
|
|
# Secure permissions for password files
|
||
|
|
chmod 600 vault_pass.txt
|
||
|
|
|
||
|
|
# Secure permissions for encrypted key
|
||
|
|
chmod 600 key/deploy
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Temporary File Safety:
|
||
|
|
- Use unique temporary filenames with timestamps
|
||
|
|
- Set strict permissions (0600)
|
||
|
|
- Always clean up, even on failure
|
||
|
|
|
||
|
|
### 3. Key Management:
|
||
|
|
- Never store unencrypted keys in version control
|
||
|
|
- Rotate deployment keys regularly
|
||
|
|
- Use different keys for different environments
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Common Issues & Solutions:
|
||
|
|
|
||
|
|
1. **"Permission denied" errors:**
|
||
|
|
- Verify vault password is correct
|
||
|
|
- Check encrypted file permissions
|
||
|
|
- Ensure cleanup tasks run successfully
|
||
|
|
|
||
|
|
2. **SSH connection failures:**
|
||
|
|
- Verify the decrypted key is authorized on target hosts
|
||
|
|
- Check network connectivity
|
||
|
|
- Validate target host accessibility
|
||
|
|
|
||
|
|
3. **Fact gathering issues:**
|
||
|
|
- Use `gather_facts: false` in main playbook
|
||
|
|
- Manually call `setup` module after decryption if needed
|
||
|
|
|
||
|
|
### Debug Commands:
|
||
|
|
```bash
|
||
|
|
# Test vault decryption
|
||
|
|
ansible-vault view key/deploy --vault-password-file vault_pass.txt
|
||
|
|
|
||
|
|
# Verify playbook syntax
|
||
|
|
ansible-playbook playbook.yml --syntax-check
|
||
|
|
|
||
|
|
# Dry run to see what would happen
|
||
|
|
ansible-playbook playbook.yml --vault-password-file vault_pass.txt --check
|
||
|
|
```
|
||
|
|
|
||
|
|
## Summary
|
||
|
|
The key points for successful encrypted SSH key management:
|
||
|
|
|
||
|
|
1. **Always use `gather_facts: false`** in the main playbook
|
||
|
|
2. **Decrypt the key early** in your tasks
|
||
|
|
3. **Use unique temporary paths** for decrypted keys
|
||
|
|
4. **Always clean up** decrypted keys, even on failures
|
||
|
|
5. **Secure your vault passwords** with proper file permissions
|
||
|
|
|
||
|
|
This approach ensures your SSH keys remain encrypted at rest and are only temporarily decrypted during execution, maintaining security throughout your deployment process.
|