diff --git a/Dockerfile b/Dockerfile index 4cd3e88..7b59605 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup \ && apt-get install -y -qq \ bash build-essential python3-dev cython3 libffi-dev libssl-dev \ libkrb5-dev ldap-utils libsasl2-modules-gssapi-mit libsasl2-dev libldap2-dev \ - python python3-pip python3-cffi iptables ipset \ + python python3-pip python3-cffi \ libncurses5-dev gawk wget unzip git rsync \ && apt-get clean \ && rm /etc/dpkg/dpkg.cfg.d/docker-apt-speedup diff --git a/pinecrypt/server/authority.py b/pinecrypt/server/authority.py index 81cd8cd..831f7d9 100644 --- a/pinecrypt/server/authority.py +++ b/pinecrypt/server/authority.py @@ -411,6 +411,7 @@ def sign(profile, skip_notify=False, overwrite=False, signer=None, namespace=con d = { "common_name": common_name, "status": "signed", + "disabled": False, "serial_number": "%x" % builder.serial_number, "signed": builder.begin_date, "expires": builder.end_date, diff --git a/pinecrypt/server/cli.py b/pinecrypt/server/cli.py index 05e53d3..b316d89 100644 --- a/pinecrypt/server/cli.py +++ b/pinecrypt/server/cli.py @@ -14,7 +14,6 @@ import logging import os import pymongo import signal -import socket import sys import pytz from asn1crypto import pem, x509 @@ -107,6 +106,36 @@ def pinecone_sign(common_name, overwrite, profile): authority.sign(common_name, overwrite=overwrite, profile=profile) +@click.command("disable", help="Disable client node or gateway replica temporarily") +@click.argument("common_name") +def pinecone_disable(common_name): + from pinecrypt.server import db + result = db.certificates.update_one({ + "common_name": common_name + }, { + "$set": { + "disabled": datetime.utcnow() + } + }) + if result.matched_count != 1: + raise click.ClickException("Invalid common name") + + +@click.command("enable", help="Enable client node or gateway replica") +@click.argument("common_name") +def pinecone_enable(common_name): + from pinecrypt.server import db + result = db.certificates.update_one({ + "common_name": common_name + }, { + "$set": { + "disabled": False + } + }) + if result.matched_count != 1: + raise click.ClickException("Invalid common name") + + @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") @@ -185,110 +214,6 @@ def pinecone_serve_builder(): @click.command("provision", help="Provision keys") def pinecone_provision(): - default_policy = "REJECT" if const.DEBUG else "DROP" - - click.echo("Setting up firewall rules") - if const.REPLICAS: - # TODO: atomic update with `ipset restore` - for replica in const.REPLICAS: - for fam, _, _, _, addrs in socket.getaddrinfo(replica, None): - if fam == 10: - os.system("ipset add ipset6-mongo-replicas %s" % addrs[0]) - elif fam == 2: - os.system("ipset add ipset4-mongo-replicas %s" % addrs[0]) - - os.system("ipset create -exist -quiet ipset4-client-ingress hash:ip timeout 3600 counters") - os.system("ipset create -exist -quiet ipset6-client-ingress hash:ip family inet6 timeout 3600 counters") - - os.system("ipset create -exist -quiet ipset4-client-egress hash:ip timeout 3600 counters") - os.system("ipset create -exist -quiet ipset6-client-egress hash:ip family inet6 timeout 3600 counters") - - os.system("ipset create -exist -quiet ipset4-mongo-replicas hash:ip") - os.system("ipset create -exist -quiet ipset6-mongo-replicas hash:ip family inet6") - - os.system("ipset create -exist -quiet ipset4-prometheus-subnets hash:net") - os.system("ipset create -exist -quiet ipset6-prometheus-subnets hash:net family inet6") - - 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]" - yield "-A INBOUND_BLOCKED -j %s -m comment --comment \"Default policy\"" % default_policy - - yield ":OUTBOUND_CLIENT - [0:0]" - yield "-A OUTBOUND_CLIENT -m set ! --match-set ipset4-client-ingress dst -j SET --add-set ipset4-client-ingress dst" - yield "-A OUTBOUND_CLIENT -j ACCEPT" - - yield ":INBOUND_CLIENT - [0:0]" - yield "-A INBOUND_CLIENT -m set ! --match-set ipset4-client-ingress src -j SET --add-set ipset4-client-ingress src" - yield "-A INBOUND_CLIENT -j ACCEPT" - - yield ":INPUT DROP [0:0]" - yield "-A INPUT -i lo -j ACCEPT -m comment --comment \"Allow loopback\"" - yield "-A INPUT -p icmp -j ACCEPT -m comment --comment \"Allow ping\"" - yield "-A INPUT -p esp -j ACCEPT -m comment --comment \"Allow ESP traffic\"" - yield "-A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT -m comment --comment \"Allow returning packets\"" - yield "-A INPUT -p tcp --dport 22 -j ACCEPT -m comment --comment \"Allow SSH\"" - yield "-A INPUT -p udp --dport 53 -j ACCEPT -m comment --comment \"Allow GoreDNS over UDP\"" - yield "-A INPUT -p tcp --dport 53 -j ACCEPT -m comment --comment \"Allow GoreDNS over TCP\"" - yield "-A INPUT -p tcp --dport 80 -j ACCEPT -m comment --comment \"Allow insecure HTTP\"" - yield "-A INPUT -p tcp --dport 443 -j ACCEPT -m comment --comment \"Allow HTTPS / OpenVPN TCP\"" - yield "-A INPUT -p tcp --dport 8443 -j ACCEPT -m comment --comment \"Allow mutually authenticated HTTPS\"" - yield "-A INPUT -p udp --dport 1194 -j ACCEPT -m comment --comment \"Allow OpenVPN UDP\"" - yield "-A INPUT -p udp --dport 500 -j ACCEPT -m comment --comment \"Allow IPsec IKE\"" - yield "-A INPUT -p udp --dport 4500 -j ACCEPT -m comment --comment \"Allow IPsec NAT traversal\"" - if const.REPLICAS: - yield "-A INPUT -p tcp --dport 27017 -j ACCEPT -m set --match-set ipset4-mongo-replicas src -m comment --comment \"Allow MongoDB internode\"" - yield "-A INPUT -p tcp --dport 9090 -j ACCEPT -m set --match-set ipset4-prometheus-subnets src -m comment --comment \"Allow Prometheus\"" - yield "-A INPUT -j INBOUND_BLOCKED" - - yield ":FORWARD DROP [0:0]" - yield "-A FORWARD -i tun0 -j INBOUND_CLIENT -m comment --comment \"Inbound traffic from OpenVPN UDP clients\"" - yield "-A FORWARD -i tun1 -j INBOUND_CLIENT -m comment --comment \"Inbound traffic from OpenVPN TCP clients\"" - yield "-A FORWARD -m policy --dir in --pol ipsec -j INBOUND_CLIENT -m comment --comment \"Inbound traffic from IPSec clients\"" - yield "-A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j OUTBOUND_CLIENT -m comment --comment \"Outbound traffic to clients\"" - yield "-A FORWARD -j %s -m comment --comment \"Default policy\"" % default_policy - - yield ":OUTPUT DROP [0:0]" - yield "-A OUTPUT -j ACCEPT" - yield "COMMIT" - - yield "*nat" - yield ":PREROUTING ACCEPT [0:0]" - yield ":INPUT ACCEPT [0:0]" - yield ":OUTPUT ACCEPT [0:0]" - yield ":POSTROUTING ACCEPT [0:0]" - if not const.DISABLE_MASQUERADE: - yield "-A POSTROUTING -j MASQUERADE" - yield "COMMIT" - - with open("/tmp/rules4", "w") as fh: - for line in g(): - fh.write(line) - fh.write("\n") - - 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": [ - {"_id": index, "host": "%s:27017" % hostname} for index, hostname in enumerate(const.REPLICAS)]} - print("Provisioning MongoDB replicaset: %s" % repr(config)) - try: - c.admin.command("replSetInitiate", config) - except pymongo.errors.OperationFailure: - print("Looks like it's already initialized") - pass # Expand variables distinguished_name = cn_to_dn(const.AUTHORITY_COMMON_NAME) @@ -432,9 +357,22 @@ def pinecone_provision(): const.SELF_KEY_PATH )) + 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": [ + {"_id": index, "host": "%s:27017" % hostname} for index, hostname in enumerate(const.REPLICAS)]} + print("Provisioning MongoDB replicaset: %s" % repr(config)) + try: + c.admin.command("replSetInitiate", config) + except pymongo.errors.OperationFailure: + print("Looks like it's already initialized") + pass + # TODO: use this task to send notification emails maybe? click.echo("Finished starting up") - sleep(86400) + sleep(999999999) @click.command("backend", help="Serve main backend") @@ -572,6 +510,8 @@ entry_point.add_command(pinecone_test) entry_point.add_command(pinecone_log) entry_point.add_command(pinecone_provision) entry_point.add_command(pinecone_session) +entry_point.add_command(pinecone_disable) +entry_point.add_command(pinecone_enable) if __name__ == "__main__": entry_point() diff --git a/pinecrypt/server/const.py b/pinecrypt/server/const.py index 4a7000d..8458369 100644 --- a/pinecrypt/server/const.py +++ b/pinecrypt/server/const.py @@ -30,9 +30,8 @@ IMAGE_BUILDER_PROFILES = [] SERVICE_PROTOCOLS = ["ikev2", "openvpn"] MONGO_URI = os.getenv("MONGO_URI") -REPLICAS = os.getenv("REPLICAS") +REPLICAS = [j for j in os.getenv("REPLICAS", "").split(",") if j] if REPLICAS: - REPLICAS = REPLICAS.split(",") if MONGO_URI: raise ValueError("Simultanously specifying MONGO_URI and REPLICAS doesn't make sense") MONGO_URI = "mongodb://%s/default?replicaSet=rs0" % (",".join(["%s:27017" % j for j in REPLICAS])) @@ -147,7 +146,6 @@ CRL_SUBNETS = getenv_subnets("AUTH_CRL_SUBNETS", "0.0.0.0/0 ::/0") OVERWRITE_SUBNETS = getenv_subnets("AUTH_OVERWRITE_SUBNETS", "") MACHINE_ENROLLMENT_SUBNETS = getenv_subnets("AUTH_MACHINE_ENROLLMENT_SUBNETS", "0.0.0.0/0 ::/0") KERBEROS_SUBNETS = getenv_subnets("AUTH_KERBEROS_SUBNETS", "") -PROMETHEUS_SUBNETS = getenv_subnets("PROMETHEUS_SUBNETS", "") BOOTSTRAP_TEMPLATE = "" USER_ENROLLMENT_ALLOWED = True @@ -176,6 +174,3 @@ SESSION_COOKIE = "sha512brownies" SESSION_AGE = 3600 SECRET_STORAGE = getenv_in("SECRET_STORAGE", "fs", "db") - -DISABLE_FIREWALL = os.getenv("DISABLE_FIREWALL") -DISABLE_MASQUERADE = os.getenv("DISABLE_MASQUERADE")