Blame cloudinit/config/cc_ssh.py

Packit Service a04d08
# Copyright (C) 2009-2010 Canonical Ltd.
Packit Service a04d08
# Copyright (C) 2012, 2013 Hewlett-Packard Development Company, L.P.
Packit Service a04d08
#
Packit Service a04d08
# Author: Scott Moser <scott.moser@canonical.com>
Packit Service a04d08
# Author: Juerg Haefliger <juerg.haefliger@hp.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
"""
Packit Service a04d08
SSH
Packit Service a04d08
---
Packit Service 751c4a
**Summary:** configure SSH and SSH keys (host and authorized)
Packit Service a04d08
Packit Service 751c4a
This module handles most configuration for SSH and both host and authorized SSH
Packit Service 751c4a
keys.
Packit Service a04d08
Packit Service 751c4a
Authorized Keys
Packit Service 751c4a
^^^^^^^^^^^^^^^
Packit Service a04d08
Packit Service 751c4a
Authorized keys are a list of public SSH keys that are allowed to connect to a
Packit Service 751c4a
a user account on a system. They are stored in `.ssh/authorized_keys` in that
Packit Service 751c4a
account's home directory. Authorized keys for the default user defined in
Packit Service 751c4a
``users`` can be specified using ``ssh_authorized_keys``. Keys
Packit Service 751c4a
should be specified as a list of public keys.
Packit Service a04d08
Packit Service 11b429
.. note::
Packit Service 751c4a
    see the ``cc_set_passwords`` module documentation to enable/disable SSH
Packit Service 751c4a
    password authentication
Packit Service a04d08
Packit Service 11b429
Root login can be enabled/disabled using the ``disable_root`` config key. Root
Packit Service 11b429
login options can be manually specified with ``disable_root_opts``. If
Packit Service 11b429
``disable_root_opts`` is specified and contains the string ``$USER``,
Packit Service 11b429
it will be replaced with the username of the default user. By default,
Packit Service 11b429
root login is disabled, and root login opts are set to::
Packit Service a04d08
Packit Service 11b429
    no-port-forwarding,no-agent-forwarding,no-X11-forwarding
Packit Service a04d08
Packit Service 751c4a
Supported public key types for the ``ssh_authorized_keys`` are:
Packit Service a04d08
Packit Service 751c4a
    - dsa
Packit Service 751c4a
    - rsa
Packit Service 751c4a
    - ecdsa
Packit Service 751c4a
    - ed25519
Packit Service 751c4a
    - ecdsa-sha2-nistp256-cert-v01@openssh.com
Packit Service 751c4a
    - ecdsa-sha2-nistp256
Packit Service 751c4a
    - ecdsa-sha2-nistp384-cert-v01@openssh.com
Packit Service 751c4a
    - ecdsa-sha2-nistp384
Packit Service 751c4a
    - ecdsa-sha2-nistp521-cert-v01@openssh.com
Packit Service 751c4a
    - ecdsa-sha2-nistp521
Packit Service 751c4a
    - sk-ecdsa-sha2-nistp256-cert-v01@openssh.com
Packit Service 751c4a
    - sk-ecdsa-sha2-nistp256@openssh.com
Packit Service 751c4a
    - sk-ssh-ed25519-cert-v01@openssh.com
Packit Service 751c4a
    - sk-ssh-ed25519@openssh.com
Packit Service 751c4a
    - ssh-dss-cert-v01@openssh.com
Packit Service 751c4a
    - ssh-dss
Packit Service 751c4a
    - ssh-ed25519-cert-v01@openssh.com
Packit Service 751c4a
    - ssh-ed25519
Packit Service 751c4a
    - ssh-rsa-cert-v01@openssh.com
Packit Service 751c4a
    - ssh-rsa
Packit Service 751c4a
    - ssh-xmss-cert-v01@openssh.com
Packit Service 751c4a
    - ssh-xmss@openssh.com
Packit Service a04d08
Packit Service a04d08
.. note::
Packit Service 751c4a
    this list has been filtered out from the supported keytypes of
Packit Service 751c4a
    `OpenSSH`_ source, where the sigonly keys are removed. Please see
Packit Service 751c4a
    ``ssh_util`` for more information.
Packit Service 751c4a
Packit Service 751c4a
    ``dsa``, ``rsa``, ``ecdsa`` and ``ed25519`` are added for legacy,
Packit Service 751c4a
    as they are valid public keys in some old distros. They can possibly
Packit Service 751c4a
    be removed in the future when support for the older distros are dropped
Packit Service 751c4a
Packit Service 751c4a
.. _OpenSSH: https://github.com/openssh/openssh-portable/blob/master/sshkey.c
Packit Service 751c4a
Packit Service 751c4a
Host Keys
Packit Service 751c4a
^^^^^^^^^
Packit Service 751c4a
Packit Service 751c4a
Host keys are for authenticating a specific instance. Many images have default
Packit Service 751c4a
host SSH keys, which can be removed using ``ssh_deletekeys``. This prevents
Packit Service 751c4a
re-use of a private host key from an image on multiple machines. Since
Packit Service 751c4a
removing default host keys is usually the desired behavior this option is
Packit Service 751c4a
enabled by default.
Packit Service 751c4a
Packit Service 751c4a
Host keys can be added using the ``ssh_keys`` configuration key. The argument
Packit Service 751c4a
to this config key should be a dictionary entries for the public and private
Packit Service 751c4a
keys of each desired key type. Entries in the ``ssh_keys`` config dict should
Packit Service 751c4a
have keys in the format ``<key type>_private`` and ``<key type>_public``,
Packit Service 751c4a
e.g. ``rsa_private: <key>`` and ``rsa_public: <key>``. See below for supported
Packit Service 751c4a
key types. Not all key types have to be specified, ones left unspecified will
Packit Service 751c4a
not be used. If this config option is used, then no keys will be generated.
Packit Service 751c4a
Packit Service 751c4a
.. note::
Packit Service 751c4a
    when specifying private host keys in cloud-config, care should be taken to
Packit Service 751c4a
    ensure that the communication between the data source and the instance is
Packit Service 751c4a
    secure
Packit Service 751c4a
Packit Service 751c4a
.. note::
Packit Service 751c4a
    to specify multiline private host keys, use yaml multiline syntax
Packit Service 751c4a
Packit Service 751c4a
If no host keys are specified using ``ssh_keys``, then keys will be generated
Packit Service 751c4a
using ``ssh-keygen``. By default one public/private pair of each supported
Packit Service 751c4a
host key type will be generated. The key types to generate can be specified
Packit Service 751c4a
using the ``ssh_genkeytypes`` config flag, which accepts a list of host key
Packit Service 751c4a
types to use. For each host key type for which this module has been instructed
Packit Service 751c4a
to create a keypair, if a key of the same type is already present on the
Packit Service 751c4a
system (i.e. if ``ssh_deletekeys`` was false), no key will be generated.
Packit Service 751c4a
Packit Service 751c4a
Supported host key types for the ``ssh_keys`` and the ``ssh_genkeytypes``
Packit Service 751c4a
config flags are:
Packit Service 751c4a
Packit Service 751c4a
    - rsa
Packit Service 751c4a
    - dsa
Packit Service 751c4a
    - ecdsa
Packit Service 751c4a
    - ed25519
Packit Service a04d08
Packit Service a04d08
**Internal name:** ``cc_ssh``
Packit Service a04d08
Packit Service a04d08
**Module frequency:** per instance
Packit Service a04d08
Packit Service a04d08
**Supported distros:** all
Packit Service a04d08
Packit Service a04d08
**Config keys**::
Packit Service a04d08
Packit Service a04d08
    ssh_deletekeys: <true/false>
Packit Service a04d08
    ssh_keys:
Packit Service a04d08
        rsa_private: |
Packit Service a04d08
            -----BEGIN RSA PRIVATE KEY-----
Packit Service a04d08
            MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco
Packit Service a04d08
            ...
Packit Service a04d08
            -----END RSA PRIVATE KEY-----
Packit Service a04d08
        rsa_public: ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ...
Packit Service a04d08
        dsa_private: |
Packit Service a04d08
            -----BEGIN DSA PRIVATE KEY-----
Packit Service a04d08
            MIIBxwIBAAJhAKD0YSHy73nUgysO13XsJmd4fHiFyQ+00R7VVu2iV9Qco
Packit Service a04d08
            ...
Packit Service a04d08
            -----END DSA PRIVATE KEY-----
Packit Service a04d08
        dsa_public: ssh-dsa AAAAB3NzaC1yc2EAAAABIwAAAGEAoPRhIfLvedSDKw7Xd ...
Packit Service a04d08
    ssh_genkeytypes: <key type>
Packit Service a04d08
    disable_root: <true/false>
Packit Service a04d08
    disable_root_opts: <disable root options string>
Packit Service a04d08
    ssh_authorized_keys:
Packit Service a04d08
        - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAGEA3FSyQwBI6Z+nCSjUU ...
Packit Service a04d08
        - ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZ ...
Packit Service a04d08
    allow_public_ssh_keys: <true/false>
Packit Service a04d08
    ssh_publish_hostkeys:
Packit Service a04d08
        enabled: <true/false> (Defaults to true)
Packit Service a04d08
        blacklist: <list of key types> (Defaults to [dsa])
Packit Service a04d08
"""
Packit Service a04d08
Packit Service a04d08
import glob
Packit Service a04d08
import os
Packit Service a04d08
import sys
Packit Service a04d08
Packit Service a04d08
from cloudinit.distros import ug_util
Packit Service a04d08
from cloudinit import ssh_util
Packit Service 751c4a
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
GENERATE_KEY_NAMES = ['rsa', 'dsa', 'ecdsa', 'ed25519']
Packit Service a04d08
KEY_FILE_TPL = '/etc/ssh/ssh_host_%s_key'
Packit Service a04d08
PUBLISH_HOST_KEYS = True
Packit Service a04d08
# Don't publish the dsa hostkey by default since OpenSSH recommends not using
Packit Service a04d08
# it.
Packit Service a04d08
HOST_KEY_PUBLISH_BLACKLIST = ['dsa']
Packit Service a04d08
Packit Service a04d08
CONFIG_KEY_TO_FILE = {}
Packit Service a04d08
PRIV_TO_PUB = {}
Packit Service a04d08
for k in GENERATE_KEY_NAMES:
Packit Service a04d08
    CONFIG_KEY_TO_FILE.update({"%s_private" % k: (KEY_FILE_TPL % k, 0o600)})
