diff options
Diffstat (limited to 'cloudinit/ssh_util.py')
-rw-r--r-- | cloudinit/ssh_util.py | 105 |
1 files changed, 62 insertions, 43 deletions
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py index 93fd55dd..c97b3819 100644 --- a/cloudinit/ssh_util.py +++ b/cloudinit/ssh_util.py @@ -19,6 +19,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +from StringIO import StringIO + +import csv import os import pwd @@ -26,6 +29,7 @@ from cloudinit import log as logging from cloudinit import util LOG = logging.getLogger(__name__) +DEF_SSHD_CFG = "/etc/ssh/sshd_config" class AuthKeyEntry(object): @@ -52,6 +56,40 @@ class AuthKeyEntry(object): self.line = str(line) (self.value, self.components) = self._parse(self.line, def_opt) + 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] + options_lst = [] + reader = csv.reader(StringIO(options), quoting=csv.QUOTE_NONE) + for row in reader: + for e in row: + e = e.strip() + if e: + options_lst.append(e) + toks = [] + if i + 1 < len(ent): + toks = ent[i + 1:].split(None, 3) + return (options_lst, toks) + def _form_components(self, toks): components = {} if len(toks) == 1: @@ -81,29 +119,10 @@ class AuthKeyEntry(object): if len(toks) < 4: tmp_components.update(self._form_components(toks)) else: - # taken from auth_rsa_key_allowed in auth-rsa.c - i = 0 - quoted = False - try: - while (i < len(ent) and - ((quoted) or (ent[i] not in (" ", "\t")))): - curc = ent[i] - nextc = ent[i + 1] - if curc == "\\" and nextc == '"': - i = i + 1 - elif curc == '"': - quoted = not quoted - i = i + 1 - except IndexError: - return (False, {}) - try: - options = ent[0:i] - toks = ent[i + 1:].split(None, 3) - if options: - tmp_components['options'] = options - tmp_components.update(self._form_components(toks)) - except (IndexError, ValueError): - return (False, {}) + (options, toks) = self._extract_options(ent) + if options: + tmp_components['options'] = ",".join(options) + tmp_components.update(self._form_components(toks)) # We got some useful value! return (True, tmp_components) @@ -125,7 +144,7 @@ class AuthKeyEntry(object): return ' '.join(toks) -def update_authorized_keys(fname, keys): +def _update_authorized_keys(fname, keys): lines = [] try: if os.path.isfile(fname): @@ -159,9 +178,11 @@ def update_authorized_keys(fname, keys): return '\n'.join(lines) -def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config"): - pwent = pwd.getpwnam(user) +def setup_user_keys(keys, user, key_prefix, sshd_config_fn=None): + if not sshd_config_fn: + sshd_config_fn = DEF_SSHD_CFG + pwent = pwd.getpwnam(user) ssh_dir = os.path.join(pwent.pw_dir, '.ssh') if not os.path.exists(ssh_dir): util.ensure_dir(ssh_dir, mode=0700) @@ -173,14 +194,12 @@ def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config with util.SeLinuxGuard(ssh_dir, recursive=True): try: - """ - 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(sshd_config_fn) + # 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(sshd_config_fn) akeys = ssh_cfg.get("authorizedkeysfile", '') akeys = akeys.strip() if not akeys: @@ -193,22 +212,22 @@ def setup_user_keys(keys, user, key_prefix, sshd_config_fn="/etc/ssh/sshd_config authorized_keys = akeys except (IOError, OSError): authorized_keys = os.path.join(ssh_dir, 'authorized_keys') - LOG.exception(("Failed extracting 'AuthorizedKeysFile' in ssh config" - " from %s, using 'AuthorizedKeysFile' file %s instead."), + LOG.exception(("Failed extracting 'AuthorizedKeysFile'" + " in ssh config" + " from %s, using 'AuthorizedKeysFile' file" + " %s instead"), sshd_config_fn, authorized_keys) - content = update_authorized_keys(authorized_keys, key_entries) + content = _update_authorized_keys(authorized_keys, key_entries) util.ensure_dir(os.path.dirname(authorized_keys), mode=0700) util.write_file(authorized_keys, content, mode=0600) util.chownbyid(authorized_keys, pwent.pw_uid, pwent.pw_gid) -def parse_ssh_config(fname): - """ - The file contains keyword-argu-ment 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 - """ +def _parse_ssh_config(fname): + # 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 ret = {} if not os.path.isfile(fname): return ret |