fedora-ansible/scripts/auth-keys-from-fas

207 lines
7.4 KiB
Python
Executable File

#!/usr/bin/python -tt
#
# Copyright 2012 Red Hat, Inc.
# License: GPLv2+
# Author: Toshio Kuratomi <toshio@fedoraproject.org>
__version_info__ = ((0, 2),)
import os
import grp
import sys
import argparse
from getpass import getpass, getuser
from configobj import ConfigObj, flatten_errors
from fedora.client import AccountSystem, AuthError
from validate import Validator
retries = 0
MAX_RETRIES = 3
def parse_commandline(args):
parser = argparse.ArgumentParser(
description='Generate an ssh authorized_keys file using fas')
parser.add_argument('users', metavar='USERS', type=str, nargs='+',
help='FAS usernames and FAS group names preceeded by "@".'
' For example: toshio skvidal @gitfas')
parser.add_argument('--with-fas-conf', dest='fas_conf',
action='store_true', default=True,
help='Look for credentials in /etc/fas.conf. On hosts that run'
' fasClient to set up users and groups, fas.conf will have'
' a username and password that will work for us. This can only be'
' read if the user is root. Otherwise a different method of'
' getting credentials will be used.')
parser.add_argument('--without-fas-conf', dest='fas_conf',
action='store_false', default=True,
help='Look for credentials in /etc/fas.conf. On hosts that run'
' fasClient to set up users and groups, fas.conf will have'
' a username and password that will work for us. This can only be'
' read if the user is root. Otherwise a different method of'
' getting credentials will be used.')
# The following can be set in the config files so the default value is
# taken from there
parser.add_argument('--limit-from', '-l', action='append', default=None,
help='IP addresses that limit where the keys can be used to'
' ssh from')
parser.add_argument('--with-fas-groups', dest='fas_groups',
action='store_true', default=None,
help='Query fas for members of groups. The default is to use the'
' groups information on the local machine. Querying fas can be'
' used on machines that do not get their group information from'
' fas or if you need group information that has been updated'
' recently but it is slower.')
parser.add_argument('--without-fas-groups', dest='fas_groups',
action='store_false', default=None,
help='Query fas for members of groups. The default is to use the'
' groups information on the local machine. Querying fas can be'
' used on machines that do not get their group information from'
' fas or if you need group information that has been updated'
' recently but it is slower.')
parser.add_argument('--username', '-u', type=str, action='store',
default=None,
help='Use this username when contacting fas. This will not be'
' used if a username and password can be read from /etc/fas.conf')
parsed = parser.parse_args(args)
args_dict = {}
args_dict['fas_conf'] = parsed.fas_conf
if parsed.limit_from is not None:
args_dict['limit_from'] = parsed.limit_from
if parsed.fas_groups is not None:
args_dict['fas_groups'] = parsed.fas_groups
if parsed.username is not None:
args_dict['username'] = parsed.username
users = set()
groups = set()
for entry in parsed.users:
if entry.startswith('@'):
groups.add(entry[1:])
elif entry == sys.argv[0]:
# argparse hasn't already subtracted the program name here....
continue
else:
users.add(entry)
args_dict['users'] = users
args_dict['groups'] = groups
return args_dict
def read_config_files(cfg_files):
config_spec = (
'limit_from = ip_addr_list(default=list())',
'fas_groups = boolean(default=False)',
'username = string(default=%s)' % getuser(),
)
options = ConfigObj(configspec=config_spec)
validator = Validator()
for cfg_file in cfg_files:
cfg_file = os.path.abspath(os.path.expanduser(cfg_file))
cfg = ConfigObj(cfg_file, configspec=config_spec)
options.merge(cfg)
results = options.validate(validator)
if results != True:
for (section_list, key, unused_) in flatten_errors(options, results):
if key is not None:
print 'The "%s" key in the section "%s" failed validation' % (
key, ', '.join(section_list))
else:
print 'The following section was missing:%s ' % ', '.join(
section_list)
sys.exit(1)
return options
def read_fas_conf():
'''Read username and password from /etc/fas.conf
This function will trhow an exception if it can't read the information.
otherwise it returns username, password
'''
cfgfile = open('/etc/fas.conf', 'r')
# Turn comment markers ";" into "#"
change_comment = lambda line: (line.startswith(';')
and line.replace(';', '#', 1) ) or line
lines = [change_comment(l) for l in cfgfile]
cfg = ConfigObj(lines)
return cfg['global']['login'], cfg['global']['password']
def members_of_group(group, use_fas=False):
if use_fas:
members = retry_fas(fas.group_members, group)
members = [m.username for m in members]
else:
group_info = grp.getgrnam(group)
members = group_info[3]
return set(members)
def iterate_ssh_keys(user):
person = retry_fas(fas.people_by_key, search=user, fields=['ssh_key'])[user]
if person.ssh_key:
for key in person.ssh_key.split('\n'):
if key:
yield key
def retry_fas(function, *args, **kwargs):
global retries
# Try the function at least once
while True:
try:
return function(*args, **kwargs)
except AuthError:
retries += 1
password = getpass('FAS Password for %s:' % function.im_self.username)
function.im_self.password = password
if retries >= MAX_RETRIES:
raise
if __name__ == '__main__':
args = parse_commandline(sys.argv)
conf = read_config_files(['/etc/fas-auth-keys.conf',
'~/.fedora/fas-auth-keys.conf'])
# Merge args and conf. Note that this requires that we've already parsed
# our args so that they're in a dict and won't overwrite pieces of conf if
# they weren't specified on the commandline
conf.merge(args)
# Merge in fas.conf
if conf['fas_conf']:
try:
username, password = read_fas_conf()
except:
# We don't care -- this is a nicety
pass
else:
conf['username'] = username
conf['password'] = password
fas = AccountSystem(username=conf['username'], password=conf['password'])
from_string = ''
if conf['limit_from']:
from_string = 'from="%s" ' % ','.join(conf['limit_from'])
for group in conf['groups']:
conf['users'].update(members_of_group(group, use_fas=conf['fas_groups']))
ssh_keys = dict()
for user in conf['users']:
ssh_keys[user] = set()
for key in iterate_ssh_keys(user):
ssh_keys[user].add(key)
for user in sorted(ssh_keys.keys()):
for key in ssh_keys[user]:
print '%s%s' % (from_string, key)