DEBOPS.AUTHORIZED_KEYS(5)

NAME
debops.authorized_keys - Manage SSH public keys using Ansible

SYNOPSIS

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

DESCRIPTION

The debops.authorized_keys role can be used to manage SSH keys centrally in the /etc/ssh/authorized_keys/ directory. The role only manages the keys themselves, you should configure the sshd service to use them separately, for example by using the debops.sshd Ansible role.

GETTING STARTED

  Security considerations

The debops.authorized_keys role is designed to manage files in /etc/ssh/authorized_keys/ directory which contain SSH public keys for user accounts.

By default, role modifies the ownership and permissions of these files after the Ansible authorized_key changes them, so that the owner of these files is root account, and the file group, either named after the specified username, set as an item.group parameter or root if the previous entries don't result in an existing group present on a given system. This assumes that each user account has a primary group of the same name as their account, and they are the only members of that group.

The file attributes will be set as 640 to allow read only access to the SSH public key files. This should ensure that user accounts can be logged into the specific SSH public keys, but they cannot be modified by their respective users.

Unfortunately, it seems that the Ansible authorized_key module enforces the "600" file permissions <https://github.com/ansible/ansible-modules-core/blob/devel/system/authorized_key.py#L298-L302> on any file change, therefore there exists a window of opportunity between the given user file being updated by the authorized_key module and permissions being enforced again by the file Ansible module. At the moment there's no good solution to this issue, perhaps in the future the authorized_key module will be updated to allow for setting custom file ownership and permissions similarly to the file module. However, if there are no updates to the user files, the permissions are not changed.

  SSH service configuration

The debops.authorized_keys role does not manage the sshd service configuration by itself. Instead, you are expected to configure the sshd daemon to use the authorized keys from its directory, either by hand or by an Ansible role.

NOTE: The debops.sshd role enables use of the SSH public keys managed by the debops.authorized_keys automatically.

To enable the sshd service to use the configured public keys, you should change the configuration in the /etc/ssh/sshd_config file to something like this:

# /etc/ssh/sshd_config
AuthorizedKeysFile /etc/ssh/authorized_keys/%u %h/.ssh/authorized_keys

The above configuration will enable use of the configured keys by all subsequent SSH connections.

You can use the Match keyword to restrict the use of the authorized keys to a particular hosts, users, groups or IP addresses/subnets as needed. For example, if you want to check these authorized keys only for users in a specific subnet, you can do it like this:

# /etc/ssh/sshd_config
Match Address 192.0.2.0/24
      AuthorizedKeysFile /etc/ssh/authorized_keys/%u %h/.ssh/authorized_keys

Alternatively, you can use different conditions to restrict the SSH public keys checked by the sshd daemon to only the authorized keys maintained by this role. For example, to restrict a particular system group to only use these authorized keys for authentication, you can do it like this:

# /etc/ssh/sshd_config
Match Group sftponly
      AuthorizedKeysFile /etc/ssh/authorized_keys/%u

  Example inventory

This role is included by default in the common.yml DebOps playbook, so you don't need to do anything to enable it.

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.authorized_keys role:

---
- name: Manage SSH public keys
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_all_hosts', 'debops_service_authorized_keys' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: authorized_keys
      tags: [ 'role::authorized_keys', 'skip::authorized_keys' ]

DEFAULT VARIABLE DETAILS

Some of debops.authorized_keys default variables have more extensive configuration than simple strings or lists, here you can find documentation and examples for them.

  authorized_keys__identities

The authorized_keys__*_identities variables are used to define a list of "SSH Key identities". Each identity can use one or more SSH public keys and has a corresponding list of UNIX accounts which will include the SSH keys of a given identity, with optional configuration as specified in the authorized_keys(5) <https://manpages.debian.org/authorized_keys(5)> manual page (see "authorized_keys file format" section for list of options).

By default the keys will be stored in the /etc/ssh/authorized_keys/ directory, with separate file for each UNIX account. These files are owned by root and the corresponding user's UNIX group has read-only access to them, to prevent modification by the users.

Alternatively, the account's ~/.ssh/authorized_keys file can be used, but this location will not be protected from the user modifications.

  Examples

Ensure that given SSH public keys are present in the user file:

authorized_keys__identities:
  - name: 'identity1'
    sshkeys: [ 'ssh-rsa AAAAB3NzaC1yc2EAAAA...', 'ssh-rsa AAAAB3NzaC1yc2EAAAA...' ]
    comment: 'admin@example.org'
    accounts:
      - 'user1'

Add SSH public keys from the ssh-agent service running on the Ansible Controller (useful with smartcards):

