#!/bin/sh """ usage: init-host [options] HOSTNAME Options: --secrets-dir DIR Path to secrets [Default: ~/secrets/] --stockholm-dir DIR Path to stockholm [Default: ~/stockholm/] --username USER Primary username of the new host [Default: $LOGNAME] --create-ssh-keys creates via ssh-keygen --ssh-key-type TYPE Type of the ssh key to generate [Default: ed25519] --create-passwords creates , password input is interactive Tinc keys are stored in secrets-dir/HOSTNAME/retiolum.rsa_key.priv . For building shared hosts set secrets-dir to `` """ import sys import os from os.path import join as path_join,exists import logging as log from subprocess import Popen,PIPE log.basicConfig(level=log.DEBUG) # a list of all the files which have been created with this script created = [] def mark(f): log.info("created {}".format(f)) created.append(f) def retiolum_ip(hostname): """ warning this function actually writes stuff to the disk """ import ipaddress as ip from random import randint mynet4 = ip.ip_network("10.243.0.0/16") mynet6 = ip.ip_network("42::/16") ret = {"hostname": hostname} ret["v6"] = str(ip.IPv6Address(mynet6[0] + randint(0,mynet6.num_addresses)))+"/128" ret["v4"] = str(ip.IPv4Address(mynet4[0] + randint(0,mynet4.num_addresses)))+"/32" return ret def write_stockholm_1systems(ret,stockholm_dir): """ writes new nix file in stockholm/$LOGNAME/1systems/${HOSTNAME}.nix if it not yet exists""" p=path_join(stockholm_dir, ret['username'],'1systems', "{}.nix".format(ret['hostname'])) if exists (p): log.warn(" {} already exists, will not override with minimal config".format(p)) else: log.info("Creating {} with minimal config".format(p)) with open(p,"w+") as f: f.write("""{{ config, pkgs, ... }}: {{ krebs = {{ enable = true; retiolum.enable = true; build.host = config.krebs.hosts.{hostname}; }}; # You want to change these :) boot.loader.grub.device = "/dev/sda"; fileSystems."/" = {{ device = "/dev/sda1"; }}; }}""".format(**ret)) mark(p) def print_stockholm_krebs_entry(ret): if "ssh" in ret: ret['ssh_entry'] = """ ssh.privkey.path = <{key_file}>; ssh.pubkey = "{pubkey}";""".format(**ret['ssh']) else: ret['ssh_entry'] = "" print("""# this entry is autogenerated and can be added to # stockholm/krebs/3modules/{username}/default.nix {hostname} = rec {{ cores = 1; {ssh_entry} nets = {{ retiolm = {{ addrs4 = ["{v4}"]; addrs6 = ["{v6}"]; aliases = [ "{hostname}.retiolum" ]; tinc.pubkey = '' {pubkey}''; }}; }}; }};""".format(**ret)) def generate_tinc_keys(base): """ creates tinc public and private keys in `base` returns rsa public key """ import shutil from os import rmdir from tempfile import mkdtemp tmpdir = mkdtemp() process = Popen(["tinc","--batch","--config",tmpdir,"generate-keys","2048"],stdout=PIPE,stderr=PIPE,stdin=PIPE,bufsize=0) process.communicate() for i in ["ed25519_key.priv", "ed25519_key.pub", "rsa_key.priv","rsa_key.pub"]: fname = base+"."+i shutil.move(path_join(tmpdir,i),fname) mark(fname) # should be empty now shutil.rmtree(tmpdir) with open(base+".rsa_key.pub") as pubfile: return pubfile.read() def generate_ssh_keys(secrets_dir,hostname,typ="ed25519"): """creates a ssh public-private keypair in `base`""" # default sshd format key_file = "{}/ssh_host_{}_key".format(secrets_dir,typ) pub_file = key_file+".pub" if exists(key_file): log.error("{} already exists".format(key_file)) log.error("Use another hostname or remove the folder to continue") sys.exit(1) Popen(["ssh-keygen","-C",hostname, "-t",typ, "-f",key_file, "-N",""]).communicate() with open(pub_file) as f: pubkey = f.read() os.unlink(pub_file) mark(key_file) return { "pubkey": pubkey, "key_file": key_file } def prepare_secrets(sec): if not exists(sec): os.makedirs(sec,mode=488) mark(sec) log.info("Creating {}".format(sec)) else: log.error(" {} already exists".format(sec)) log.error("Use another hostname or remove the folder to continue") sys.exit(1) def check_existence(files): for f in files: if not exists(f): log.error(" {} does not exist but is a hard requirement for \ continuing".format(f)) log.error("Create/Clone the folder or set it to the correct \ location via cli options (--help)") log.error(__doc__) sys.exit(1) def create_passwords(sec,usernames): import crypt from getpass import getpass shadow = path_join(sec,"hashedPasswords.nix") with open(shadow, "w+")as f: f.write("{\n") for usr in usernames: # TODO: do not block, set password via another channel pw = getpass("Password for {}:".format(usr)) crypted = crypt.crypt(pw, crypt.mksalt(crypt.METHOD_SHA512)) f.write(' {} = "{}";\n'.format(usr,crypted)) f.write("}\n") mark(shadow) return shadow def main(): from os.path import expanduser,expandvars from docopt import docopt args = docopt(__doc__) hostname = args["HOSTNAME"] secrets_dir = expanduser(args["--secrets-dir"]) username = expandvars(args["--username"]) stockholm_dir = expanduser(args["--stockholm-dir"]) check_existence([secrets_dir,stockholm_dir]) host_secrets = path_join(secrets_dir,hostname) prepare_secrets(host_secrets) ret = retiolum_ip(hostname) ret['username'] = username # generate tinc keys, return pubkey retiolum = path_join(secrets_dir,hostname,"retiolum") ret['pubkey'] = generate_tinc_keys(retiolum) if args["--create-passwords"]: ret['shadow'] = create_passwords(host_secrets,["root",username]) if args["--create-ssh-keys"]: ret['ssh'] = generate_ssh_keys(path_join(secrets_dir,hostname), hostname, typ=args['--ssh-key-type']) write_stockholm_1systems(ret,stockholm_dir) print_stockholm_krebs_entry(ret) log.info("The following files have been created on your behalf:") for f in created: log.info(" "+f) if __name__ == '__main__': main()