source: trunk/host/credit-card/host.py @ 2269

Last change on this file since 2269 was 2269, checked in by ezyang, 12 years ago
Split sql-mit-edu.cfg.php into sql-password for platform agnosticism.
File size: 6.6 KB
RevLine 
[1999]1import os
2import optparse
3import socket
4import tempfile
5import shutil
6import errno
[2044]7import csv
[1999]8
9import shell
10
11HOST = socket.gethostname()
12
13# XXX test server and wizard server
14
[2044]15# UIDs (sketchy):
16#   signup 102
17#   fedora-ds 103 (sketchy, not true for b-b)
18#   logview 501 (really sketchy, since it's in the dynamic range)
[1999]19
[2044]20# Works for passwd and group, but be careful! They're different things!
21def lookup(filename):
22    # Super-safe to assume and volume IDs (expensive to check)
23    r = {
24        'root': 0,
25        'sql': 537704221,
26    }
27    with open(filename, 'rb') as f:
28        reader = csv.reader(f, delimiter=':', quoting=csv.QUOTE_NONE)
29        for row in reader:
30            r[row[0]] = int(row[2])
31    return r
32
33# Format here assumes that we always chmod $USER:$USER ...
34# but note the latter refers to group...
[1999]35COMMON_CREDS = [
[2044]36    ('root', 0o600, 'root/.bashrc'),
37    ('root', 0o600, 'root/.screenrc'),
38    ('root', 0o600, 'root/.ssh/authorized_keys'),
39    ('root', 0o600, 'root/.ssh/authorized_keys2'),
40    ('root', 0o600, 'root/.vimrc'),
41    ('root', 0o600, 'root/.k5login'),
[1999]42    # punted /root/.ssh/known_hosts
43
[2044]44    # XXX user must be created in Kickstart
45    ('logview', 0o600, 'home/logview/.k5login'),
[1999]46    ]
47
48COMMON_PROD_CREDS = [ # important: no leading slashes!
[2044]49    ('root', 0o600, 'root/.ldapvirc'),
50    ('root', 0o600, 'etc/ssh/ssh_host_dsa_key'),
51    ('root', 0o600, 'etc/ssh/ssh_host_key'),
52    ('root', 0o600, 'etc/ssh/ssh_host_rsa_key'),
[2045]53    ('root', 0o600, 'etc/pki/tls/private/scripts-1024.key'),
[2044]54    ('root', 0o600, 'etc/pki/tls/private/scripts.key'),
55    ('root', 0o600, 'etc/whoisd-password'),
[2049]56    ('afsagent', 0o600, 'etc/daemon.keytab'),
[1999]57
[2044]58    ('root', 0o644, 'etc/ssh/ssh_host_dsa_key.pub'),
59    ('root', 0o644, 'etc/ssh/ssh_host_key.pub'),
60    ('root', 0o644, 'etc/ssh/ssh_host_rsa_key.pub'),
[1999]61
[2269]62    ('sql', 0o600, 'etc/sql-mit-edu.cfg.php'), # technically doesn't have to be secret anymore
63    ('sql', 0o600, 'etc/sql-password'),
[2044]64    ('signup', 0o600, 'etc/signup-ldap-pw'),
[1999]65    ]
66
67MACHINE_PROD_CREDS = [
68    # XXX NEED TO CHECK THAT THESE ARE SENSIBLE
[2044]69    ('root', 0o600, 'etc/krb5.keytab'),
70    ('fedora-ds', 0o600, 'etc/dirsrv/keytab')
[1999]71    ]
72
[2246]73def drop_caches():
74    with open("/proc/sys/vm/drop_caches", 'w') as f:
75        f.write("1")
76
[2044]77def mkdir_p(path): # it's like mkdir -p
[1999]78    try:
79        os.makedirs(path)
[2044]80    except OSError as e:
81        if e.errno == errno.EEXIST:
[1999]82            pass
83        else: raise
84
[2044]85# XXX This code is kind of dangerous, because we are directly using the
86# kernel modules to manipulate possibly untrusted disk images.  This
87# means that if an attacker can corrupt the disk, and exploit a problem
88# in the kernel vfs driver, he can escalate a guest root exploit
89# to a host root exploit.  Ultimately we should use libguestfs
90# which makes this attack harder to pull off, but at the time of writing
91# squeeze didn't package libguestfs.
92#
93# We try to minimize attack surface by explicitly specifying the
94# expected filesystem type.
[1999]95class WithMount(object):
96    """Context for running code with an extra mountpoint."""
97    guest = None
[2044]98    types = None # comma separated, like the mount argument -t
[1999]99    mount = None
100    dev = None
[2044]101    def __init__(self, guest, types):
[1999]102        self.guest = guest
[2044]103        self.types = types
[1999]104    def __enter__(self):
[2246]105        drop_caches()
[1999]106        self.dev = "/dev/%s/%s-root" % (HOST, self.guest)
107
108        mapper_name = shell.eval("kpartx", "-l", self.dev).split()[0]
109        shell.call("kpartx", "-a", self.dev)
110        mapper = "/dev/mapper/%s" % mapper_name
111
112        # this is why bracketing functions and hanging lambdas are a good idea
113        try:
114            self.mount = tempfile.mkdtemp("-%s" % self.guest, 'vm-', '/mnt') # no trailing slash
115            try:
[2044]116                shell.call("mount", "--types", self.types, mapper, self.mount)
[1999]117            except:
118                os.rmdir(self.mount)
119                raise
120        except:
121            shell.call("kpartx", "-d", self.dev)
122            raise
123
124        return self.mount
[2044]125    def __exit__(self, _type, _value, _traceback):
[1999]126        shell.call("umount", self.mount)
127        os.rmdir(self.mount)
128        shell.call("kpartx", "-d", self.dev)
[2246]129        drop_caches()
[1999]130
131def main():
[2044]132    usage = """usage: %prog [push|pull|pull-common] GUEST"""
[1999]133
134    parser = optparse.OptionParser(usage)
[2044]135    # ext3 will probably supported for a while yet and a pretty
136    # reasonable thing to always try
137    parser.add_option('-t', '--types', dest="types", default="ext4,ext3",
138            help="filesystem type(s)")
139    parser.add_option('--creds-dir', dest="creds_dir", default="/root/creds",
140            help="directory to store/fetch credentials in")
141    options, args = parser.parse_args()
[1999]142
[2044]143    if not os.path.isdir(options.creds_dir):
144        raise Exception("/root/creds does not exist") # XXX STRING
145    # XXX check owned by root and appropriately chmodded
[1999]146
147    os.umask(0o077) # overly restrictive
148
149    if len(args) != 2:
[2044]150        parser.print_help()
[1999]151        raise Exception("Wrong number of arguments")
152
153    command = args[0]
154    guest   = args[1]
155
[2044]156    with WithMount(guest, options.types) as tmp_mount:
157        uid_lookup = lookup("%s/etc/passwd" % tmp_mount)
158        gid_lookup = lookup("%s/etc/group" % tmp_mount)
[1999]159        def push_files(files, type):
[2044]160            for (usergroup, perms, f) in files:
[1999]161                dest = "%s/%s" % (tmp_mount, f)
[2044]162                mkdir_p(os.path.dirname(dest)) # useful for .ssh
[1999]163                # assuming OK to overwrite
[2044]164                # XXX we could compare the files before doing anything...
165                shutil.copyfile("%s/%s/%s" % (options.creds_dir, type, f), dest)
166                try:
167                    os.chown(dest, uid_lookup[usergroup], gid_lookup[usergroup])
168                    os.chmod(dest, perms)
169                except:
170                    # never ever leave un-chowned files lying around
171                    os.unlink(dest)
172                    raise
[1999]173        def pull_files(files, type):
174            for (_, _, f) in files:
[2044]175                dest = "%s/%s/%s" % (options.creds_dir, type, f)
[1999]176                mkdir_p(os.path.dirname(dest))
177                # error if doesn't exist
178                shutil.copyfile("%s/%s" % (tmp_mount, f), dest)
179
180        if command == "push":
181            push_files(COMMON_CREDS, 'common')
182            push_files(COMMON_PROD_CREDS,  'common')
183            push_files(MACHINE_PROD_CREDS, 'machine/%s' % guest)
184        elif command == "pull":
185            pull_files(MACHINE_PROD_CREDS, 'machine/%s' % guest)
186        elif command == "pull-common":
187            pull_files(COMMON_CREDS, 'common')
188            pull_files(COMMON_PROD_CREDS,  'common')
189
190if __name__ == "__main__":
191    main()
Note: See TracBrowser for help on using the repository browser.