1. Alexandre Macabies
  2. sadm

Commits

Alexandre Macabies  committed cca65d5 Merge

Merged prologin/sadm into master

  • Participants
  • Parent commits a1c95b4, 7aefd6f
  • Branches master

Comments (0)

Files changed (22)

File config/hfs-client.yml

View file
  • Ignore whitespace
-url_pattern: "http://{}/"
+url_pattern: "http://{}:20100/"
 host_pattern: "hfs{}"
 shared_secret: "DEFAULT_PASSWORD_HFS_CLIENT"

File config/netboot.yml

View file
  • Ignore whitespace
 static_path: /srv/tftp
-options: init=/bin/sh
+options: root=/dev/nfs ip=:::::eth0:dhcp nosplash

File dns/mdbdns.py

View file
  • Ignore whitespace
 
 
 def build_machines_revdns_zone(machines, mtype, ip):
-    machines = [m for m in machines if m['mtype'] == mtype]
-    if mtype == 'user':  # hack: orga machines are like user machines
-        machines.extend(m for m in machines if m['mtype'] == 'orga')
+    machines = [m for m in machines if m['mtype'] in mtype]
 
     records = [
         (m['ip'].split('.')[-1], 'IN', 'PTR', '%s.prolo.' % m['hostname'])
 def build_normal_prolo_zone(machines):
     records = []
     for m in machines:
-        names = [m['hostname']] + [s.strip() for s in m['aliases'].split(',')]
+        names = [m['hostname']] + [s.strip() for s in m['aliases'].split(',')
+                                             if s.strip()]
         for n in names:
             records.append((n, 'IN', 'A', m['ip']))
     build_zone('prolo_normal', records)
 
     logging.warning("MDB update received, generating zones")
     build_alien_revdns_zone()
-    build_machines_revdns_zone(machines, 'user', '0.168.192')
-    build_machines_revdns_zone(machines, 'service', '1.168.192')
-    build_machines_revdns_zone(machines, 'cluster', '2.168.192')
+    build_machines_revdns_zone(machines, {'user', 'orga'}, '0.168.192')
+    build_machines_revdns_zone(machines, {'service'}, '1.168.192')
+    build_machines_revdns_zone(machines, {'cluster'}, '2.168.192')
     build_alien_prolo_zone()
     build_normal_prolo_zone(machines)
 

File hfs/hfs.py

View file
  • Ignore whitespace
 import urllib.parse
 import urllib.request
 
+from socketserver import ThreadingMixIn
+
 CFG = prologin.config.load('hfs-server')
-CLT_CFG = prologin.config.loat('hfs-client')
+CLT_CFG = prologin.config.load('hfs-client')
 
 if 'shared_secret' not in CLT_CFG:
     raise RuntimeError('Missing shared_secret in the hfs-client YAML config')
     def get_argument(self, name):
         """Returns the value of the GET argument called <name>. If it does not
         exist, send an error page."""
-        qs = self.path.split('?', 1)
-        if len(qs) != 2:
+        qs = self.post_data.decode('utf-8')
+        args = urllib.parse.parse_qs(qs)
+        data = json.loads(args['data'][0])
+        if name not in data:
             raise ArgumentMissing(name)
-        else:
-            qs = qs[1]
-            args = urllib.parse.parse_qs(qs)
-            if name not in args:
-                raise ArgumentMissing(name)
-            return args[name][0]
+        return data[name]
 
-    def do_GET(self):
+    def do_POST(self):
+        rfile_len = int(self.headers['content-length'])
+        self.post_data = self.rfile.read(rfile_len)
         try:
             if self.path.startswith('/get_hfs'):
                 self.get_hfs()
                 self.send_error(404)
         except ArgumentMissing as e:
             self.send_error(400, message=str(e))
+        except Exception:
+            logging.exception('Something wrong happened')
 
     def migrate_user(self):
         # If we have a nbd-server for that user, kill it.
         if self.user in RUNNING_NBD:
             # To make sure we don't have two machines writing on the same NBD
             # (would be very, very troublesome).
-            os.kill(RUNNING_NBD[self.user]['pid'], signal.SIGKILL)
+            try:
+                os.kill(RUNNING_NBD[self.user]['pid'], signal.SIGKILL)
+            except Exception:
+                pass
             del RUNNING_NBD[self.user]
 
     def get_nbd_size(self):
         # TODO(delroth): replace the wget by something using urllib
         self.check_available_space()
 
-        url = ('http', 'hfs%d:%d' % (peer_id, CFG['port']),
-               '/migrate_user', 'user=%s&hfs=%d' % (self.user, peer_id), '')
+        url = ('http', 'hfs%d:%d' % (peer_id, CFG['port']), '/migrate_user',
+               '', '')
         url = urllib.parse.urlunsplit(url)
+        data = { 'user': self.user, 'hfs': peer_id }
+        data = 'data=%s' % (urllib.parse.quote(json.dumps(data)))
+        data = data.encode('utf-8')
 
         with open(self.nbd_filename(), 'wb') as fp:
-            with urllib.request.urlopen(url) as rfp:
+            with urllib.request.urlopen(url, data=data) as rfp:
                 with gzip.GzipFile(fileobj=rfp, mode='rb') as zfp:
                     while True:
                         block = zfp.read(65536)
                             break
                         fp.write(block)
 
+class ThHTTPServer(http.server.HTTPServer, ThreadingMixIn):
+    pass
+
 if __name__ == '__main__':
     prologin.log.setup_logging('hfs')
     if len(sys.argv) != 2:
         print('usage: %s <iface>' % sys.argv[0])
         sys.exit(1)
-    server = http.server.HTTPServer((get_iface_ip(sys.argv[1]), CFG['port']),
-                                    HFSRequestHandler)
+    server = ThHTTPServer((get_iface_ip(sys.argv[1]), CFG['port']),
+                          HFSRequestHandler)
     server.serve_forever()

File install.py

View file
  • Ignore whitespace
     install_cfg_profile('presencesync-pub', group='presencesync')
     install_cfg_profile('presencesync-sub', group='presencesync_public')
     install_cfg_profile('presenced-client', group='presenced')
+    install_cfg_profile('hfs-client', group='hfs_public')
 
 
 def install_nginxcfg():
     mkdir('/etc/dhcpd', mode=0o770, owner='root:mdbdhcp')
 
 
+def install_sshdcfg():
+    install_cfg('ssh/sshd_config', '/etc/ssh', owner='root:root', mode=0o644)
+
+
 def install_mdb():
     requires('libprologin')
     requires('nginxcfg')
 
     install_service_dir('hfs', owner='hfs:hfs', mode=0o700)
     install_systemd_unit('hfs@')
+    install_cfg_profile('hfs-server', group='hfs')
 
 
 def install_minecraft():
     'bindcfg',
     'nginxcfg',
     'dhcpdcfg',
+    'sshdcfg',
     'mdb',
     'mdbsync',
     'mdbdns',

File netboot/netboot.py

View file
  • Ignore whitespace
 """
 
 BOOT_SCRIPT = """#!ipxe
-echo Booting the kernel on rfs-%(rfs)d:/nfsroot
-initrd http://netboot/static/initrd
-boot http://netboot/static/kernel rfs=%(rfs)d %(options)s
+echo Booting the kernel on %(rfs_ip)s:/nfsroot
+initrd http://netboot/static/initrd%(suffix)s
+boot http://netboot/static/kernel%(suffix)s nfsroot=%(rfs_ip)s:/export/nfsroot %(options)s
 """
 
 REGISTER_ERROR_SCRIPT = """#!ipxe
         if len(machine) != 1:
             self.finish(BOOT_UNKNOWN_SCRIPT)
         machine = machine[0]
-        script = BOOT_SCRIPT % { 'rfs': machine['rfs'],
+        rfs_hostname = 'rfs%d' % machine['rfs']
+        rfs = prologin.mdb.connect().query(aliases__contains=rfs_hostname)
+        rfs_ip = rfs[0]['ip']
+        if machine['room'] == 'masters':
+            suffix = '-masters'
+        else:
+            suffix = ''
+        script = BOOT_SCRIPT % { 'rfs_ip': rfs_ip, 'suffix': suffix,
                                  'options': CFG['options'] }
         self.finish(script)
 

File presenced/pam_presenced.py

View file
  • Ignore whitespace
 import os
 import os.path
 import prologin.hfs
+import prologin.log
 import prologin.presenced
 import socket
 import subprocess
 import sys
+import time
+
+
+sys.stderr = open('/tmp/pam_log', 'w')
+prologin.log.setup_logging('pam_presenced')
 
 
 def get_home_dir(login):
     return '/home/{}'.format(login)
 
 def get_block_device(login):
-    return '/dev/ndb{}'.format(login)
+    return '/dev/nbd0'
 
 def fail(reason):
     # TODO: be sure the user can see the reason.
 
     # Prologin users must use a display manager (not a TTY, nor screen).
     if PAM_SERVICE not in ('gdm', 'kdm', 'slim', 'xdm'):
-        fail('Please log in the graphical display manager')
+        print('Please log in the graphical display manager')
 
     # Request the login to Presencd and PresenceSync.
     failure_reason = prologin.presenced.connect().request_login(login)
-    if failure_reason:
+    if failure_reason is not None:
         # Login is forbidden by presenced.
         fail('Login forbidden: {}'.format(failure_reason))
 
     # Request HOME directory migration and wait for it.
     hfs = prologin.hfs.connect()
     try:
-        host, port = hfs.get_hfs(login, socket.gethostname())
+        hostname = '.'.join(socket.gethostname().split('.')[:-1])
+        host, port = hfs.get_hfs(login, hostname)
     except RuntimeError as e:
         fail(str(e))
 
         os.mkdir(home_dir)
 
     # Get a block device for the HOME mount point and mount it.
-    if subprocess.check_call(['nbd-client', '{}:{}', block_device]):
+    if subprocess.check_call(['/usr/sbin/nbd-client', host, str(port),
+                              block_device]):
         fail('Cannot get the home directory block device')
-    if subprocess.check_call(['mount', block_device, home_dir]):
+    if subprocess.check_call(['/bin/mount', block_device, home_dir]):
         fail('Cannot mount the home directory')
 
     sys.exit(0)
 
 elif PAM_TYPE == 'close_session':
     if is_prologin_user:
-        subprocess.check_call(['umount', get_home_dir(login)])
-        subprocess.check_call(['ndb-client', '-d', get_block_device(login)])
+        subprocess.check_call(['/usr/bin/pkill', '-9', '-u', login])
+        time.sleep(2)
+        subprocess.check_call(['/bin/umount', get_home_dir(login)])
+        subprocess.check_call(['/usr/sbin/nbd-client', '-d', get_block_device(login)])
     sys.exit(0)
 
 else:

File presenced/presenced.py

View file
  • Ignore whitespace
 
 CLT_CFG = prologin.config.load('presenced-client')
 
-HOSTNAME = socket.gethostname()
+HOSTNAME = '.'.join(socket.gethostname().split('.')[:-1])  # Remove .prolo
 
 if 'shared_secret' not in CLT_CFG:
     raise RuntimeError(
         result = self.application.presencesync.request_login(
             login, HOSTNAME
         )
-        if result:
+        if result is not None:
             self.set_status(423, 'Login refused')
             self.write(result)
         else:

File python-lib/prologin/mdb.py

View file
  • Ignore whitespace
         are:
           hostname: the machine name and any of its aliases
           ip: the machine IP address
+          aliases: the machine aliases
           mac: the machine MAC address
           rfs: nearest root file server
           hfs: nearest home file server
           mtype: machine type, either user/orga/cluster/service
           room: physical room location, either pasteur/masters/cluster/other
         """
-        fields = { 'hostname', 'ip', 'mac', 'rfs', 'hfs', 'mtype', 'room' }
+        fields = { 'hostname', 'ip', 'aliases', 'mac', 'rfs', 'hfs', 'mtype',
+                   'room' }
         for q in kwargs:
             base = q.split('_')[0]
             if base not in fields:

File python-lib/prologin/presenced.py

View file
  • Ignore whitespace
             '/login', self.secret,
             {'login': login}
         )
+        logging.debug('Request login status code: {}'.format(r.status_code))
         if r.status_code != 200:
-            return r.text
+            return r.text or 'No reason given'
         else:
             return None
 

File python-lib/prologin/presencesync.py

View file
  • Ignore whitespace
             '/login', self.pub_secret,
             {'login': login, 'hostname': hostname}
         )
+        logging.debug(
+            'Request login:'
+            ' PresenceSync status code is {}'.format(r.status_code)
+        )
         if r.status_code != 200:
-            return r.text
+            return r.text or 'No reason given'
         else:
             return None
 

File python-lib/prologin/timeauth.py

View file
  • Ignore whitespace
 
 
 # Validity time (in seconds) of a generated token.
-TOKEN_TIMEOUT = 10
+TOKEN_TIMEOUT = 120
 
 
 def generate_token(secret, message=None):

File python-lib/prologin/tornadauth.py

View file
  • Ignore whitespace
             ):
                 logging.error('INVALID TOKEN!')
                 self.set_status(403, 'Invalid token')
+                self.write('Invalid token')
             else:
                 return func(self, self.get_argument('data'))
 

File rfs/init.sh

View file
  • Ignore whitespace
   exit 1
 fi
 
-pacman -Sy devtools nfs-utils sshd
-mkdir -p "$ROOTFS"
+pacman -Sy devtools nfs-utils openssh
 for svc in {sshd,nfsd,rpc-{idmapd,gssd,mountd,statd}}.service; do
   systemctl enable "$svc"
   systemctl start  "$svc"
 done
 echo "$ROOTFS $SUBNET(ro,no_root_squash,subtree_check,async)" > /etc/exports.d/rootfs.exports
-exportfs -arv
 mkarchroot "$ROOTFS" base $PACKAGES
+exportfs -arv
 cp -rv initcpio $ROOTFS/lib/
-mkarchroot -r "rfs.sh" "$ROOTFS"
+cp rfs.sh "$ROOTFS/"
+mkarchroot -r "/rfs.sh" "$ROOTFS"
+rm -f "$ROOTFS/rfs.sh"

File rfs/packages_lists

View file
  • Ignore whitespace
 openssh
 strace
 tcpdump
+git
+dhcpcd
+devtools
+grub-bios
+mercurial
+parted
+ntp

File rfs/rfs.sh

View file
  • Ignore whitespace
 locale-gen
 ssh-keygen -A
 mkinitcpio -p linux
-for svc in sshd.service; do
+for svc in {sshd,ntpd}.service; do
   systemctl enable "$svc"
 done
 

File ssh/sshd_config

View file
  • Ignore whitespace
+#	$OpenBSD: sshd_config,v 1.89 2013/02/06 00:20:42 dtucker Exp $
+
+# This is the sshd server system-wide configuration file.  See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented.  Uncommented options override the
+# default value.
+
+#Port 22
+#AddressFamily any
+#ListenAddress 0.0.0.0
+#ListenAddress ::
+
+# The default requires explicit activation of protocol 1
+#Protocol 2
+
+# HostKey for protocol version 1
+#HostKey /etc/ssh/ssh_host_key
+# HostKeys for protocol version 2
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_dsa_key
+#HostKey /etc/ssh/ssh_host_ecdsa_key
+
+# Lifetime and size of ephemeral version 1 server key
+#KeyRegenerationInterval 1h
+#ServerKeyBits 1024
+
+# Logging
+# obsoletes QuietMode and FascistLogging
+#SyslogFacility AUTH
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+PermitRootLogin yes
+#StrictModes yes
+#MaxAuthTries 6
+#MaxSessions 10
+
+#RSAAuthentication yes
+#PubkeyAuthentication yes
+
+# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
+# but this is overridden so installations will only check .ssh/authorized_keys
+AuthorizedKeysFile	.ssh/authorized_keys
+
+#AuthorizedPrincipalsFile none
+
+#AuthorizedKeysCommand none
+#AuthorizedKeysCommandUser nobody
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+#RhostsRSAAuthentication no
+# similar for protocol version 2
+#HostbasedAuthentication no
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# RhostsRSAAuthentication and HostbasedAuthentication
+#IgnoreUserKnownHosts no
+# Don't read the user's ~/.rhosts and ~/.shosts files
+#IgnoreRhosts yes
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication no
+#PermitEmptyPasswords no
+
+# Change to no to disable s/key passwords
+ChallengeResponseAuthentication no
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+# Set this to 'yes' to enable PAM authentication, account processing, 
+# and session processing. If this is enabled, PAM authentication will 
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication.  Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+UsePAM yes
+
+#AllowAgentForwarding yes
+#AllowTcpForwarding yes
+#GatewayPorts no
+#X11Forwarding no
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+PrintMotd no # pam does that
+#PrintLastLog yes
+#TCPKeepAlive yes
+#UseLogin no
+UsePrivilegeSeparation sandbox		# Default for new installations.
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS yes
+#PidFile /run/sshd.pid
+#MaxStartups 10:30:100
+#PermitTunnel no
+#ChrootDirectory none
+#VersionAddendum none
+
+# no default banner path
+#Banner none
+
+AllowUsers root
+
+# override default of no subsystems
+Subsystem	sftp	/usr/lib/ssh/sftp-server
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+#	X11Forwarding no
+#	AllowTcpForwarding no
+#	ForceCommand cvs server

File systemd/hfs@.service

View file
  • Ignore whitespace
 
 [Service]
 Type=simple
-User=hfs
+User=root
 WorkingDirectory=/var/prologin/hfs
 ExecStart=/var/prologin/venv/bin/python hfs.py %i
 

File systemd/rootssh-copy.service

View file
  • Ignore whitespace
+[Unit]
+Description=Copy ~root/.ssh/authorized_keys to NFS root
+
+[Service]
+Type=oneshot
+ExecStart=/bin/mkdir -p /export/nfsroot/root/.ssh
+ExecStart=/bin/cp /root/.ssh/authorized_keys /export/nfsroot/root/.ssh
+ExecStart=/bin/chmod -R go-rwx /export/nfsroot/root/.ssh

File systemd/rootssh.path

View file
  • Ignore whitespace
+[Unit]
+Description=Copy ~root/.ssh/authorized_keys to NFS root
+
+[Path]
+PathChanged=/root/.ssh/authorized_keys
+Unit=rootssh-copy.service
+
+[Install]
+WantedBy=multi-user.target

File systemd/udbsync_passwd_nfsroot.service

View file
  • Ignore whitespace
+[Unit]
+Description = /etc/{passwd,shadow,group} synchronisation daemon for nfsroot
+After = network.service
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/var/prologin/udbsync_passwd
+ExecStart=/var/prologin/venv/bin/python udbsync_passwd.py /export/nfsroot
+
+[Install]
+WantedBy=multi-user.target

File udbsync_passwd/udbsync_passwd.py

View file
  • Ignore whitespace
 
 import collections
 import datetime
+import functools
 import logging
 import os
 import prologin.config
 Group = collections.namedtuple('Group', 'name password gid members')
 
 USER_PATTERN = re.compile(
-    '(?P<login>[-a-z]+)'
+    '(?P<login>[-a-z_0-9]+)'
     ':(?P<password>[^:]*)'
     ':(?P<uid>\d+)'
     ':(?P<gid>\d+)'
 )
 
 SHADOW_PATTERN = re.compile(
-    '(?P<login>[-a-z]+):(?P<remainder>.*)$'
+    '(?P<login>[-a-z_0-9]+):(?P<remainder>.*)$'
 )
 
 GROUP_PATTERN = re.compile(
-    '(?P<name>[-a-z]+)'
+    '(?P<name>[-a-z_0-9]+)'
     ':(?P<password>[^:]*)'
     ':(?P<gid>\d+)'
-    ':(?P<members>[-a-z,]*)$'
+    ':(?P<members>[-a-z_0-9,]*)$'
 )
 
 PROLOGIN_GROUPS = {
         self.f.close()
         shutil.move(self.temp_path, self.filepath)
 
-def callback(users, updates_metadata):
+def callback(root_path, users, updates_metadata):
     logging.info('New updates: {!r}'.format(updates_metadata))
 
     passwd_users = {}
     groups = {}
 
     # First, parse /etc/passwd to get users that are not handled by Prologin.
-    with open('/etc/passwd', 'r') as f:
+    with open(os.path.join(root_path, 'etc/passwd'), 'r') as f:
         for line in f:
             line = line.rstrip('\n')
             m = USER_PATTERN.match(line)
                 return
 
     # Then, parse /etc/shadow to get passwords.
-    with open('/etc/shadow', 'r') as f:
+    with open(os.path.join(root_path, 'etc/shadow'), 'r') as f:
         for line in f:
             line = line.rstrip('\n')
             m = SHADOW_PATTERN.match(line)
                 return
 
     # Parse /etc/group to get... groups!
-    with open('/etc/group', 'r') as f:
+    with open(os.path.join(root_path, 'etc/group'), 'r') as f:
         for line in f:
             line = line.rstrip('\n')
             m = GROUP_PATTERN.match(line)
     sorted_groups.sort(key=lambda g: g.gid)
 
     # Finally, output updated files.
-    with BufferFile('/etc/passwd', PASSWD_PERMS) as f:
+    with BufferFile(os.path.join(root_path, 'etc/passwd'), PASSWD_PERMS) as f:
         for user in sorted_users:
             print(
                 '{0.login}:{0.password}'
                 file=f
             )
 
-    with BufferFile('/etc/shadow', SHADOW_PERMS) as f:
+    with BufferFile(os.path.join(root_path, 'etc/shadow'), SHADOW_PERMS) as f:
         for user in sorted_users:
             try:
                 shadow = shadow_passwords[user.login]
             else:
                 print('{}:{}'.format(user.login, shadow), file=f)
 
-    with BufferFile('/etc/group', GROUP_PERMS) as f:
+    with BufferFile(os.path.join(root_path, 'etc/group'), GROUP_PERMS) as f:
         for group in sorted_groups:
             print(
                 '{0.name}:{0.password}:{0.gid}:{1}'.format(
 
 
 if __name__ == '__main__':
+    if len(sys.argv) != 2:
+        root_path = '/'
+    else:
+        root_path = sys.argv[1]
     prologin.log.setup_logging('udbsync_passwd')
+    callback = functools.partial(callback, root_path)
     prologin.udbsync.connect().poll_updates(callback)