1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
#!/usr/bin/python -tt
#
# Copyright 2015 Red Hat, Inc.
# License: GPLv2+
# Author: Pierre-Yves Chibon <pingou@pingoured.fr>
# Author: Toshio Kuratomi <toshio@fedoraproject.org>
__version_info__ = ((0, 1),)
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 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']))
for user in sorted(set(conf['users'])):
print user
|