cloud-init/cloudinit/SshUtil.py
Scott Moser 50f694070e Restore created files' selinux contexts
This adds a restorecon_if_possible method which uses selinux
python module, and uses that for files modified in /etc.

taken from
git://pkgs.fedoraproject.org/cloud-init.git
commit 87f33190f43d2b26cced4597e7298835024466c2
Author: Garrett Holmstrom <gholms@fedoraproject.org>
Patch3: cloud-init-0.6.2-filecontext.patch
2011-10-30 17:17:03 -04:00

197 lines
6.6 KiB
Python

#!/usr/bin/python
import os
import os.path
import cloudinit.util as util
class AuthKeyEntry():
# lines are options, keytype, base64-encoded key, comment
# man page says the following which I did not understand:
# The options field is optional; its presence is determined by whether
# the line starts with a number or not (the options field never starts
# with a number)
options = None
keytype = None
base64 = None
comment = None
is_comment = False
line_in = ""
def __init__(self, line, def_opt=None):
line=line.rstrip("\n\r")
self.line_in = line
if line.startswith("#") or line.strip() == "":
self.is_comment = True
else:
ent = line.strip()
toks = ent.split(None,3)
if len(toks) == 1:
self.base64 = toks[0]
elif len(toks) == 2:
(self.base64, self.comment) = toks
elif len(toks) == 3:
(self.keytype, self.base64, self.comment) = toks
elif len(toks) == 4:
i = 0
ent = line.strip()
quoted = False
# taken from auth_rsa_key_allowed in auth-rsa.c
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 as e:
self.is_comment = True
return()
try:
self.options = ent[0:i]
(self.keytype, self.base64, self.comment) = \
ent[i+1:].split(None,3)
except ValueError as e:
# we did not understand this line
self.is_comment = True
if self.options == None and def_opt:
self.options = def_opt
return
def debug(self):
print("line_in=%s\ncomment: %s\noptions=%s\nkeytype=%s\nbase64=%s\ncomment=%s\n" %
(self.line_in, self.is_comment, self.options, self.keytype, self.base64, self.comment)),
def __repr__(self):
if self.is_comment:
return(self.line_in)
else:
toks = [ ]
for e in (self.options, self.keytype, self.base64, self.comment):
if e:
toks.append(e)
return(' '.join(toks))
def update_authorized_keys(fname, keys):
# keys is a list of AuthKeyEntries
# key_prefix is the prefix (options) to prepend
try:
fp = open(fname, "r")
lines = fp.readlines() # lines have carriage return
fp.close()
except IOError as e:
lines = [ ]
ka_stats = { } # keys_added status
for k in keys:
ka_stats[k] = False
to_add = []
for key in keys:
to_add.append(key)
for i in range(0,len(lines)):
ent = AuthKeyEntry(lines[i])
for k in keys:
if k.base64 == ent.base64 and not k.is_comment:
ent = k
try:
to_add.remove(k)
except ValueError:
pass
lines[i] = str(ent)
# now append any entries we did not match above
for key in to_add:
lines.append(str(key))
if len(lines) == 0:
return("")
else:
return('\n'.join(lines) + "\n")
def setup_user_keys(keys, user, key_prefix, log=None):
import pwd
saved_umask = os.umask(077)
pwent = pwd.getpwnam(user)
ssh_dir = '%s/.ssh' % pwent.pw_dir
if not os.path.exists(ssh_dir):
os.mkdir(ssh_dir)
os.chown(ssh_dir, pwent.pw_uid, pwent.pw_gid)
try:
ssh_cfg = parse_ssh_config()
akeys = ssh_cfg.get("AuthorizedKeysFile","%h/.ssh/authorized_keys")
akeys = akeys.replace("%h", pwent.pw_dir)
akeys = akeys.replace("%u", user)
authorized_keys = akeys
except Exception as e:
authorized_keys = '%s/.ssh/authorized_keys' % pwent.pw_dir
if log:
util.logexc(log)
key_entries = []
for k in keys:
ke = AuthKeyEntry(k, def_opt=key_prefix)
key_entries.append(ke)
content = update_authorized_keys(authorized_keys, key_entries)
util.write_file(authorized_keys, content, 0600)
os.chown(authorized_keys, pwent.pw_uid, pwent.pw_gid)
util.restorecon_if_possible(ssh_dir, recursive=True)
os.umask(saved_umask)
if __name__ == "__main__":
import sys
# usage: orig_file, new_keys, [key_prefix]
# prints out merged, where 'new_keys' will trump old
## example
## ### begin authorized_keys ###
# ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA28CDAGtxSucHezSKqwh1wAs39xdeZTSVmmyMcKDI5Njnd1d/Uhgj/awxP0Whep8eRSm6F+Xgwi0pH1KNPCszPvq+03K+yi3YkYkQIkVBhctK6AP/UmlVQTVmjJdEvgtrppFTjCzf16q0BT0mXX5YFV3csgm8cJn7UveKHkYjJp8= smoser-work
# ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3I7VUf2l5gSn5uavROsc5HRDpZdQueUq5ozemNSj8T7enqKHOEaFoU2VoPgGEWC9RyzSQVeyD6s7APMcE82EtmW4skVEgEGSbDc1pvxzxtchBj78hJP6Cf5TCMFSXw+Fz5rF1dR23QDbN1mkHs7adr8GW4kSWqU7Q7NDwfIrJJtO7Hi42GyXtvEONHbiRPOe8stqUly7MvUoN+5kfjBM8Qqpfl2+FNhTYWpMfYdPUnE7u536WqzFmsaqJctz3gBxH9Ex7dFtrxR4qiqEr9Qtlu3xGn7Bw07/+i1D+ey3ONkZLN+LQ714cgj8fRS4Hj29SCmXp5Kt5/82cD/VN3NtHw== smoser@brickies
# ### end authorized_keys ###
#
# ### begin new_keys ###
# ssh-rsa nonmatch smoser@newhost
# ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEA28CDAGtxSucHezSKqwh1wAs39xdeZTSVmmyMcKDI5Njnd1d/Uhgj/awxP0Whep8eRSm6F+Xgwi0pH1KNPCszPvq+03K+yi3YkYkQIkVBhctK6AP/UmlVQTVmjJdEvgtrppFTjCzf16q0BT0mXX5YFV3csgm8cJn7UveKHkYjJp8= new_comment
# ### end new_keys ###
#
# Then run as:
# program authorized_keys new_keys 'no-port-forwarding,command=\"echo hi world;\"'
def_prefix = None
orig_key_file = sys.argv[1]
new_key_file = sys.argv[2]
if len(sys.argv) > 3:
def_prefix = sys.argv[3]
fp = open(new_key_file)
newkeys = [ ]
for line in fp.readlines():
newkeys.append(AuthKeyEntry(line, def_prefix))
fp.close()
print update_authorized_keys(orig_key_file, newkeys)
def parse_ssh_config(fname="/etc/ssh/sshd_config"):
ret = { }
fp=open(fname)
for l in fp.readlines():
l = l.strip()
if not l or l.startswith("#"):
continue
key,val = l.split(None,1)
ret[key]=val
fp.close()
return(ret)