Ignore:
Timestamp:
Nov 11, 2011, 1:07:59 AM (13 years ago)
Author:
ezyang
Message:
Make credit-card safer and more robust.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • branches/fc15-dev/host/credit-card/host.py

    r1999 r2044  
    11import os
    22import optparse
    3 import logging
    43import socket
    54import tempfile
    65import shutil
    76import errno
     7import csv
    88
    99import shell
     
    1313# XXX test server and wizard server
    1414
    15 ROOT_UID = 0
    16 SIGNUP_UID = 102
    17 SQL_UID = 537704221
    18 FEDORA_DS_UID = 103 # XXX ACTUALLY CONFIGURE SERVERS TO USE THIS
    19 LOGVIEW_UID = 501 # XXX Autogenerated, I don't like this...
     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)
    2019
     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...
    2135COMMON_CREDS = [
    22     (ROOT_UID, 0o600, 'root/.bashrc'),
    23     (ROOT_UID, 0o600, 'root/.screenrc'),
    24     (ROOT_UID, 0o600, 'root/.ssh/authorized_keys'),
    25     (ROOT_UID, 0o600, 'root/.ssh/authorized_keys2'),
    26     (ROOT_UID, 0o600, 'root/.vimrc'),
    27     (ROOT_UID, 0o600, 'root/.k5login'),
     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'),
    2842    # punted /root/.ssh/known_hosts
    2943
    30     # XXX must be created in Kickstart
    31     (LOGVIEW_UID, 0o600, 'home/logview/.k5login'),
     44    # XXX user must be created in Kickstart
     45    ('logview', 0o600, 'home/logview/.k5login'),
    3246    ]
    3347
    3448COMMON_PROD_CREDS = [ # important: no leading slashes!
    35     (ROOT_UID, 0o600, 'root/.ldapvirc'),
    36     (ROOT_UID, 0o600, 'etc/ssh/ssh_host_dsa_key'),
    37     (ROOT_UID, 0o600, 'etc/ssh/ssh_host_key'),
    38     (ROOT_UID, 0o600, 'etc/ssh/ssh_host_rsa_key'),
    39     (ROOT_UID, 0o600, 'etc/pki/tls/private/scripts.key'),
    40     (ROOT_UID, 0o600, 'etc/whoisd-password'),
    41     (ROOT_UID, 0o600, 'etc/daemon.keytab'),
     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'),
     53    ('root', 0o600, 'etc/pki/tls/private/scripts.key'),
     54    ('root', 0o600, 'etc/whoisd-password'),
     55    ('root', 0o600, 'etc/daemon.keytab'),
    4256
    43     (ROOT_UID, 0o644, 'etc/ssh/ssh_host_dsa_key.pub'),
    44     (ROOT_UID, 0o644, 'etc/ssh/ssh_host_key.pub'),
    45     (ROOT_UID, 0o644, 'etc/ssh/ssh_host_rsa_key.pub'),
     57    ('root', 0o644, 'etc/ssh/ssh_host_dsa_key.pub'),
     58    ('root', 0o644, 'etc/ssh/ssh_host_key.pub'),
     59    ('root', 0o644, 'etc/ssh/ssh_host_rsa_key.pub'),
    4660
    47     (SQL_UID, 0o600, 'etc/sql-mit-edu.cfg.php'),
    48     (SIGNUP_UID, 0o600, 'etc/signup-ldap-pw'),
     61    ('sql', 0o600, 'etc/sql-mit-edu.cfg.php'),
     62    ('signup', 0o600, 'etc/signup-ldap-pw'),
    4963    ]
    5064
    5165MACHINE_PROD_CREDS = [
    5266    # XXX NEED TO CHECK THAT THESE ARE SENSIBLE
    53     (ROOT_UID, 0o600, 'etc/krb5.keytab'),
    54     (FEDORA_DS_UID, 0o600, 'etc/dirsrv/keytab')
     67    ('root', 0o600, 'etc/krb5.keytab'),
     68    ('fedora-ds', 0o600, 'etc/dirsrv/keytab')
    5569    ]
    5670
    57 def mkdir_p(path):
     71def mkdir_p(path): # it's like mkdir -p
    5872    try:
    5973        os.makedirs(path)
    60     except OSError as exc: # Python >2.5
    61         if exc.errno == errno.EEXIST:
     74    except OSError as e:
     75        if e.errno == errno.EEXIST:
    6276            pass
    6377        else: raise
    6478
     79# XXX This code is kind of dangerous, because we are directly using the
     80# kernel modules to manipulate possibly untrusted disk images.  This
     81# means that if an attacker can corrupt the disk, and exploit a problem
     82# in the kernel vfs driver, he can escalate a guest root exploit
     83# to a host root exploit.  Ultimately we should use libguestfs
     84# which makes this attack harder to pull off, but at the time of writing
     85# squeeze didn't package libguestfs.
     86#
     87# We try to minimize attack surface by explicitly specifying the
     88# expected filesystem type.
    6589class WithMount(object):
    6690    """Context for running code with an extra mountpoint."""
    6791    guest = None
     92    types = None # comma separated, like the mount argument -t
    6893    mount = None
    6994    dev = None
    70     def __init__(self, guest):
     95    def __init__(self, guest, types):
    7196        self.guest = guest
     97        self.types = types
    7298    def __enter__(self):
    7399        self.dev = "/dev/%s/%s-root" % (HOST, self.guest)
     
    81107            self.mount = tempfile.mkdtemp("-%s" % self.guest, 'vm-', '/mnt') # no trailing slash
    82108            try:
    83                 shell.call("mount", mapper, self.mount)
     109                shell.call("mount", "--types", self.types, mapper, self.mount)
    84110            except:
    85111                os.rmdir(self.mount)
     
    90116
    91117        return self.mount
    92     def __exit__(self, *args):
     118    def __exit__(self, _type, _value, _traceback):
    93119        shell.call("umount", self.mount)
    94120        os.rmdir(self.mount)
     
    96122
    97123def main():
    98     usage = """usage: %prog [ARGS]"""
     124    usage = """usage: %prog [push|pull|pull-common] GUEST"""
    99125
    100126    parser = optparse.OptionParser(usage)
    101     _, args = parser.parse_args()
     127    # ext3 will probably supported for a while yet and a pretty
     128    # reasonable thing to always try
     129    parser.add_option('-t', '--types', dest="types", default="ext4,ext3",
     130            help="filesystem type(s)")
     131    parser.add_option('--creds-dir', dest="creds_dir", default="/root/creds",
     132            help="directory to store/fetch credentials in")
     133    options, args = parser.parse_args()
    102134
    103     creds = "/root/creds" # XXX check exists, check owned by root
    104     # make an option
    105     if not os.path.isdir(creds):
    106         raise Exception("/root/creds does not exist")
     135    if not os.path.isdir(options.creds_dir):
     136        raise Exception("/root/creds does not exist") # XXX STRING
     137    # XXX check owned by root and appropriately chmodded
    107138
    108139    os.umask(0o077) # overly restrictive
    109140
    110     # XXX error handling
    111 
    112141    if len(args) != 2:
     142        parser.print_help()
    113143        raise Exception("Wrong number of arguments")
    114144
     
    116146    guest   = args[1]
    117147
    118     with WithMount(guest) as tmp_mount:
     148    with WithMount(guest, options.types) as tmp_mount:
     149        uid_lookup = lookup("%s/etc/passwd" % tmp_mount)
     150        gid_lookup = lookup("%s/etc/group" % tmp_mount)
    119151        def push_files(files, type):
    120             for (ugid, perms, f) in files:
    121                 # assumes directories exist
     152            for (usergroup, perms, f) in files:
    122153                dest = "%s/%s" % (tmp_mount, f)
     154                mkdir_p(os.path.dirname(dest)) # useful for .ssh
    123155                # assuming OK to overwrite
    124                 shutil.copyfile("%s/%s/%s" % (creds, type, f), dest)
    125                 os.chown(dest, ugid, ugid)
    126                 os.chmod(dest, perms)
     156                # XXX we could compare the files before doing anything...
     157                shutil.copyfile("%s/%s/%s" % (options.creds_dir, type, f), dest)
     158                try:
     159                    os.chown(dest, uid_lookup[usergroup], gid_lookup[usergroup])
     160                    os.chmod(dest, perms)
     161                except:
     162                    # never ever leave un-chowned files lying around
     163                    os.unlink(dest)
     164                    raise
    127165        def pull_files(files, type):
    128166            for (_, _, f) in files:
    129                 dest = "%s/%s/%s" % (creds, type, f)
     167                dest = "%s/%s/%s" % (options.creds_dir, type, f)
    130168                mkdir_p(os.path.dirname(dest))
    131169                # error if doesn't exist
    132170                shutil.copyfile("%s/%s" % (tmp_mount, f), dest)
    133171
    134         # push case
    135172        if command == "push":
    136173            push_files(COMMON_CREDS, 'common')
     
    138175            push_files(MACHINE_PROD_CREDS, 'machine/%s' % guest)
    139176        elif command == "pull":
    140             # check if /root/creds exists
    141177            pull_files(MACHINE_PROD_CREDS, 'machine/%s' % guest)
    142178        elif command == "pull-common":
Note: See TracChangeset for help on using the changeset viewer.