authorized_keys__identities:
  - name: 'identity2'
    sshkeys: '{{ lookup("pipe","ssh-add -L | grep ^ssh || cat ~/.ssh/*.pub || true") }}'
    accounts:
      - 'user2'

Define two global SSH identities and add them to different UNIX accounts on different hosts:

 # ansible/inventory/group_vars/debops_all_hosts/authorized_keys.yml
 authorized_keys__identities:
   - name: 'manager'
     url: 'https://github.com/user2.keys'
   - name: 'deployment'
     url: 'https://gitlab.com/user2.keys'
# ansible/inventory/host_vars/webserver/authorized_keys.yml
authorized_keys__host_identities:
  - name: 'manager'
    accounts:
      - name: 'application'
        home: True
  - name: 'deployment'
    accounts:
      - name: 'application'
        options: [ 'restrict', 'pty' ]
        command: '/usr/local/bin/rrsync -wo /home/application/'
        comment: 'Deployment account'
# ansible/inventory/host_vars/database/authorized_keys.yml
authorized_keys__host_identities:
  - name: 'manager'
    accounts:
      - 'postgres'
  - name: 'deployment'
    accounts:
      - name: 'postgres'
        options: [ 'restrict', 'pty' ]
        environment:
          APP_NAME: 'application1'
        command: '/usr/bin/psql application_db app_user'
        comment: 'Database management access'

Set SSH keys from a file on Ansible Controller as the only keys on a given user account:

authorized_keys__identities:
  - name: 'identity3'
    sshkeys: '{{ lookup("file", "/path/to/user3.pub") }}'
    accounts:
      - name: 'user3'
        exclusive: True

A few examples from the authorized_keys(5) <https://manpages.debian.org/authorized_keys(5)> manual page:

authorized_keys__identities:
  - name: 'john'
    comment: 'john@example.net'
    sshkeys: 'ssh-rsa AAAAB2...19Q=='
    accounts:
      - name: 'application'
        options:
          - 'from="*.sales.example.net,!pc.sales.example.net"'
 - name: 'dump-user'
   comment: 'example.net'
   sshkeys: [ 'ssh-rsa AAAAC3...51R==' ]
   accounts:
     - name: 'backup'
       command: 'dump /home'
 - name: 'permit-access'
   sshkeys: 'ssh-rsa AAAAB5...21S=='
   accounts:
     - name: 'account1'
       options:
         - 'permitopen="192.0.2.1:80"'
         - 'permitopen="192.0.2.2:25"'
     - name: 'account2'
       options:
         - 'permitlisten="localhost:8080"'
         - 'permitopen="localhost:22000"'
 - name: 'jane'
   sshkeys: 'ssh-rsa AAAA...=='
   accounts:
     - name: 'vpntunnel'
       comment: 'jane@example.net'
       options:
         - 'tunnel="0"'
       command: 'sh /etc/netstart tun0'
 - name: 'restricted-user'
   sshkeys:
     - 'ssh-rsa AAAA1C8...32Tv=='
     - 'ssh-rsa AAAA1f8...IrrC5=='
   comment: 'user@example.net'
   options:
     - 'restrict'
   accounts:
     - name: 'uptime'
       command: 'uptime'
     - name: 'games'
       options:
         - 'pty'
       command: 'nethack'

Setup a git-shell(1) <https://manpages.debian.org/git-shell(1)> service for a specific SSH key identity on a given UNIX account, with a custom gitserve wrapper to support both interactive and non-interactive operation:

#!/bin/sh
# /usr/local/bin/gitserve
# See also: https://www.reddit.com/r/git/comments/2s0yi6/
set -ef
if [ -n "$SSH_ORIGINAL_COMMAND" ] ; then
    exec git-shell -c "$SSH_ORIGINAL_COMMAND"
else
    exec git-shell
fi

authorized_keys__identities:
  - name: 'git-deploy'
    url: 'https://github.com/username.keys'
    comment: 'Deployment identity'
    options:
      - 'restrict'
      - 'pty'
    command: '/usr/local/bin/gitserve'
    accounts:
      - 'application1'
      - 'application2'
      - 'application3'

  Syntax

Each list entry is a YAML dictionary with specific parameters:

name
    Required. A string which identifies an "SSH key identity", not used otherwise. Configuration entries with the same name parameter are merged during role execution and can affect each other. Most of the parameters in subsequent configuration entries will replace the former ones; exceptions are options, environment and accounts parameters.

sshkeys
    String or a YAML list containing SSH public keys. If both sshkeys and url are specified, the former takes precedence.

