summaryrefslogtreecommitdiff
path: root/cloudinit/ssh_util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/ssh_util.py')
-rw-r--r--cloudinit/ssh_util.py314
1 files changed, 0 insertions, 314 deletions
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py
deleted file mode 100644
index c74a7ae2..00000000
--- a/cloudinit/ssh_util.py
+++ /dev/null
@@ -1,314 +0,0 @@
-#!/usr/bin/python
-# vi: ts=4 expandtab
-#
-# Copyright (C) 2012 Canonical Ltd.
-# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
-#
-# Author: Scott Moser <scott.moser@canonical.com>
-# Author: Juerg Hafliger <juerg.haefliger@hp.com>
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3, as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import pwd
-
-from cloudinit import log as logging
-from cloudinit import util
-
-LOG = logging.getLogger(__name__)
-
-# See: man sshd_config
-DEF_SSHD_CFG = "/etc/ssh/sshd_config"
-
-# taken from openssh source key.c/key_type_from_name
-VALID_KEY_TYPES = (
- "rsa", "dsa", "ssh-rsa", "ssh-dss", "ecdsa",
- "ssh-rsa-cert-v00@openssh.com", "ssh-dss-cert-v00@openssh.com",
- "ssh-rsa-cert-v00@openssh.com", "ssh-dss-cert-v00@openssh.com",
- "ssh-rsa-cert-v01@openssh.com", "ssh-dss-cert-v01@openssh.com",
- "ecdsa-sha2-nistp256-cert-v01@openssh.com",
- "ecdsa-sha2-nistp384-cert-v01@openssh.com",
- "ecdsa-sha2-nistp521-cert-v01@openssh.com")
-
-
-class AuthKeyLine(object):
- def __init__(self, source, keytype=None, base64=None,
- comment=None, options=None):
- self.base64 = base64
- self.comment = comment
- self.options = options
- self.keytype = keytype
- self.source = source
-
- def valid(self):
- return (self.base64 and self.keytype)
-
- def __str__(self):
- toks = []
- if self.options:
- toks.append(self.options)
- if self.keytype:
- toks.append(self.keytype)
- if self.base64:
- toks.append(self.base64)
- if self.comment:
- toks.append(self.comment)
- if not toks:
- return self.source
- else:
- return ' '.join(toks)
-
-
-class AuthKeyLineParser(object):
- """
- AUTHORIZED_KEYS FILE FORMAT
- AuthorizedKeysFile specifies the file containing public keys for public
- key authentication; if none is specified, the default is
- ~/.ssh/authorized_keys. Each line of the file contains one key (empty
- (because of the size of the public key encoding) up to a limit of 8 kilo-
- bytes, which permits DSA keys up to 8 kilobits and RSA keys up to 16
- kilobits. You don't want to type them in; instead, copy the
- identity.pub, id_dsa.pub, or the id_rsa.pub file and edit it.
-
- sshd enforces a minimum RSA key modulus size for protocol 1 and protocol
- 2 keys of 768 bits.
-
- The options (if present) consist of comma-separated option specifica-
- tions. No spaces are permitted, except within double quotes. The fol-
- lowing option specifications are supported (note that option keywords are
- case-insensitive):
- """
-
- def _extract_options(self, ent):
- """
- The options (if present) consist of comma-separated option specifica-
- tions. No spaces are permitted, except within double quotes.
- Note that option keywords are case-insensitive.
- """
- quoted = False
- i = 0
- while (i < len(ent) and
- ((quoted) or (ent[i] not in (" ", "\t")))):
- curc = ent[i]
- if i + 1 >= len(ent):
- i = i + 1
- break
- nextc = ent[i + 1]
- if curc == "\\" and nextc == '"':
- i = i + 1
- elif curc == '"':
- quoted = not quoted
- i = i + 1
-
- options = ent[0:i]
-
- # Return the rest of the string in 'remain'
- remain = ent[i:].lstrip()
- return (options, remain)
-
- def parse(self, src_line, options=None):
- # modeled after opensshes auth2-pubkey.c:user_key_allowed2
- line = src_line.rstrip("\r\n")
- if line.startswith("#") or line.strip() == '':
- return AuthKeyLine(src_line)
-
- def parse_ssh_key(ent):
- # return ketype, key, [comment]
- toks = ent.split(None, 2)
- if len(toks) < 2:
- raise TypeError("To few fields: %s" % len(toks))
- if toks[0] not in VALID_KEY_TYPES:
- raise TypeError("Invalid keytype %s" % toks[0])
-
- # valid key type and 2 or 3 fields:
- if len(toks) == 2:
- # no comment in line
- toks.append("")
-
- return toks
-
- ent = line.strip()
- try:
- (keytype, base64, comment) = parse_ssh_key(ent)
- except TypeError:
- (keyopts, remain) = self._extract_options(ent)
- if options is None:
- options = keyopts
-
- try:
- (keytype, base64, comment) = parse_ssh_key(remain)
- except TypeError:
- return AuthKeyLine(src_line)
-
- return AuthKeyLine(src_line, keytype=keytype, base64=base64,
- comment=comment, options=options)
-
-
-def parse_authorized_keys(fname):
- lines = []
- try:
- if os.path.isfile(fname):
- lines = util.load_file(fname).splitlines()
- except (IOError, OSError):
- util.logexc(LOG, "Error reading lines from %s", fname)
- lines = []
-
- parser = AuthKeyLineParser()
- contents = []
- for line in lines:
- contents.append(parser.parse(line))
- return contents
-
-
-def update_authorized_keys(old_entries, keys):
- to_add = list(keys)
-
- for i in range(0, len(old_entries)):
- ent = old_entries[i]
- if not ent.valid():
- continue
- # Replace those with the same base64
- for k in keys:
- if not ent.valid():
- continue
- if k.base64 == ent.base64:
- # Replace it with our better one
- ent = k
- # Don't add it later
- if k in to_add:
- to_add.remove(k)
- old_entries[i] = ent
-
- # Now append any entries we did not match above
- for key in to_add:
- old_entries.append(key)
-
- # Now format them back to strings...
- lines = [str(b) for b in old_entries]
-
- # Ensure it ends with a newline
- lines.append('')
- return '\n'.join(lines)
-
-
-def users_ssh_info(username):
- pw_ent = pwd.getpwnam(username)
- if not pw_ent or not pw_ent.pw_dir:
- raise RuntimeError("Unable to get ssh info for user %r" % (username))
- return (os.path.join(pw_ent.pw_dir, '.ssh'), pw_ent)
-
-
-def extract_authorized_keys(username):
- (ssh_dir, pw_ent) = users_ssh_info(username)
- auth_key_fn = None
- with util.SeLinuxGuard(ssh_dir, recursive=True):
- try:
- # The 'AuthorizedKeysFile' may contain tokens
- # of the form %T which are substituted during connection set-up.
- # The following tokens are defined: %% is replaced by a literal
- # '%', %h is replaced by the home directory of the user being
- # authenticated and %u is replaced by the username of that user.
- ssh_cfg = parse_ssh_config_map(DEF_SSHD_CFG)
- auth_key_fn = ssh_cfg.get("authorizedkeysfile", '').strip()
- if not auth_key_fn:
- auth_key_fn = "%h/.ssh/authorized_keys"
- auth_key_fn = auth_key_fn.replace("%h", pw_ent.pw_dir)
- auth_key_fn = auth_key_fn.replace("%u", username)
- auth_key_fn = auth_key_fn.replace("%%", '%')
- if not auth_key_fn.startswith('/'):
- auth_key_fn = os.path.join(pw_ent.pw_dir, auth_key_fn)
- except (IOError, OSError):
- # Give up and use a default key filename
- auth_key_fn = os.path.join(ssh_dir, 'authorized_keys')
- util.logexc(LOG, "Failed extracting 'AuthorizedKeysFile' in ssh "
- "config from %r, using 'AuthorizedKeysFile' file "
- "%r instead", DEF_SSHD_CFG, auth_key_fn)
- return (auth_key_fn, parse_authorized_keys(auth_key_fn))
-
-
-def setup_user_keys(keys, username, options=None):
- # Make sure the users .ssh dir is setup accordingly
- (ssh_dir, pwent) = users_ssh_info(username)
- if not os.path.isdir(ssh_dir):
- util.ensure_dir(ssh_dir, mode=0o700)
- util.chownbyid(ssh_dir, pwent.pw_uid, pwent.pw_gid)
-
- # Turn the 'update' keys given into actual entries
- parser = AuthKeyLineParser()
- key_entries = []
- for k in keys:
- key_entries.append(parser.parse(str(k), options=options))
-
- # Extract the old and make the new
- (auth_key_fn, auth_key_entries) = extract_authorized_keys(username)
- with util.SeLinuxGuard(ssh_dir, recursive=True):
- content = update_authorized_keys(auth_key_entries, key_entries)
- util.ensure_dir(os.path.dirname(auth_key_fn), mode=0o700)
- util.write_file(auth_key_fn, content, mode=0o600)
- util.chownbyid(auth_key_fn, pwent.pw_uid, pwent.pw_gid)
-
-
-class SshdConfigLine(object):
- def __init__(self, line, k=None, v=None):
- self.line = line
- self._key = k
- self.value = v
-
- @property
- def key(self):
- if self._key is None:
- return None
- # Keywords are case-insensitive
- return self._key.lower()
-
- def __str__(self):
- if self._key is None:
- return str(self.line)
- else:
- v = str(self._key)
- if self.value:
- v += " " + str(self.value)
- return v
-
-
-def parse_ssh_config(fname):
- # See: man sshd_config
- # The file contains keyword-argument pairs, one per line.
- # Lines starting with '#' and empty lines are interpreted as comments.
- # Note: key-words are case-insensitive and arguments are case-sensitive
- lines = []
- if not os.path.isfile(fname):
- return lines
- for line in util.load_file(fname).splitlines():
- line = line.strip()
- if not line or line.startswith("#"):
- lines.append(SshdConfigLine(line))
- continue
- try:
- key, val = line.split(None, 1)
- except ValueError:
- key, val = line.split('=', 1)
- lines.append(SshdConfigLine(line, key, val))
- return lines
-
-
-def parse_ssh_config_map(fname):
- lines = parse_ssh_config(fname)
- if not lines:
- return {}
- ret = {}
- for line in lines:
- if not line.key:
- continue
- ret[line.key] = line.value
- return ret