How to Deploy SSH Keys to Minions Using SaltStack Pillars: A Complete Guide


2 views

When managing infrastructure with SaltStack, securely distributing SSH keys to minions is a common requirement. The pillar system provides an excellent way to handle sensitive data like private keys, but proper configuration is essential.

The initial configuration attempts to use pillars to reference files from the salt file server, then apply them through a state file:

/xxx/yyy/zzz/id_rsa:
  file.managed:
    - source: salt://private/id_rsa

/xxx/yyy/zz/id_rsa.pub:
  file.managed:
    - source: salt://private/id_rsa.pub

The state file reference appears incorrect:

ssh:
  file.managed:
    - name: {{ pillar['private'] }}

Here's the proper way to structure this deployment:

1. Pillar Configuration

First, ensure your pillar top file targets the correct minion:

base:
  'target-minion':
    - ssh_keys

2. Revised Pillar Structure

Create a more maintainable pillar structure:

ssh_keys:
  private_key: |
    -----BEGIN RSA PRIVATE KEY-----
    YOUR_PRIVATE_KEY_CONTENT
    -----END RSA PRIVATE KEY-----
  public_key: ssh-rsa PUBLIC_KEY_CONTENT user@host
  key_path: /etc/ssh/keys

3. State File Implementation

The corresponding state file should handle:

ssh_key_deployment:
  file.directory:
    - name: {{ pillar['ssh_keys']['key_path'] }}
    - user: root
    - group: root
    - mode: 700

  file.managed:
    - name: {{ pillar['ssh_keys']['key_path'] }}/id_rsa
    - contents: {{ pillar['ssh_keys']['private_key'] }}
    - user: root
    - group: root
    - mode: 600

  file.managed:
    - name: {{ pillar['ssh_keys']['key_path'] }}/id_rsa.pub
    - contents: {{ pillar['ssh_keys']['public_key'] }}
    - user: root
    - group: root
    - mode: 644

Always remember:

  • Restrict pillar access using pillar top files
  • Set appropriate file permissions (600 for private keys)
  • Consider using GPG to encrypt sensitive pillar data
  • Use salt-ssh for initial bootstrap if needed

If you prefer keeping keys in your salt file server:

ssh_keys_file_server:
  file.managed:
    - name: /etc/ssh/keys/id_rsa
    - source: salt://ssh_keys/id_rsa
    - user: root
    - group: root
    - mode: 600
    - makedirs: True

Remember to secure your salt file server appropriately when storing sensitive data.


When working with SaltStack, deploying SSH keys from the master to minions is a common task for managing secure authentication. However, the process can be tricky if not configured correctly. The original poster encountered errors when trying to deploy two SSH keys using Salt pillars and states.

The original pillar configuration looked like this:

/xxx/yyy/zzz/id_rsa:
  file.managed:
    - source: salt://private/id_rsa

/xxx/yyy/zz/id_rsa.pub:
  file.managed:
    - source: salt://private/id_rsa.pub

And the state file was:

ssh:
  file.managed:
    - name: {{ pillar['private'] }}

There are several problems with this approach:

  1. The pillar data structure doesn't match the state file reference
  2. File permissions for SSH keys are critical and weren't specified
  3. The paths in the pillar are absolute but might need variable substitution

Here's a better way to structure this deployment:

1. Pillar Configuration

ssh_keys:
  private_key:
    path: /home/user/.ssh/id_rsa
    source: salt://ssh_keys/id_rsa
    mode: 600
    user: user
    group: user
  public_key:
    path: /home/user/.ssh/id_rsa.pub
    source: salt://ssh_keys/id_rsa.pub
    mode: 644
    user: user
    group: user

2. State File Implementation

{% for key, args in pillar.get('ssh_keys', {}).items() %}
deploy_ssh_{{ key }}:
  file.managed:
    - name: {{ args.path }}
    - source: {{ args.source }}
    - mode: {{ args.mode }}
    - user: {{ args.user }}
    - group: {{ args.group }}
    - makedirs: True
{% endfor %}

For production environments, consider these additional best practices:

Using Encrypted Keys in Pillars

ssh_keys:
  private_key: |
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
    ...
    -----END OPENSSH PRIVATE KEY-----

Dynamic Path Generation

{% set user = 'deploy' %}
ssh_keys:
  private_key:
    path: /home/{{ user }}/.ssh/id_rsa
    ...

If you still encounter issues:

  1. Verify pillar data is available to the minion: salt 'minion-id' pillar.items
  2. Check file permissions on the master: keys should be readable by the salt user
  3. Test with state.apply in test mode first