Packit Service a04d08
    CONFIG_KEY_TO_FILE.update(
Packit Service a04d08
        {"%s_public" % k: (KEY_FILE_TPL % k + ".pub", 0o600)})
Packit Service a04d08
    PRIV_TO_PUB["%s_private" % k] = "%s_public" % k
Packit Service a04d08
Packit Service a04d08
KEY_GEN_TPL = 'o=$(ssh-keygen -yf "%s") && echo "$o" root@localhost > "%s"'
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def handle(_name, cfg, cloud, log, _args):
Packit Service a04d08
Packit Service a04d08
    # remove the static keys from the pristine image
Packit Service a04d08
    if cfg.get("ssh_deletekeys", True):
Packit Service a04d08
        key_pth = os.path.join("/etc/ssh/", "ssh_host_*key*")
Packit Service a04d08
        for f in glob.glob(key_pth):
Packit Service a04d08
            try:
Packit Service a04d08
                util.del_file(f)
Packit Service a04d08
            except Exception:
Packit Service a04d08
                util.logexc(log, "Failed deleting key file %s", f)
Packit Service a04d08
Packit Service a04d08
    if "ssh_keys" in cfg:
Packit Service a04d08
        # if there are keys in cloud-config, use them
Packit Service a04d08
        for (key, val) in cfg["ssh_keys"].items():
