oof, get user creation working

pull/11/head
nathaniel smith 2017-02-15 23:25:39 -08:00
parent 2b46bef460
commit 55562572ae
3 changed files with 150 additions and 1 deletions

4
scripts/abc 100644
View File

@ -0,0 +1,4 @@
foobar
some stuff
yeahh
hm

View File

@ -0,0 +1,14 @@
#!/usr/bin/env python3
import sys
KEYFILE_PATH = '/home/{}/.ssh/authorized_keys2'
def main(argv):
username = argv[1]
with open(KEYFILE_PATH.format(username), 'w') as f:
f.write(sys.stdin.read())
if __name__ == '__main__':
exit(main(sys.argv))

View File

@ -1,7 +1,10 @@
import os
import re import re
from subprocess import run, CalledProcessError, PIPE
from tempfile import TemporaryFile
from django.db.models import Model from django.db.models import Model
from django.db.models.signals import pre_save from django.db.models.signals import pre_save, post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import TextField, BooleanField, CharField, ForeignKey from django.db.models import TextField, BooleanField, CharField, ForeignKey
@ -15,6 +18,23 @@ SSH_TYPE_CHOICES = (
('ssh-dss', 'ssh-dss',), ('ssh-dss', 'ssh-dss',),
) )
KEYFILE_HEADER = """Hi! This file is automatically managed by tilde.town. You
probably shouldn't change it. If you want to add more public keys that's
totally fine: you can put them in ~/.ssh/authorized_keys"""
TMP_PATH = '/tmp/ttadmin'
ENSURE_PRESENT = 'present'
ENSURE_ABSENT = 'absent'
def user_in_passwd(username):
"""Given a username, returns either the user's line in passwd or None.
Opens and reads passwd every time. Memoize or something if this becomes an
issue."""
with open('/etc/passwd') as passwd:
for line in passwd:
if username == line.split(':')[0]:
return line.rstrip()
class Townie(User): class Townie(User):
"""Both an almost normal Django User as well as an abstraction over a """Both an almost normal Django User as well as an abstraction over a
@ -44,6 +64,58 @@ class Townie(User):
self.username, self.username,
self.email)) self.email))
# managing concrete system state
def reconcile(self, ensure):
assert(self.reviewed)
dot_ssh_path = '/home/{}/.ssh'.format(self.username)
if ensure == ENSURE_ABSENT:
# TODO delete
# This is manual for now because it very rarely comes up and I want
# the present case to work.
pass
if ensure == ENSURE_PRESENT:
# TODO handle rename case either with update fields or a rename action
# Add the user
result = run(['sudo',
'adduser',
'--quiet',
'--shell={}'.format(self.shell),
'--gecos="{}"'.format(self.displayname),
'--disabled-password',
self.username,],
check=True,
)
# Create .ssh
run(['sudo', '--user={}'.format(self.username), 'mkdir', dot_ssh_path])
# Write out authorized_keys file
with TemporaryFile(dir="/tmp") as fp:
fp.write(self.generate_authorized_keys().encode('utf-8'))
fp.seek(0)
run(['sudo',
'--user={}'.format(self.username),
'/opt/bin/create_keyfile.py',
self.username],
stdin=fp
)
def generate_authorized_keys(self):
"""returns a string suitable for writing out to an authorized_keys
file"""
content = KEYFILE_HEADER
pubkeys = Pubkey.objects.filter(townie=self)
for key in pubkeys:
if key.startswith('ssh-'):
content += '\n {}'.format(key.key)
else:
content += '\n{} {}'.format(key.key_type, key.key)
return content
class Pubkey(Model): class Pubkey(Model):
key_type = CharField(max_length=50, key_type = CharField(max_length=50,
blank=False, blank=False,
@ -62,3 +134,62 @@ def on_townie_pre_save(sender, instance, **kwargs):
if not existing[0].reviewed and instance.reviewed == True: if not existing[0].reviewed and instance.reviewed == True:
instance.send_welcome_email() instance.send_welcome_email()
@receiver(post_save, sender=Townie)
def post_save_reconcile(sender, instance, **kwargs):
if not instance.reviewed:
return
instance.reconcile(ENSURE_PRESENT)
@receiver(post_delete, sender=Townie)
def post_delete_reconcile(sender, instance, **kwargs):
if not instance.reviewed:
# TODO should i actually do this check?
# I might want to make it such that users can never become un-reviewed.
return
instance.reconcile(ENSURE_ABSENT)
# what the puppet module does:
# * creates user account
# * creates home directory
# * creates authorized_keys2
# * adds user to group 'tilde' (why?)
# * sets shell
# * creates .ssh directory
# * creates .irssi directory
# * creates templatized .irssi config file (irssi isn't even default anymore...)
# * creates hardcoded .twurlrc file (why not skel?)
#
# some of this stuff is pointless and the actually required stuff is:
# * create user account (useradd)
# * create home dir (useradd)
# * create .ssh/authorized_keys2 (need functions for this
# * set shell (chsh)
# * create .twurlrc (just use /etc/skel)
# other things to consider:
# * what happens when a user wants their name changed?
# * it looks like usermod -l and a mv of the home dir can change a user's username.
# * would hook this into the pre_save signal to note a username change
# * what happens when a user is marked as not reviewed?
# * does this signal user deletion? Or does literal Townie deletion signal
# "needs to be removed from disk"? I think it makes the most sense for the
# latter to imply full user deletion.
# * I honestly can't even think of a reason to revert a user to "not reviewed"
# and perhaps it's best to just not make that possible. for now, though, I
# think I can ignore it.
# * what happens when a user needs to be banned?
# * the Townie should be deleted via post_delete signal
# * what are things about a user that might change in django and require changes on disk?
# * username
# * displayname (only if i start using this?)
# * ssh key
#
# how should this code be structured?
# * within the Townie model, hardcoded
# * outside the Townie model, procedurally
# * within an abstract class
#
# for now my gut says to implement stuff hardcoded in the Townie class but with
# an eye towards generalizing the pattern in some base class for other
# resources as needed.