def refresh_cert(key_path, cert_path, ca_path, variant, ca_key_name, ca_cert_name): if configuration.get_config().is_kerberos_enabled(): print("rotating", variant, "certs via keyreq") if ca_path is None: call_keyreq(variant + "-cert", key_path, cert_path) else: call_keyreq(variant + "-cert", key_path, cert_path, ca_path) else: print("generating", variant, "cert via local bypass method") with tempfile.TemporaryDirectory() as dir: ca_key = os.path.join(dir, ca_key_name) ca_pem = os.path.join(dir, ca_cert_name) util.writefile( ca_key, authority.get_decrypted_by_filename("./" + ca_key_name)) pem = authority.get_pubkey_by_filename("./" + ca_cert_name) if ca_path is not None: util.writefile(ca_path, pem) util.writefile(ca_pem, pem) os.chmod(ca_key, 0o600) subprocess.check_call([ "keylocalcert", ca_key, ca_pem, "temporary-%s-bypass-grant" % variant, "4h", key_path, cert_path ])
def launch_user_grant(export: bool = False): "deploy the specifications to run the user grant website" config = configuration.get_config() if config.user_grant_domain == '': command.fail("no user_grant_domain specified in setup.yaml") if config.user_grant_email_domain == '': command.fail("no user_grant_email_domain specified in setup.yaml") skey, scert = keys.decrypt_https(config.user_grant_domain) skey64, scert64 = base64.b64encode(skey), base64.b64encode(scert) ikey = authority.get_decrypted_by_filename("./kubernetes.key") icert = authority.get_pubkey_by_filename("./kubernetes.pem") ikey64, icert64 = base64.b64encode(ikey), base64.b64encode(icert) _, upstream_cert_path = authority.get_upstream_cert_paths() if not os.path.exists(upstream_cert_path): command.fail( "user-grant-upstream.pem not found in homeworld directory") upstream_cert = util.readfile(upstream_cert_path).decode() launch_spec("//user-grant:kubernetes.yaml", { "SERVER_KEY_BASE64": skey64.decode(), "SERVER_CERT_BASE64": scert64.decode(), "ISSUER_KEY_BASE64": ikey64.decode(), "ISSUER_CERT_BASE64": icert64.decode(), "EMAIL_DOMAIN": config.user_grant_email_domain, "UPSTREAM_CERTIFICATE": upstream_cert, }, export=export)
def call_keyreq(command, *params, collect=False): config = configuration.Config.load_from_project() keyserver_domain = config.keyserver.hostname + "." + config.external_domain + ":20557" invoke_variant = subprocess.check_output if collect else subprocess.check_call with tempfile.TemporaryDirectory() as tdir: https_cert_path = os.path.join(tdir, "server.pem") util.writefile(https_cert_path, authority.get_pubkey_by_filename("./server.pem")) return invoke_variant(["keyreq", command, https_cert_path, keyserver_domain] + list(params))
def refresh_cert(key_path, cert_path, ca_path, variant, ca_key_name, ca_cert_name): errs = [] try: if configuration.get_config().is_kerberos_enabled(): print("rotating", variant, "certs via keyreq") if ca_path is None: call_keyreq(variant + "-cert", key_path, cert_path) else: call_keyreq(variant + "-cert", key_path, cert_path, ca_path) return except Exception as e: print("[keyreq failed, set SPIRE_DEBUG for traceback]") if os.environ.get('SPIRE_DEBUG'): traceback.print_exc() errs.append(e) try: print("generating", variant, "cert via local bypass method") with tempfile.TemporaryDirectory() as dir: ca_key = os.path.join(dir, ca_key_name) ca_pem = os.path.join(dir, ca_cert_name) util.writefile( ca_key, authority.get_decrypted_by_filename("./" + ca_key_name)) pem = authority.get_pubkey_by_filename("./" + ca_cert_name) if ca_path is not None: util.writefile(ca_path, pem) util.writefile(ca_pem, pem) os.chmod(ca_key, 0o600) if variant == "kube": name = "root:direct" orgs = ["system:masters"] else: name = "temporary-%s-bypass-grant" % variant orgs = [] subprocess.check_call([ "keylocalcert", ca_key, ca_pem, name, "4h", key_path, cert_path, "", ",".join(orgs) ]) return except Exception as e: print("[local bypass failed, set SPIRE_DEBUG for traceback]") if os.environ.get('SPIRE_DEBUG'): traceback.print_exc() errs.append(e) if len(errs) > 1: raise command.MultipleExceptions('refresh_cert failed', errs) raise Exception('refresh_cert failed') from errs[0]
def launch_user_grant(): config = configuration.get_config() skey, scert = keys.decrypt_https(config.user_grant_domain) skey64, scert64 = base64.b64encode(skey), base64.b64encode(scert) ikey = authority.get_decrypted_by_filename("./kubernetes.key") icert = authority.get_pubkey_by_filename("./kubernetes.pem") ikey64, icert64 = base64.b64encode(ikey), base64.b64encode(icert) launch_spec( "user-grant.yaml", { "SERVER_KEY_BASE64": skey64.decode(), "SERVER_CERT_BASE64": scert64.decode(), "ISSUER_KEY_BASE64": ikey64.decode(), "ISSUER_CERT_BASE64": icert64.decode(), })
def call_keyreq(keyreq_command, *params): config = configuration.get_config() keyserver_domain = config.keyserver.hostname + "." + config.external_domain + ":20557" with tempfile.TemporaryDirectory() as tdir: https_cert_path = os.path.join(tdir, "clusterca.pem") util.writefile(https_cert_path, authority.get_pubkey_by_filename("./clusterca.pem")) keyreq_sp = subprocess.Popen( ["keyreq", keyreq_command, https_cert_path, keyserver_domain] + list(params), stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err_bytes = keyreq_sp.communicate() if keyreq_sp.returncode != 0: err = err_bytes.decode() raise KeyreqFailed(keyreq_sp.returncode, err) return output
def update_known_hosts(): # uses local copies of machine list and ssh-host pubkey # TODO: eliminate now-redundant machine.list download from keyserver machines = configuration.get_machine_list_file().strip() cert_authority_pubkey = authority.get_pubkey_by_filename("./ssh_host_ca.pub") homedir = os.getenv("HOME") if homedir is None: command.fail("could not determine home directory, so could not find ~/.ssh/known_hosts") known_hosts_path = os.path.join(homedir, ".ssh", "known_hosts") known_hosts_old = util.readfile(known_hosts_path).decode().split("\n") if os.path.exists(known_hosts_path) else [] if known_hosts_old and not known_hosts_old[-1]: known_hosts_old.pop() known_hosts_new = _replace_cert_authority(known_hosts_old, machines, cert_authority_pubkey) util.writefile(known_hosts_path, ("\n".join(known_hosts_new) + "\n").encode()) print("~/.ssh/known_hosts updated")
def update_known_hosts(): "update ~/.ssh/known_hosts file with @ca-certificates directive" config = configuration.Config.load_from_project() machines = ",".join("%s.%s" % (node.hostname, config.external_domain) for node in config.nodes) cert_authority_pubkey = authority.get_pubkey_by_filename("./ssh-host.pub") known_hosts_path = get_known_hosts_path() known_hosts_old = util.readfile(known_hosts_path).decode().split( "\n") if os.path.exists(known_hosts_path) else [] if known_hosts_old and not known_hosts_old[-1]: known_hosts_old.pop() known_hosts_new = _replace_cert_authority(known_hosts_old, machines, cert_authority_pubkey) util.writefile(known_hosts_path, ("\n".join(known_hosts_new) + "\n").encode()) print("~/.ssh/known_hosts updated")
def update_known_hosts(): # uses local copies of machine list and ssh-host pubkey # TODO: eliminate now-redundant machine.list download from keyserver machines = configuration.get_machine_list_file().strip() cert_authority_pubkey = authority.get_pubkey_by_filename( "./ssh_host_ca.pub") known_hosts_path = get_known_hosts_path() known_hosts_old = util.readfile(known_hosts_path).decode().split( "\n") if os.path.exists(known_hosts_path) else [] if known_hosts_old and not known_hosts_old[-1]: known_hosts_old.pop() known_hosts_new = _replace_cert_authority(known_hosts_old, machines, cert_authority_pubkey) util.writefile(known_hosts_path, ("\n".join(known_hosts_new) + "\n").encode()) print("~/.ssh/known_hosts updated")
def call_keyreq(keyreq_command, *params, collect=False): config = configuration.get_config() keyserver_domain = config.keyserver.hostname + "." + config.external_domain + ":20557" invoke_variant = subprocess.check_output if collect else subprocess.check_call with tempfile.TemporaryDirectory() as tdir: https_cert_path = os.path.join(tdir, "server.pem") util.writefile(https_cert_path, authority.get_pubkey_by_filename("./server.pem")) try: return invoke_variant( ["keyreq", keyreq_command, https_cert_path, keyserver_domain] + list(params)) except subprocess.CalledProcessError as e: if e.returncode == 1: fail_hint = "do you have valid kerberos tickets?\n" \ "or, your connection to the server might be faulty.\n" \ "or, the server's keygateway might be broken." command.fail("keyreq failed: %s" % e, fail_hint) raise e
def call_keyreq(keyreq_command, *params): config = configuration.get_config() keyserver_domain = config.keyserver.hostname + "." + config.external_domain + ":20557" with tempfile.TemporaryDirectory() as tdir: https_cert_path = os.path.join(tdir, "server.pem") util.writefile(https_cert_path, authority.get_pubkey_by_filename("./server.pem")) keyreq_sp = subprocess.Popen( ["keyreq", keyreq_command, https_cert_path, keyserver_domain] + list(params), stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, err_bytes = keyreq_sp.communicate() if keyreq_sp.returncode != 0: err = err_bytes.decode() print(err) error_code_meaning, fail_hint = diagnose_keyreq_error( keyreq_sp.returncode, err) command.fail( "keyreq failed with error code %d: %s" % (keyreq_sp.returncode, error_code_meaning), fail_hint) return output
def gen_local_https_cert(name): "generate and encrypt a HTTPS keypair using the cluster-internal CA" if "," in name: command.fail( "cannot create https cert with comma: would be misinterpreted by keylocalcert" ) print("generating local-only https cert for", name, "via local bypass method") with tempfile.TemporaryDirectory() as dir: ca_key = os.path.join(dir, "ca.key") ca_pem = os.path.join(dir, "ca.pem") key_path = os.path.join(dir, "gen.key") cert_path = os.path.join(dir, "gen.pem") util.writefile(ca_key, authority.get_decrypted_by_filename("./clusterca.key")) pem = authority.get_pubkey_by_filename("./clusterca.pem") util.writefile(ca_pem, pem) os.chmod(ca_key, 0o600) subprocess.check_call([ "keylocalcert", ca_key, ca_pem, name, "4h", key_path, cert_path, name, "" ]) import_https(name, key_path, cert_path) print("generated local-only https cert!")
def gen_iso(iso_image, authorized_key, mode=None): "generate ISO" with tempfile.TemporaryDirectory() as d: config = configuration.get_config() inclusion = [] with open(os.path.join(d, "dns_bootstrap_lines"), "w") as outfile: outfile.write(setup.dns_bootstrap_lines()) inclusion += ["dns_bootstrap_lines"] util.copy(authorized_key, os.path.join(d, "authorized.pub")) util.writefile(os.path.join(d, "keyservertls.pem"), authority.get_pubkey_by_filename("./clusterca.pem")) inclusion += ["authorized.pub", "keyservertls.pem"] os.makedirs(os.path.join(d, "var/lib/dpkg/info")) scripts = { "//spire/resources:postinstall.sh": "postinstall.sh", "//spire/resources:prepartition.sh": "prepartition.sh", "//spire/resources:netcfg.postinst": "var/lib/dpkg/info/netcfg.postinst", } for source, destination in sorted(scripts.items()): resource.extract(source, os.path.join(d, destination)) os.chmod(os.path.join(d, destination), 0o755) inclusion.append(destination) util.writefile(os.path.join(d, "keyserver.domain"), configuration.get_keyserver_domain().encode()) inclusion.append("keyserver.domain") util.writefile(os.path.join(d, "vlan.txt"), b"%d\n" % config.vlan) inclusion.append("vlan.txt") resource.extract("//spire/resources:sshd_config", os.path.join(d, "sshd_config.new")) preseeded = resource.get("//spire/resources:preseed.cfg.in") generated_password = util.pwgen(20) creation_time = datetime.datetime.now().isoformat() git_hash = metadata.get_git_version().encode() add_password_to_log(generated_password, creation_time) print("generated password added to log") preseeded = preseeded.replace(b"{{HASH}}", util.mkpasswd(generated_password)) preseeded = preseeded.replace(b"{{BUILDDATE}}", creation_time.encode()) preseeded = preseeded.replace(b"{{GITHASH}}", git_hash) mirror = config.mirror if mirror.count("/") < 1 or mirror.count(".") < 1: command.fail( "invalid mirror specification '%s'; must be of the form HOST.NAME/PATH" ) mirror_host, mirror_dir = mirror.split("/", 1) preseeded = preseeded.replace(b"{{MIRROR-HOST}}", mirror_host.encode()) preseeded = preseeded.replace(b"{{MIRROR-DIR}}", ("/" + mirror_dir).encode()) preseeded = preseeded.replace(b"{{KERBEROS-REALM}}", config.realm.encode()) cidr_nodes = config.cidr_nodes node_cidr_prefix = ".".join( str(cidr_nodes.network_address).split(".")[:-1]) + "." preseeded = preseeded.replace(b"{{IP-PREFIX}}", node_cidr_prefix.encode()) node_cidr_gateway = next(cidr_nodes.hosts()) preseeded = preseeded.replace(b"{{GATEWAY}}", str(node_cidr_gateway).encode()) preseeded = preseeded.replace(b"{{NETMASK}}", str(cidr_nodes.netmask).encode()) preseeded = preseeded.replace( b"{{NAMESERVERS}}", " ".join(str(server_ip) for server_ip in config.dns_upstreams).encode()) util.writefile(os.path.join(d, "preseed.cfg"), preseeded) inclusion += ["sshd_config.new", "preseed.cfg"] for package_name, (short_filename, package_bytes) in packages.verified_download_full( PACKAGES).items(): if ("/" in short_filename or not short_filename.startswith(package_name + "_") or not short_filename.endswith("_amd64.deb")): raise ValueError("invalid package name: %s for %s" % (short_filename, package_name)) util.writefile(os.path.join(d, short_filename), package_bytes) inclusion.append(short_filename) cddir = os.path.join(d, "cd") os.mkdir(cddir) subprocess.check_call( ["bsdtar", "-C", cddir, "-xzf", "/usr/share/homeworld/debian.iso"]) subprocess.check_call(["chmod", "+w", "--recursive", cddir]) if mode is not None: if mode not in MODES: command.fail("no such ISO mode: %s" % mode) MODES[mode](d, cddir, inclusion) with gzip.open(os.path.join(cddir, "initrd.gz"), "ab") as f: f.write( subprocess.check_output( ["cpio", "--create", "--format=newc"], input="".join("%s\n" % filename for filename in inclusion).encode(), cwd=d)) files_for_md5sum = subprocess.check_output( ["find", ".", "-follow", "-type", "f", "-print0"], cwd=cddir).decode().split("\0") files_for_md5sum = [x for x in files_for_md5sum if x] md5s = subprocess.check_output(["md5sum", "--"] + files_for_md5sum, cwd=cddir) util.writefile(os.path.join(cddir, "md5sum.txt"), md5s) temp_iso = os.path.join(d, "temp.iso") subprocess.check_call([ "xorriso", "-as", "mkisofs", "-quiet", "-o", temp_iso, "-r", "-J", "-c", "boot.cat", "-b", "isolinux.bin", "-no-emul-boot", "-boot-load-size", "4", "-boot-info-table", cddir ]) subprocess.check_call(["isohybrid", "-h", "64", "-s", "32", temp_iso]) util.copy(temp_iso, iso_image)
def gen_iso(iso_image, authorized_key, mode=None): with tempfile.TemporaryDirectory() as d: inclusion = [] with open(os.path.join(d, "dns_bootstrap_lines"), "w") as outfile: outfile.write(setup.dns_bootstrap_lines()) inclusion += ["dns_bootstrap_lines"] util.copy(authorized_key, os.path.join(d, "authorized.pub")) util.writefile(os.path.join(d, "keyservertls.pem"), authority.get_pubkey_by_filename("./server.pem")) resource.copy_to("postinstall.sh", os.path.join(d, "postinstall.sh")) os.chmod(os.path.join(d, "postinstall.sh"), 0o755) inclusion += ["authorized.pub", "keyservertls.pem", "postinstall.sh"] for variant in configuration.KEYCLIENT_VARIANTS: util.writefile(os.path.join(d, "keyclient-%s.yaml" % variant), configuration.get_keyclient_yaml(variant).encode()) inclusion.append("keyclient-%s.yaml" % variant) resource.copy_to("sshd_config", os.path.join(d, "sshd_config.new")) preseeded = resource.get_resource("preseed.cfg.in") generated_password = util.pwgen(20) creation_time = datetime.datetime.now().isoformat() git_hash = get_git_version().encode() add_password_to_log(generated_password, creation_time) print("generated password added to log") preseeded = preseeded.replace(b"{{HASH}}", util.mkpasswd(generated_password)) preseeded = preseeded.replace(b"{{BUILDDATE}}", creation_time.encode()) preseeded = preseeded.replace(b"{{GITHASH}}", git_hash) mirror = configuration.get_config().mirror if mirror.count("/") < 1 or mirror.count(".") < 1: command.fail( "invalid mirror specification '%s'; must be of the form HOST.NAME/PATH" ) mirror_host, mirror_dir = mirror.split("/", 1) preseeded = preseeded.replace(b"{{MIRROR-HOST}}", mirror_host.encode()) preseeded = preseeded.replace(b"{{MIRROR-DIR}}", ("/" + mirror_dir).encode()) realm = configuration.get_config().realm preseeded = preseeded.replace(b"{{KERBEROS-REALM}}", realm.encode()) cidr_nodes, upstream_dns_servers = configuration.get_config( ).cidr_nodes, configuration.get_config().dns_upstreams node_cidr_prefix = ".".join(str(cidr_nodes.ip).split(".")[:-1]) + "." preseeded = preseeded.replace(b"{{IP-PREFIX}}", node_cidr_prefix.encode()) node_cidr_gateway = cidr_nodes.gateway() preseeded = preseeded.replace(b"{{GATEWAY}}", str(node_cidr_gateway).encode()) node_cidr_netmask = cidr_nodes.netmask() preseeded = preseeded.replace(b"{{NETMASK}}", str(node_cidr_netmask).encode()) preseeded = preseeded.replace( b"{{NAMESERVERS}}", " ".join(str(server_ip) for server_ip in upstream_dns_servers).encode()) util.writefile(os.path.join(d, "preseed.cfg"), preseeded) inclusion += ["sshd_config.new", "preseed.cfg"] for package_name, (short_filename, package_bytes) in packages.verified_download_full( PACKAGES).items(): assert "/" not in short_filename, "invalid package name: %s for %s" % ( short_filename, package_name) assert short_filename.startswith( package_name + "_"), "invalid package name: %s for %s" % (short_filename, package_name) assert short_filename.endswith( "_amd64.deb"), "invalid package name: %s for %s" % ( short_filename, package_name) util.writefile(os.path.join(d, short_filename), package_bytes) inclusion.append(short_filename) cddir = os.path.join(d, "cd") os.mkdir(cddir) subprocess.check_call( ["bsdtar", "-C", cddir, "-xzf", "/usr/share/homeworld/debian.iso"]) subprocess.check_call(["chmod", "+w", "--recursive", cddir]) if mode is not None: if mode not in MODES: command.fail("no such ISO mode: %s" % mode) MODES[mode](d, cddir, inclusion) with gzip.open(os.path.join(cddir, "initrd.gz"), "ab") as f: subprocess.run(["cpio", "--create", "--format=newc"], check=True, stdout=f, input="".join("%s\n" % filename for filename in inclusion).encode(), cwd=d) files_for_md5sum = subprocess.check_output( ["find", ".", "-follow", "-type", "f", "-print0"], cwd=cddir).decode().split("\0") assert files_for_md5sum.pop() == "" md5s = subprocess.check_output(["md5sum", "--"] + files_for_md5sum, cwd=cddir) util.writefile(os.path.join(cddir, "md5sum.txt"), md5s) temp_iso = os.path.join(d, "temp.iso") subprocess.check_call([ "xorriso", "-as", "mkisofs", "-quiet", "-o", temp_iso, "-r", "-J", "-c", "boot.cat", "-b", "isolinux.bin", "-no-emul-boot", "-boot-load-size", "4", "-boot-info-table", cddir ]) subprocess.check_call(["isohybrid", "-h", "64", "-s", "32", temp_iso]) util.copy(temp_iso, iso_image)
def gen_iso(iso_image, authorized_key, cdpack=None): with tempfile.TemporaryDirectory() as d: inclusion = [] util.copy(authorized_key, os.path.join(d, "authorized.pub")) util.writefile(os.path.join(d, "keyservertls.pem"), authority.get_pubkey_by_filename("./server.pem")) resource.copy_to("postinstall.sh", os.path.join(d, "postinstall.sh")) os.chmod(os.path.join(d, "postinstall.sh"), 0o755) inclusion += ["authorized.pub", "keyservertls.pem", "postinstall.sh"] for variant in configuration.KEYCLIENT_VARIANTS: util.writefile(os.path.join(d, "keyclient-%s.yaml" % variant), configuration.get_keyclient_yaml(variant).encode()) inclusion.append("keyclient-%s.yaml" % variant) resource.copy_to("sshd_config", os.path.join(d, "sshd_config.new")) preseeded = resource.get_resource("preseed.cfg.in") generated_password = util.pwgen(20) add_password_to_log(generated_password) print("generated password added to log") preseeded = preseeded.replace(b"{{HASH}}", util.mkpasswd(generated_password)) util.writefile(os.path.join(d, "preseed.cfg"), preseeded) inclusion += ["sshd_config.new", "preseed.cfg"] for package_name, (short_filename, package_bytes) in packages.verified_download_full( PACKAGES).items(): assert "/" not in short_filename, "invalid package name: %s for %s" % ( short_filename, package_name) assert short_filename.startswith( package_name + "_"), "invalid package name: %s for %s" % (short_filename, package_name) assert short_filename.endswith( "_amd64.deb"), "invalid package name: %s for %s" % ( short_filename, package_name) util.writefile(os.path.join(d, short_filename), package_bytes) inclusion.append(short_filename) if cdpack is not None: subprocess.check_call(["tar", "-C", d, "-xzf", cdpack, "cd"]) else: subprocess.check_output( ["tar", "-C", d, "-xz", "cd"], input=resource.get_resource("debian-9.2.0-cdpack.tgz")) subprocess.check_output([ "cpio", "--create", "--append", "--format=newc", "--file=cd/initrd" ], input="".join( "%s\n" % filename for filename in inclusion).encode(), cwd=d) subprocess.check_call(["gzip", os.path.join(d, "cd/initrd")]) files_for_md5sum = subprocess.check_output( ["find", ".", "-follow", "-type", "f", "-print0"], cwd=os.path.join(d, "cd")).decode().split("\0") assert files_for_md5sum.pop() == "" md5s = subprocess.check_output(["md5sum", "--"] + files_for_md5sum, cwd=os.path.join(d, "cd")) util.writefile(os.path.join(d, "cd", "md5sum.txt"), md5s) subprocess.check_call([ "genisoimage", "-quiet", "-o", iso_image, "-r", "-J", "-no-emul-boot", "-boot-load-size", "4", "-boot-info-table", "-b", "isolinux.bin", "-c", "isolinux.cat", os.path.join(d, "cd") ])
def get_verified_keyserver_opener() -> urllib.request.OpenerDirector: keyserver_cert = authority.get_pubkey_by_filename("./clusterca.pem") context = ssl.create_default_context(cadata=keyserver_cert.decode()) opener = urllib.request.OpenerDirector() opener.add_handler(urllib.request.HTTPSHandler(context=context, check_hostname=True)) return opener