Packit Service a04d08
            if key in CONFIG_KEY_TO_FILE:
Packit Service a04d08
                tgt_fn = CONFIG_KEY_TO_FILE[key][0]
Packit Service a04d08
                tgt_perms = CONFIG_KEY_TO_FILE[key][1]
Packit Service a04d08
                util.write_file(tgt_fn, val, tgt_perms)
Packit Service a04d08
Packit Service a04d08
        for (priv, pub) in PRIV_TO_PUB.items():
Packit Service a04d08
            if pub in cfg['ssh_keys'] or priv not in cfg['ssh_keys']:
Packit Service a04d08
                continue
Packit Service a04d08
            pair = (CONFIG_KEY_TO_FILE[priv][0], CONFIG_KEY_TO_FILE[pub][0])
Packit Service a04d08
            cmd = ['sh', '-xc', KEY_GEN_TPL % pair]
Packit Service a04d08
            try:
Packit Service a04d08
                # TODO(harlowja): Is this guard needed?
Packit Service a04d08
                with util.SeLinuxGuard("/etc/ssh", recursive=True):
Packit Service 751c4a
                    subp.subp(cmd, capture=False)
Packit Service a04d08
                log.debug("Generated a key for %s from %s", pair[0], pair[1])
Packit Service a04d08
            except Exception:
