This repository has been archived by the owner on Nov 9, 2017. It is now read-only.
/
create.py
executable file
·212 lines (166 loc) · 7.54 KB
/
create.py
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/usr/bin/env python
"""
"Let there be light." -- Genesis 1:3
User creation tool.
"""
# Dependencies:
# python2.7 (Or python2.6 + python-argparse)
# python-ldap
# pycrypto
from __future__ import with_statement, print_function
import os
import os.path
import sys
sys.path.insert(0, os.path.join(os.path.split(__file__)[0], "lib", "python2.6", "site-packages"))
import argparse
from datetime import datetime
import errno
from getpass import getpass, getuser
from grp import getgrnam
from pwd import getpwnam
import shutil
from subprocess import check_call
import fcntl
from filter_accounts import filter_accounts
from finalize_accounts import finalize_accounts
from utils import get_users, fancy_open, write_users, kinit, irc_alert
import ldap
import ldap.sasl
def _associate_calnet(account_name):
pass
def _check_account_name(account_name):
# do getent passwd account_name
pass
def _finish_account_creation(src):
now = datetime.now().strftime("%Y-%m-%d")
directory, name = os.path.split(src)
dest = os.path.join(directory, "..", "oldapprovedusers", "{0}.{1}".format(name, now))
shutil.move(src, dest)
# All this chown / chmod'ing necessary? Aren't we guaranteed to be running as root?
os.chown(dest, getpwnam("root").pw_uid, getgrnam("root").gr_gid)
os.chmod(dest, 0600)
# Create the new approved.users file for future use.
with open(src, "a"):
pass
os.chown(src, getpwnam("root").pw_uid, getgrnam("root").gr_gid)
os.chmod(src, 0600)
def _create_parser():
parser = argparse.ArgumentParser(description = "Process and create user accounts.")
parser.add_argument("-u", "--usersfile", dest = "users_file",
default = "/opt/create/public/approved.users",
help = "Input file of approved users")
parser.add_argument("-m", "--midapprove", dest = "mid_approve",
default = "/opt/create/private/mid_approved.users",
help = "Input file of users in mid stage of approval")
parser.add_argument("-a", "--admin-user", dest = "admin_user",
default = os.environ.get("SUDO_USER", getuser()),
help = "User to autheticate through kerberos with")
parser.add_argument("-i", "--interactive", dest = "interactive",
action = "store_true",
help = "Ask stdin when staff approval is required")
parser.add_argument("-b", "--backup", dest = "backup",
default = "/opt/create/private/backup/",
help = "Directory to backup approved.user and mid_approved.users to")
parser.add_argument("-n", "--no-email", dest = "email",
action = "store_false",
help = "Don't send account creation / rejection emails")
parser.add_argument("-v", "--verbose", dest = "verbose",
action = "store_true",
help = "Print information on script actions")
parser.add_argument("-l", "--logfile", dest = "log_file",
default = "/opt/create/public/approved.log",
help = "Input file of approved log")
parser.add_argument("-p", "--priv-key", dest = "rsa_priv_key",
default = "/opt/create/private/private_pass.pem",
help = "Private key to decrypt user passwords")
parser.add_argument("-c", "--calnetldap", dest = "calnet_ldap_url",
default = "ldap://nds.berkeley.edu",
help = "Url of CalNet's LDAP")
parser.add_argument("-o", "--ocfldap", dest = "ocf_ldap_url",
default = "ldaps://ldap.ocf.berkeley.edu",
help = "Url of OCF's LDAP")
parser.add_argument("-d", "--uidlowerbound", dest = "conflict_uid_lower_bound",
default = 16000,
help = "Lower bound for OCF name collision detection")
parser.add_argument("-k", "--keytab", dest = "keytab",
help = "Keytab file to use for kinit")
return parser
def get_lock(path='/srv/atool/.create.lock'):
"""Obtain an exclusive lock on the given path."""
try:
lock_file = open(path, 'a')
fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
return lock_file
except IOError:
raise RuntimeError(
"Unable to lock {}; are you running multiple of me?".format(path))
def backup(options):
files = [options.mid_approve, options.users_file]
for s_path in files:
if os.path.isfile(s_path):
d_path = os.path.join(options.backup, os.path.basename(s_path))
with open(d_path, "a") as dest, open(s_path) as src:
now = datetime.now()
dest.write("# Backup of {0} as it appeared on {1}\n".format(s_path, now))
shutil.copyfileobj(src, dest)
def filter_stage(options):
"""Filter accounts in the first stage into mid-approval."""
# Process all of the recently requested accounts
with fancy_open(options.users_file, lock = True,
pass_missing = True) as f:
needs_approval = filter_accounts(get_users(f, options), options)
for user, comment in needs_approval:
msg = "`{}` ({}) needs approval: {}".format(user['account_name'], user['owner'], comment)
write_and_alert('/srv/atool/pending', msg, all=True)
# Write the users needing staff approval back to the users file
with fancy_open(options.users_file, "w", lock = True) as f:
write_users(f, [user for user, comment in needs_approval])
def write_and_alert(path, line, all=False):
"""Append to the given file, alerting to IRC. Only appends/alerts if the
line has not already been sent."""
# is it already in the file?
if os.path.isfile(path):
with open(path) as f:
if line in (l.strip() for l in f.readlines()):
return
with open(path, "a") as f:
f.write(line + "\n")
irc_alert(line, all=all)
def create_stage(options):
"""Create accounts in the mid-approval stage."""
try:
principal = options.admin_user + "/admin"
if getattr(options, "keytab", None) is None:
# Autheticate our ldap session using gssapi
options.admin_password = \
getpass("{0}@OCF.BERKELEY.EDU's Password: ".format(principal))
kinit(principal, options.admin_password)
else:
kinit(principal, None, keytab = options.keytab)
options.ocf_ldap.sasl_interactive_bind_s("", ldap.sasl.gssapi(""))
with fancy_open(options.mid_approve, lock = True,
pass_missing = True, delete = True) as f:
finalize_accounts(get_users(f, options), options)
finally:
check_call(["kdestroy"])
def main(args):
"""
Process a file contain a list of user accounts to create.
"""
options = _create_parser().parse_args(args = args)
user = getuser()
if user != 'atool':
raise RuntimeError("Not running as correct user: " + user + " (run as atool)")
lock = get_lock()
options.calnet_ldap = ldap.initialize(options.calnet_ldap_url)
options.calnet_ldap.simple_bind_s("", "")
options.calnet_ldap.protocol_version = ldap.VERSION3
options.ocf_ldap = ldap.initialize(options.ocf_ldap_url)
options.ocf_ldap.simple_bind_s("", "")
options.ocf_ldap.protocol_version = ldap.VERSION3
if options.backup:
backup(options)
filter_stage(options)
create_stage(options)
if __name__ == "__main__":
main(sys.argv[1:])