diff --git a/pinecrypt/server/cli.py b/pinecrypt/server/cli.py index 64c79d5..72940ae 100644 --- a/pinecrypt/server/cli.py +++ b/pinecrypt/server/cli.py @@ -30,10 +30,12 @@ from wsgiref.simple_server import make_server logger = logging.getLogger(__name__) mongolog.register() + def graceful_exit(signal_number, stack_frame): print("Received signal %d, exiting now" % signal_number) sys.exit(0) + def fqdn_required(func): def wrapped(**args): common_name = args.get("common_name") @@ -104,7 +106,8 @@ def pinecone_sign(common_name, overwrite, profile): @click.command("revoke", help="Revoke certificate") -@click.option("--reason", "-r", default="key_compromise", help="Revocation reason, one of: key_compromise affiliation_changed superseded cessation_of_operation privilege_withdrawn") +@click.option("--reason", "-r", default="key_compromise", + help="Revocation reason, one of: key_compromise affiliation_changed superseded cessation_of_operation privilege_withdrawn") @click.argument("common_name") def pinecone_revoke(common_name, reason): from pinecrypt.server import authority @@ -172,16 +175,19 @@ def pinecone_serve_ocsp_responder(): from pinecrypt.server.api.ocsp import app app.run(port=5001, debug=const.DEBUG) + @click.command("events") def pinecone_serve_events(): from pinecrypt.server.api.events import app app.run(port=8001, debug=const.DEBUG) + @click.command("builder") def pinecone_serve_builder(): from pinecrypt.server.api.builder import app app.run(port=7001, debug=const.DEBUG) + @click.command("provision", help="Provision keys") def pinecone_provision(): default_policy = "REJECT" if const.DEBUG else "DROP" @@ -211,7 +217,6 @@ def pinecone_provision(): for subnet in const.PROMETHEUS_SUBNETS: os.system("ipset add -exist -quiet ipset%d-prometheus-subnets %s" % (subnet.version, subnet)) - def g(): yield "*filter" yield ":INBOUND_BLOCKED - [0:0]" @@ -268,18 +273,19 @@ def pinecone_provision(): fh.write(line) fh.write("\n") - os.system("iptables-restore < /tmp/rules4") - os.system("sed -e 's/ipset4/ipset6/g' -e 's/p icmp/p ipv6-icmp/g' /tmp/rules4 > /tmp/rules6") - os.system("ip6tables-restore < /tmp/rules6") - os.system("sysctl -w net.ipv6.conf.all.forwarding=1") - os.system("sysctl -w net.ipv6.conf.default.forwarding=1") - os.system("sysctl -w net.ipv4.ip_forward=1") + if not const.DISABLE_FIREWALL: + os.system("iptables-restore < /tmp/rules4") + os.system("sed -e 's/ipset4/ipset6/g' -e 's/p icmp/p ipv6-icmp/g' /tmp/rules4 > /tmp/rules6") + os.system("ip6tables-restore < /tmp/rules6") + os.system("sysctl -w net.ipv6.conf.all.forwarding=1") + os.system("sysctl -w net.ipv6.conf.default.forwarding=1") + os.system("sysctl -w net.ipv4.ip_forward=1") if const.REPLICAS: click.echo("Provisioning MongoDB replicaset") # WTF https://github.com/docker-library/mongo/issues/339 c = pymongo.MongoClient("localhost", 27017) - config ={"_id" : "rs0", "members": [ + config = {"_id": "rs0", "members": [ {"_id": index, "host": "%s:27017" % hostname} for index, hostname in enumerate(const.REPLICAS)]} print("Provisioning MongoDB replicaset: %s" % repr(config)) try: @@ -404,7 +410,7 @@ def pinecone_serve_backend(): token_resource = None token_manager = None - if const.USER_ENROLLMENT_ALLOWED: # TODO: add token enable/disable flag for config + if const.USER_ENROLLMENT_ALLOWED: # TODO: add token enable/disable flag for config token_manager = TokenManager() token_resource = TokenResource(token_manager) app.add_route("/api/token", token_resource) @@ -442,7 +448,7 @@ def pinecone_token_list(): cols = "uuid", "expires", "subject", "state" now = datetime.utcnow().replace(tzinfo=pytz.UTC) for token in token_manager.list(expired=True, used=True): - token["state"] = "used" if token.get("used") else ("valid" if token.get("expires") > now else "expired") + token["state"] = "used" if token.get("used") else ("valid" if token.get("expires") > now else "expired") print(";".join([str(token.get(col)) for col in cols])) @@ -555,8 +561,8 @@ def pinecone_serve_openvpn(local, proto, client_subnet_slot): # For more info see: openvpn --show-tls args += "--tls-version-min", config.get("Globals", "OPENVPN_TLS_VERSION_MIN")["value"] - args += "--tls-ciphersuites", config.get("Globals", "OPENVPN_TLS_CIPHERSUITES")["value"], # Used by TLS 1.3 - args += "--tls-cipher", config.get("Globals", "OPENVPN_TLS_CIPHER")["value"], # Used by TLS 1.2 + args += "--tls-ciphersuites", config.get("Globals", "OPENVPN_TLS_CIPHERSUITES")["value"], # Used by TLS 1.3 + args += "--tls-cipher", config.get("Globals", "OPENVPN_TLS_CIPHER")["value"], # Used by TLS 1.2 # Data channel encryption parameters # TODO: Rename to --data-cipher when OpenVPN 2.5 becomes available @@ -604,7 +610,7 @@ def pinecone_serve_strongswan(client_subnet_slot): fh.write(" left=%s\n" % const.AUTHORITY_NAMESPACE) fh.write(" leftsendcert=always\n") - fh.write(" leftallowany=yes\n") # For load-balancing + fh.write(" leftallowany=yes\n") # For load-balancing fh.write(" leftcert=%s\n" % const.SELF_CERT_PATH) if const.PUSH_SUBNETS: fh.write(" leftsubnet=%s\n" % ",".join([str(j) for j in const.PUSH_SUBNETS])) @@ -626,8 +632,8 @@ def pinecone_serve_strongswan(client_subnet_slot): public_key = asymmetric.load_public_key(certificate["tbs_certificate"]["subject_public_key_info"]) with open("/etc/ipsec.secrets", "w") as fh: fh.write(": %s %s\n" % ( - "ECDSA" if public_key.algorithm == "ec" else "RSA", - const.SELF_KEY_PATH + "ECDSA" if public_key.algorithm == "ec" else "RSA", + const.SELF_KEY_PATH )) executable = "/usr/sbin/ipsec" args = executable, "start", "--nofork" diff --git a/pinecrypt/server/const.py b/pinecrypt/server/const.py index aeaa7fc..2f4e4d7 100644 --- a/pinecrypt/server/const.py +++ b/pinecrypt/server/const.py @@ -1,4 +1,4 @@ - +import ldap import click import os import re @@ -9,19 +9,22 @@ from ipaddress import ip_network from math import log, ceil RE_USERNAME = r"^[a-z][a-z0-9]+$" -RE_FQDN = r"^(([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])?$" -RE_HOSTNAME = r"^[a-z0-9]([a-z0-9\-_]{0,61}[a-z0-9])?$" +RE_FQDN = r"^(([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])?$" +RE_HOSTNAME = r"^[a-z0-9]([a-z0-9\-_]{0,61}[a-z0-9])?$" RE_COMMON_NAME = r"^[A-Za-z0-9\-\_]+$" # Make sure locales don't mess up anything assert re.match(RE_USERNAME, "abstuzxy19") + # To be migrated to Mongo or removed def parse_tag_types(d): r = [] for j in d.split(","): r.append(j.split("/")) return r + + TAG_TYPES = parse_tag_types(os.getenv("TAG_TYPES", "owner/str,location/str,phone/str,other/str")) SCRIPT_DIR = "" IMAGE_BUILDER_PROFILES = [] @@ -57,16 +60,18 @@ except socket.gaierror: try: HOSTNAME, DOMAIN = FQDN.split(".", 1) -except ValueError: # If FQDN is not configured +except ValueError: # If FQDN is not configured click.echo("FQDN not configured: %s" % repr(FQDN)) sys.exit(255) + def getenv_in(key, default, *vals): val = os.getenv(key, default) if val not in (default,) + vals: raise ValueError("Got %s for %s, expected one of %s" % (repr(val), key, vals)) return val + # Authority namespace corresponds to DNS entry which represents refers to all replicas AUTHORITY_NAMESPACE = os.getenv("AUTHORITY_NAMESPACE", FQDN) if FQDN != AUTHORITY_NAMESPACE and not FQDN.endswith(".%s" % AUTHORITY_NAMESPACE): @@ -76,14 +81,14 @@ USER_NAMESPACE = "u.%s" % AUTHORITY_NAMESPACE MACHINE_NAMESPACE = "m.%s" % AUTHORITY_NAMESPACE AUTHORITY_COMMON_NAME = "Pinecrypt Gateway at %s" % AUTHORITY_NAMESPACE AUTHORITY_ORGANIZATION = os.getenv("AUTHORITY_ORGANIZATION") -AUTHORITY_LIFETIME_DAYS = 20*365 +AUTHORITY_LIFETIME_DAYS = 20 * 365 # Advertise following IP addresses via DNS record ADVERTISE_ADDRESS = os.getenv("ADVERTISE_ADDRESS", "").split(",") if not ADVERTISE_ADDRESS: ADVERTISE_ADDRESS = set() - for fam, _, _, _, addrs in socket.getaddrinfo(const.FQDN, None): - if fam in(2, 10): + for fam, _, _, _, addrs in socket.getaddrinfo(FQDN, None): + if fam in (2, 10): ADVERTISE_ADDRESS.add(addrs[0]) # Mailer settings @@ -125,7 +130,6 @@ LDAP_USER_FILTER = os.getenv("LDAP_USER_FILTER", "(samaccountname=%s)") LDAP_ADMIN_FILTER = os.getenv("LDAP_ADMIN_FILTER", "(samaccountname=%s)") LDAP_COMPUTER_FILTER = os.getenv("LDAP_COMPUTER_FILTER", "()") -import ldap LDAP_CA_CERT = os.getenv("LDAP_CA_CERT") if LDAP_CA_CERT: ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT) @@ -134,9 +138,11 @@ if os.getenv("LDAP_DEBUG"): ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) ldap.set_option(ldap.OPT_DEBUG_LEVEL, 1) + def getenv_subnets(key, default=""): return set([ip_network(j) for j in os.getenv(key, default).replace(",", " ").split(" ") if j]) + USER_SUBNETS = getenv_subnets("AUTH_USER_SUBNETS", "0.0.0.0/0 ::/0") ADMIN_SUBNETS = getenv_subnets("AUTH_ADMIN_SUBNETS", "0.0.0.0/0 ::/0") AUTOSIGN_SUBNETS = getenv_subnets("AUTH_AUTOSIGN_SUBNETS", "") @@ -178,3 +184,5 @@ SESSION_COOKIE = "sha512brownies" SESSION_AGE = 3600 SECRET_STORAGE = getenv_in("SECRET_STORAGE", "fs", "db") + +DISABLE_FIREWALL = os.getenv("DISABLE_FIREWALL") == "True" if os.getenv("DISABLE_FIREWALL") else False