Packit Service a04d08
                util.logexc(log, "Failed generated a key for %s from %s",
Packit Service a04d08
                            pair[0], pair[1])
Packit Service a04d08
    else:
Packit Service a04d08
        # if not, generate them
Packit Service a04d08
        genkeys = util.get_cfg_option_list(cfg,
Packit Service a04d08
                                           'ssh_genkeytypes',
Packit Service a04d08
                                           GENERATE_KEY_NAMES)
Packit Service a04d08
        lang_c = os.environ.copy()
Packit Service a04d08
        lang_c['LANG'] = 'C'
Packit Service a04d08
        for keytype in genkeys:
Packit Service a04d08
            keyfile = KEY_FILE_TPL % (keytype)
Packit Service a04d08
            if os.path.exists(keyfile):
Packit Service a04d08
                continue
Packit Service a04d08
            util.ensure_dir(os.path.dirname(keyfile))
Packit Service a04d08
            cmd = ['ssh-keygen', '-t', keytype, '-N', '', '-f', keyfile]
Packit Service a04d08
Packit Service a04d08
            # TODO(harlowja): Is this guard needed?
Packit Service a04d08
            with util.SeLinuxGuard("/etc/ssh", recursive=True):
Packit Service a04d08
                try:
Packit Service 751c4a
                    out, err = subp.subp(cmd, capture=True, env=lang_c)
Packit Service a04d08
                    sys.stdout.write(util.decode_binary(out))
Packit Service 751c4a
                except subp.ProcessExecutionError as e:
Packit Service a04d08
                    err = util.decode_binary(e.stderr).lower()
Packit Service a04d08
                    if (e.exit_code == 1 and
Packit Service a04d08
                            err.lower().startswith("unknown key")):
Packit Service a04d08
                        log.debug("ssh-keygen: unknown key type '%s'", keytype)
Packit Service a04d08
                    else:
Packit Service a04d08
                        util.logexc(log, "Failed generating key type %s to "
Packit Service a04d08
                                    "file %s", keytype, keyfile)
Packit Service a04d08
Packit Service a04d08
    if "ssh_publish_hostkeys" in cfg:
Packit Service a04d08
        host_key_blacklist = util.get_cfg_option_list(
Packit Service a04d08
            cfg["ssh_publish_hostkeys"], "blacklist",
Packit Service a04d08
            HOST_KEY_PUBLISH_BLACKLIST)
Packit Service a04d08
        publish_hostkeys = util.get_cfg_option_bool(
Packit Service a04d08
            cfg["ssh_publish_hostkeys"], "enabled", PUBLISH_HOST_KEYS)
Packit Service a04d08
    else:
Packit Service a04d08
        host_key_blacklist = HOST_KEY_PUBLISH_BLACKLIST
Packit Service a04d08
        publish_hostkeys = PUBLISH_HOST_KEYS
Packit Service a04d08
Packit Service a04d08
    if publish_hostkeys:
Packit Service a04d08
        hostkeys = get_public_host_keys(blacklist=host_key_blacklist)
Packit Service a04d08
        try:
Packit Service a04d08
            cloud.datasource.publish_host_keys(hostkeys)
Packit Service a04d08
        except Exception:
Packit Service a04d08
            util.logexc(log, "Publishing host keys failed!")
Packit Service a04d08
Packit Service a04d08
    try:
Packit Service a04d08
        (users, _groups) = ug_util.normalize_users_groups(cfg, cloud.distro)
