summaryrefslogtreecommitdiff
path: root/tools/add-borguser.py
blob: 86e4cb733f81dee88623c836ceea9767b3e11052 (plain)
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
#!/usr/bin/env python3

import argparse
import base64
import binascii
import os
import pwd
import re
import subprocess


class UserAdder(object):
    def __init__(self, *args, **kwargs):
        # This doesn't *really* need to be a class, but I may do something with it in the future.
        _tmpargs = locals()
        del(_tmpargs['self'])
        for k, v in _tmpargs.items():
            setattr(self, k, v)
        self.users = {}

    def addUser(self, user, *args, **kwargs):
        # We ideally should do this purely pythonically, but libuser is external and not available everywhere...
        # We *could* do it by hand (add to /etc/passwd, etc.) but that's not guaranteed to be totally compatible.
        # Don't try to add a user if they exist. Doesn't support e.g. LDAP auth.
        try:
            u = pwd.getpwnam(user)
            homedir = u.pw_dir
        except KeyError:
            homedir = '/home/{0}'.format(user)
            subprocess.run(['useradd',
                            '-M',
                            '-c',
                            'Added by add-borguser.py',
                            '-d',
                            homedir,
                            user])
        sshdir = os.path.join(homedir, '.ssh')
        authkeys = os.path.join(sshdir, 'authorized_keys')
        userent = pwd.getpwnam(user)
        uid, gid = userent.pw_uid, userent.pw_gid
        os.makedirs(homedir, mode = 0o700, exist_ok = True)
        os.makedirs(sshdir, mode = 0o700, exist_ok = True)
        os.chown(homedir, uid, gid)
        os.chown(sshdir, uid, gid)
        if not os.path.isfile(authkeys):
            with open(authkeys, 'w') as f:
                f.write('')
        os.chmod(authkeys, 0o0400)
        os.chown(authkeys, uid, gid)
        self.users[user] = authkeys
        return()

    def addKey(self, ssh_key, *args, **kwargs):
        key_template = ('command='
                            #'"cd {homedir};'
                            #'borg serve --restrict-to-path {homedir}",'
                            '"/usr/local/bin/borg-restricted.py ${SSH_ORIGINAL_COMMAND}"',
                        'no-port-forwarding,'
                        'no-X11-forwarding,'
                        'no-pty,'
                        'no-agent-forwarding,'
                        'no-user-rc '
                        '{keystr}\n')
        for u, kp in self.users.items():
            userent = pwd.getpwnam(u)
            homedir = userent.pw_dir
            sshdir = os.path.join(homedir, '.ssh')
            key_insert = key_template.format(user = u,
                                             homedir = homedir,
                                             keystr = ssh_key)
            with open(kp, 'a') as f:
                f.write(key_insert)
            # When CentOS/RHEL move to python3 native, and port policycoreutils, do this natively.
            # But for now...
            subprocess.run(['chcon',
                            '-R unconfined_u:object_r:user_home_t:s0',
                            sshdir])
            subprocess.run(['semanage',
                            'fcontext',
                            '-a',
                                '-t',
                                    'ssh_home_t',
                            sshdir])
        return()

    def clean(self):
        self.users = {}
        return()

def parseArgs():
    def _valid_posix_user(username):
        # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_437
        # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_282
        # https://unix.stackexchange.com/a/435120/284004
        if not re.search('^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}\$)$', username):
            raise argparse.ArgumentTypeError('user must be a POSIX-compliant username')
        return(username)
    def _valid_ssh_key(keystr):
        # This validation is *super* cursory. We could probably do some better parsing at some point.
        key_components = keystr.split()
        keytype = re.sub('^ssh-(.*)', '\g<1>', key_components[0])
        # We don't support anything but ED25519 or RSA, given that they used the hardening guide.
        if keytype not in ('ed25519', 'rsa'):
            raise argparse.ArgumentTypeError('Not a valid SSH pubkey type (must be RSA or ED25519)')
        try:
            base64.b64decode(key_components[1].encode('utf-8'))
        except binascii.Error:
            raise argparse.ArgumentTypeError('Not a valid SSH pubkey')
        return(keystr)
    args = argparse.ArgumentParser(description = ('Add local users to a borg server'))
    args.add_argument('user',
                      type = _valid_posix_user,
                      help = 'The username/machine name to add')
    args.add_argument('ssh_key',
                      type = _valid_ssh_key,
                      help = ('The full SSH pubkey (remember to enclose in quotes)'))
    return(args)

def main():
    if not os.geteuid() == 0:
        raise PermissionError('This script must be run as root or with root-like privileges')
    args = vars(parseArgs().parse_args())
    um = UserAdder(**args)
    um.addUser(**args)
    um.addKey(**args)
    um.clean()
    return()

if __name__ == '__main__':
    main()