url String containing an URL to a resource which returns a file with SSH public keys (only one URL is allowed at the moment). Be aware that SSH key sources not controlled directly can be modified at any time, in which case the role might lose track of SSH keys already present on the host.

comment
    Optional. String which will be added at the end of the SSH public key on all defined UNIX accounts, usually a comment about the key owner.

state
    Optional. If not specified or present, a given SSH identity will be included in the list of generated identities. SSH keys will be added on any UNIX accounts defined in the accounts parameter. If absent, known SSH keys will be removed from any defined UNIX accounts. If ignore, a given configuration entry will not be processed by the role during execution.

options
    Optional. YAML list of authorized_keys(5) <https://manpages.debian.org/authorized_keys(5)> key options which should be added to SSH keys added to all defined UNIX accounts. Mostly useful to add the restrict option to all SSH keys, which can then be augmented on a per-account basis. Values of the parametrized options need to include quotation marks. The environment= and command= options have separate parameters for special handling and should not be defined using the options parameter. The options parameters from multiple configuration entries are merged together and can affect each other, depending on the state parameter.

environment
    Optional. YAML dictionary which specifies environment variables which should be set for a given SSH key. The variable names are defined as dictionary keys and their values as dictionary values. These environment variables will be set on all defined UNIX accounts; variable values can be overridden on a per-account basis.

command
    Optional. A string which will be defined as the command= option for given SSH keys on all specified UNIX accounts. Commands will be automatically quoted using double quotes ("") characters. The command parameter can be overridden on a per-account basis.

accounts
    Optional. List of UNIX accounts on which a given SSH identity should be configured. The accounts parameters from multiple configuration entries are merged together and can affect each other, depending on the state parameter.

    The list entries can be simple strings which will be interpreted as UNIX account names - in this case a given SSH identity will be configured in the /etc/ssh/authorized_keys/<account> file. Alternative syntax using YAML dictionary with specific parameters described below can be used to modify SSH identity configuration on a per-account basis:

name
    Required. Name of the user account to configure. This will be used as the name of the file located in the /etc/ssh/authorized_keys/ directory. If needed, a corresponding UNIX group with the same name will be created to provide access to the file with SSH keys.

state
    Optional. If undefined or present, the SSH public keys will be added to the user file. If absent, the specified SSH public keys will be removed from the user file.

comment
    Optional. String which will be added at the end of the SSH public key, usually a comment about the key owner.

options
    Optional. YAML list of authorized_keys(5) <https://manpages.debian.org/authorized_keys(5)> key options which should be added to SSH keys added to a given UNIX account. Values of the parametrized options need to include quotation marks. The environment= and command= options have separate parameters for special handling and should not be defined using the options parameter.

environment
    Optional. YAML dictionary which specifies environment variables which should be set for a given SSH key on a given UNIX account. The variable names are defined as dictionary keys and their values as dictionary values.

command
    Optional. A string which will be defined as the command= option for given SSH keys on a given UNIX account. Commands will be automatically quoted using double quotes ("") characters.

exclusive
    Optional, boolean. If defined and True, role will remove all other SSH public keys located in the user file and set only the SSH public keys present in the item.sshkeys or item.url parameters. Keep in mind that using this option can break idempotency if multiple entries with the same name parameter are used.

follow
    Optional, boolean. If defined and True, the role will follow symlinks to the authorized_keys file instead of replacing them.

home
    Optional, boolean. If not specified or False, the SSH keys will be managed in the /etc/ssh/authorized_keys/ directory, with custom access permissions. If True, the SSH keys will be maintained in the ~/.ssh/authorized_keys file of a given UNIX account, if that account already exists.

    The parameters below are related to the files located in the /etc/ssh/authorized_keys/ directory:

owner
    Optional. Set the owner of the SSH identity file. If the owner account does not exist, root will become the owner.

group
    Optional. Name of the primary group of a given SSH identity file. If the specified group does not exist, it will be automatically created by the role.

    If the group parameter is not specified, the role will try to set the group of the file the same as the specified UNIX account in name parameter with the assumption that the corresponding primary group exists. If it does not exist, the primary group of the SSH identity file will be root.

system
    Optional, boolean. If undefined or True, the group created by the role will be a "system" group, with GID < 1000. If False, the created group will be a "normal" group with GID >= 1000. The existing groups are not modified.

gid Optional. Specify the GID a given group should use.

mode
    Optional. Set the mode of the SSH identity file. If not specified, mode 0640 will be set to allow read access to the UNIX group.

file_state
    Optional. If undefined or present, the SSH identity file will be present. If absent, the SSH identity file will be removed.

AUTHOR
Maciej Delmanowski

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