Packit Service a04d08
        (user, _user_config) = ug_util.extract_default(users)
Packit Service a04d08
        disable_root = util.get_cfg_option_bool(cfg, "disable_root", True)
Packit Service a04d08
        disable_root_opts = util.get_cfg_option_str(cfg, "disable_root_opts",
Packit Service a04d08
                                                    ssh_util.DISABLE_USER_OPTS)
Packit Service a04d08
Packit Service a04d08
        keys = []
Packit Service a04d08
        if util.get_cfg_option_bool(cfg, 'allow_public_ssh_keys', True):
Packit Service a04d08
            keys = cloud.get_public_ssh_keys() or []
Packit Service a04d08
        else:
Packit Service 751c4a
            log.debug('Skipping import of publish SSH keys per '
Packit Service a04d08
                      'config setting: allow_public_ssh_keys=False')
Packit Service a04d08
Packit Service a04d08
        if "ssh_authorized_keys" in cfg:
Packit Service a04d08
            cfgkeys = cfg["ssh_authorized_keys"]
Packit Service a04d08
            keys.extend(cfgkeys)
Packit Service a04d08
Packit Service a04d08
        apply_credentials(keys, user, disable_root, disable_root_opts)
Packit Service a04d08
    except Exception:
Packit Service 751c4a
        util.logexc(log, "Applying SSH credentials failed!")
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def apply_credentials(keys, user, disable_root, disable_root_opts):
Packit Service a04d08
Packit Service a04d08
    keys = set(keys)
Packit Service a04d08
    if user:
Packit Service a04d08
        ssh_util.setup_user_keys(keys, user)
Packit Service a04d08
Packit Service a04d08
    if disable_root:
Packit Service a04d08
        if not user:
Packit Service a04d08
            user = "NONE"
Packit Service a04d08
        key_prefix = disable_root_opts.replace('$USER', user)
Packit Service a04d08
        key_prefix = key_prefix.replace('$DISABLE_USER', 'root')
Packit Service a04d08
    else:
Packit Service a04d08
        key_prefix = ''
Packit Service a04d08
Packit Service a04d08
    ssh_util.setup_user_keys(keys, 'root', options=key_prefix)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def get_public_host_keys(blacklist=None):
Packit Service a04d08
    """Read host keys from /etc/ssh/*.pub files and return them as a list.
Packit Service a04d08
Packit Service a04d08
    @param blacklist: List of key types to ignore. e.g. ['dsa', 'rsa']
Packit Service a04d08
    @returns: List of keys, each formatted as a two-element tuple.
Packit Service a04d08
        e.g. [('ssh-rsa', 'AAAAB3Nz...'), ('ssh-ed25519', 'AAAAC3Nx...')]
Packit Service a04d08
    """
Packit Service a04d08
    public_key_file_tmpl = '%s.pub' % (KEY_FILE_TPL,)
Packit Service a04d08
    key_list = []
Packit Service a04d08
    blacklist_files = []
Packit Service a04d08
    if blacklist:
Packit Service a04d08
        # Convert blacklist to filenames:
Packit Service a04d08
        # 'dsa' -> '/etc/ssh/ssh_host_dsa_key.pub'
Packit Service a04d08
        blacklist_files = [public_key_file_tmpl % (key_type,)
Packit Service a04d08
                           for key_type in blacklist]
Packit Service a04d08
    # Get list of public key files and filter out blacklisted files.
Packit Service a04d08
    file_list = [hostfile for hostfile
Packit Service a04d08
                 in glob.glob(public_key_file_tmpl % ('*',))
Packit Service a04d08
                 if hostfile not in blacklist_files]
Packit Service a04d08
Packit Service a04d08
    # Read host key files, retrieve first two fields as a tuple and
Packit Service a04d08
    # append that tuple to key_list.
Packit Service a04d08
    for file_name in file_list:
Packit Service a04d08
        file_contents = util.load_file(file_name)
Packit Service a04d08
        key_data = file_contents.split()
Packit Service a04d08
        if key_data and len(key_data) > 1:
Packit Service a04d08
            key_list.append(tuple(key_data[:2]))
Packit Service a04d08
    return key_list
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab