diff options
Diffstat (limited to 'cloudinit/ssh_util.py')
-rw-r--r-- | cloudinit/ssh_util.py | 172 |
1 files changed, 109 insertions, 63 deletions
diff --git a/cloudinit/ssh_util.py b/cloudinit/ssh_util.py index 33679dcc..ab4c63aa 100644 --- a/cloudinit/ssh_util.py +++ b/cloudinit/ssh_util.py @@ -60,14 +60,16 @@ _DISABLE_USER_SSH_EXIT = 142 DISABLE_USER_OPTS = ( "no-port-forwarding,no-agent-forwarding," - "no-X11-forwarding,command=\"echo \'Please login as the user \\\"$USER\\\"" - " rather than the user \\\"$DISABLE_USER\\\".\';echo;sleep 10;" - "exit " + str(_DISABLE_USER_SSH_EXIT) + "\"") + 'no-X11-forwarding,command="echo \'Please login as the user \\"$USER\\"' + ' rather than the user \\"$DISABLE_USER\\".\';echo;sleep 10;' + "exit " + str(_DISABLE_USER_SSH_EXIT) + '"' +) class AuthKeyLine(object): - def __init__(self, source, keytype=None, base64=None, - comment=None, options=None): + def __init__( + self, source, keytype=None, base64=None, comment=None, options=None + ): self.base64 = base64 self.comment = comment self.options = options @@ -75,7 +77,7 @@ class AuthKeyLine(object): self.source = source def valid(self): - return (self.base64 and self.keytype) + return self.base64 and self.keytype def __str__(self): toks = [] @@ -90,7 +92,7 @@ class AuthKeyLine(object): if not toks: return self.source else: - return ' '.join(toks) + return " ".join(toks) class AuthKeyLineParser(object): @@ -121,8 +123,7 @@ class AuthKeyLineParser(object): """ quoted = False i = 0 - while (i < len(ent) and - ((quoted) or (ent[i] not in (" ", "\t")))): + while i < len(ent) and ((quoted) or (ent[i] not in (" ", "\t"))): curc = ent[i] if i + 1 >= len(ent): i = i + 1 @@ -143,7 +144,7 @@ class AuthKeyLineParser(object): 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() == '': + if line.startswith("#") or line.strip() == "": return AuthKeyLine(src_line) def parse_ssh_key(ent): @@ -174,8 +175,13 @@ class AuthKeyLineParser(object): except TypeError: return AuthKeyLine(src_line) - return AuthKeyLine(src_line, keytype=keytype, base64=base64, - comment=comment, options=options) + return AuthKeyLine( + src_line, + keytype=keytype, + base64=base64, + comment=comment, + options=options, + ) def parse_authorized_keys(fnames): @@ -218,15 +224,15 @@ def update_authorized_keys(old_entries, keys): lines = [str(b) for b in old_entries] # Ensure it ends with a newline - lines.append('') - return '\n'.join(lines) + 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) + return (os.path.join(pw_ent.pw_dir, ".ssh"), pw_ent) def render_authorizedkeysfile_paths(value, homedir, username): @@ -269,9 +275,14 @@ def check_permissions(username, current_path, full_path, is_file, strictmodes): # 1. owner must be either root or the user itself owner = util.get_owner(current_path) if strictmodes and owner != username and owner != "root": - LOG.debug("Path %s in %s must be own by user %s or" - " by root, but instead is own by %s. Ignoring key.", - current_path, full_path, username, owner) + LOG.debug( + "Path %s in %s must be own by user %s or" + " by root, but instead is own by %s. Ignoring key.", + current_path, + full_path, + username, + owner, + ) return False parent_permission = util.get_permissions(current_path) @@ -291,17 +302,24 @@ def check_permissions(username, current_path, full_path, is_file, strictmodes): minimal_permissions &= 0o007 if parent_permission & minimal_permissions == 0: - LOG.debug("Path %s in %s must be accessible by user %s," - " check its permissions", - current_path, full_path, username) + LOG.debug( + "Path %s in %s must be accessible by user %s," + " check its permissions", + current_path, + full_path, + username, + ) return False # 3. no write permission (w) is given to group and world users (022) # Group and world user can still have +rx. if strictmodes and parent_permission & 0o022 != 0: - LOG.debug("Path %s in %s must not give write" - "permission to group or world users. Ignoring key.", - current_path, full_path) + LOG.debug( + "Path %s in %s must not give write" + "permission to group or world users. Ignoring key.", + current_path, + full_path, + ) return False return True @@ -326,17 +344,20 @@ def check_create_path(username, filename, strictmodes): if os.path.islink(parent_folder): LOG.debug( "Invalid directory. Symlink exists in path: %s", - parent_folder) + parent_folder, + ) return False if os.path.isfile(parent_folder): LOG.debug( - "Invalid directory. File exists in path: %s", - parent_folder) + "Invalid directory. File exists in path: %s", parent_folder + ) return False - if (home_folder.startswith(parent_folder) or - parent_folder == user_pwent.pw_dir): + if ( + home_folder.startswith(parent_folder) + or parent_folder == user_pwent.pw_dir + ): continue if not os.path.exists(parent_folder): @@ -354,8 +375,9 @@ def check_create_path(username, filename, strictmodes): os.makedirs(parent_folder, mode=mode, exist_ok=True) util.chownbyid(parent_folder, uid, gid) - permissions = check_permissions(username, parent_folder, - filename, False, strictmodes) + permissions = check_permissions( + username, parent_folder, filename, False, strictmodes + ) if not permissions: return False @@ -367,11 +389,12 @@ def check_create_path(username, filename, strictmodes): if not os.path.exists(filename): # if file does not exist: we need to create it, since the # folders at this point exist and have right permissions - util.write_file(filename, '', mode=0o600, ensure_dir_exists=True) + util.write_file(filename, "", mode=0o600, ensure_dir_exists=True) util.chownbyid(filename, user_pwent.pw_uid, user_pwent.pw_gid) - permissions = check_permissions(username, filename, - filename, True, strictmodes) + permissions = check_permissions( + username, filename, filename, True, strictmodes + ) if not permissions: return False except (IOError, OSError) as e: @@ -383,34 +406,44 @@ def check_create_path(username, filename, strictmodes): def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG): (ssh_dir, pw_ent) = users_ssh_info(username) - default_authorizedkeys_file = os.path.join(ssh_dir, 'authorized_keys') + default_authorizedkeys_file = os.path.join(ssh_dir, "authorized_keys") user_authorizedkeys_file = default_authorizedkeys_file auth_key_fns = [] with util.SeLinuxGuard(ssh_dir, recursive=True): try: ssh_cfg = parse_ssh_config_map(sshd_cfg_file) - key_paths = ssh_cfg.get("authorizedkeysfile", - "%h/.ssh/authorized_keys") + key_paths = ssh_cfg.get( + "authorizedkeysfile", "%h/.ssh/authorized_keys" + ) strictmodes = ssh_cfg.get("strictmodes", "yes") auth_key_fns = render_authorizedkeysfile_paths( - key_paths, pw_ent.pw_dir, username) + key_paths, pw_ent.pw_dir, username + ) except (IOError, OSError): # Give up and use a default key filename auth_key_fns[0] = default_authorizedkeys_file - util.logexc(LOG, "Failed extracting 'AuthorizedKeysFile' in SSH " - "config from %r, using 'AuthorizedKeysFile' file " - "%r instead", DEF_SSHD_CFG, auth_key_fns[0]) + util.logexc( + LOG, + "Failed extracting 'AuthorizedKeysFile' in SSH " + "config from %r, using 'AuthorizedKeysFile' file " + "%r instead", + DEF_SSHD_CFG, + auth_key_fns[0], + ) # check if one of the keys is the user's one and has the right permissions for key_path, auth_key_fn in zip(key_paths.split(), auth_key_fns): - if any([ - '%u' in key_path, - '%h' in key_path, - auth_key_fn.startswith('{}/'.format(pw_ent.pw_dir)) - ]): - permissions_ok = check_create_path(username, auth_key_fn, - strictmodes == "yes") + if any( + [ + "%u" in key_path, + "%h" in key_path, + auth_key_fn.startswith("{}/".format(pw_ent.pw_dir)), + ] + ): + permissions_ok = check_create_path( + username, auth_key_fn, strictmodes == "yes" + ) if permissions_ok: user_authorizedkeys_file = auth_key_fn break @@ -418,11 +451,13 @@ def extract_authorized_keys(username, sshd_cfg_file=DEF_SSHD_CFG): if user_authorizedkeys_file != default_authorizedkeys_file: LOG.debug( "AuthorizedKeysFile has an user-specific authorized_keys, " - "using %s", user_authorizedkeys_file) + "using %s", + user_authorizedkeys_file, + ) return ( user_authorizedkeys_file, - parse_authorized_keys([user_authorizedkeys_file]) + parse_authorized_keys([user_authorizedkeys_file]), ) @@ -485,11 +520,13 @@ def parse_ssh_config_lines(lines): key, val = line.split(None, 1) except ValueError: try: - key, val = line.split('=', 1) + key, val = line.split("=", 1) except ValueError: LOG.debug( - "sshd_config: option \"%s\" has no key/value pair," - " skipping it", line) + 'sshd_config: option "%s" has no key/value pair,' + " skipping it", + line, + ) continue ret.append(SshdConfigLine(line, key, val)) return ret @@ -516,9 +553,10 @@ def update_ssh_config(updates, fname=DEF_SSHD_CFG): changed = update_ssh_config_lines(lines=lines, updates=updates) if changed: util.write_file( - fname, "\n".join( - [str(line) for line in lines] - ) + "\n", preserve_mode=True) + fname, + "\n".join([str(line) for line in lines]) + "\n", + preserve_mode=True, + ) return len(changed) != 0 @@ -542,12 +580,18 @@ def update_ssh_config_lines(lines, updates): value = updates[key] found.add(key) if line.value == value: - LOG.debug("line %d: option %s already set to %s", - i, key, value) + LOG.debug( + "line %d: option %s already set to %s", i, key, value + ) else: changed.append(key) - LOG.debug("line %d: option %s updated %s -> %s", i, - key, line.value, value) + LOG.debug( + "line %d: option %s updated %s -> %s", + i, + key, + line.value, + value, + ) line.value = value if len(found) != len(updates): @@ -555,9 +599,11 @@ def update_ssh_config_lines(lines, updates): if key in found: continue changed.append(key) - lines.append(SshdConfigLine('', key, value)) - LOG.debug("line %d: option %s added with %s", - len(lines), key, value) + lines.append(SshdConfigLine("", key, value)) + LOG.debug( + "line %d: option %s added with %s", len(lines), key, value + ) return changed + # vi: ts=4 expandtab |