From 2640aec6d872cd73134653bc7a426ac61c3c7755 Mon Sep 17 00:00:00 2001 From: Jeffrey Townsend Date: Thu, 21 Apr 2016 01:00:04 +0000 Subject: [PATCH] ONL-CONFIG datastore and PKI infrastructure. --- packages/base/all/boot.d/src/51.pki | 6 + .../src/etc/onl/config/csr.yml | 7 + packages/base/all/vendor-config-onl/PKG.yml | 1 + .../base/all/vendor-config-onl/src/bin/pki | 3 + .../src/python/onl/mounts/__init__.py | 156 +++++++++++++++++- .../src/python/onl/pki/__init__.py | 129 +++++++++++++++ 6 files changed, 301 insertions(+), 1 deletion(-) create mode 100755 packages/base/all/boot.d/src/51.pki create mode 100644 packages/base/all/initrds/loader-initrd-files/src/etc/onl/config/csr.yml create mode 100755 packages/base/all/vendor-config-onl/src/bin/pki create mode 100755 packages/base/all/vendor-config-onl/src/python/onl/pki/__init__.py diff --git a/packages/base/all/boot.d/src/51.pki b/packages/base/all/boot.d/src/51.pki new file mode 100755 index 00000000..5752d4e9 --- /dev/null +++ b/packages/base/all/boot.d/src/51.pki @@ -0,0 +1,6 @@ +#!/bin/sh +if [ -f /usr/bin/pki ]; then + /usr/bin/pki --init +fi + + diff --git a/packages/base/all/initrds/loader-initrd-files/src/etc/onl/config/csr.yml b/packages/base/all/initrds/loader-initrd-files/src/etc/onl/config/csr.yml new file mode 100644 index 00000000..c64b4847 --- /dev/null +++ b/packages/base/all/initrds/loader-initrd-files/src/etc/onl/config/csr.yml @@ -0,0 +1,7 @@ +C: US +ST: CA +O: Open Compute Project +localityName: Santa Clara +commonName: Networking +organizationalUnitName: Open Network Linux +emailAddress: support@bigswitch.com diff --git a/packages/base/all/vendor-config-onl/PKG.yml b/packages/base/all/vendor-config-onl/PKG.yml index 794948a1..5e5a397a 100644 --- a/packages/base/all/vendor-config-onl/PKG.yml +++ b/packages/base/all/vendor-config-onl/PKG.yml @@ -25,5 +25,6 @@ packages: files: src/python/onl : /usr/lib/python2.7/onl src/bin/initmounts : /bin/initmounts + src/bin/pki : /sbin/pki changelog: Changes diff --git a/packages/base/all/vendor-config-onl/src/bin/pki b/packages/base/all/vendor-config-onl/src/bin/pki new file mode 100755 index 00000000..eb5105a8 --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/bin/pki @@ -0,0 +1,3 @@ +#!/usr/bin/python +from onl.pki import OnlPKI +OnlPKI.main() diff --git a/packages/base/all/vendor-config-onl/src/python/onl/mounts/__init__.py b/packages/base/all/vendor-config-onl/src/python/onl/mounts/__init__.py index eaaea1ff..e5d8c9d0 100755 --- a/packages/base/all/vendor-config-onl/src/python/onl/mounts/__init__.py +++ b/packages/base/all/vendor-config-onl/src/python/onl/mounts/__init__.py @@ -7,6 +7,8 @@ import logging import time import json import yaml +import tempfile +import shutil class OnlMountManager(object): def __init__(self, mdata, logger): @@ -88,7 +90,7 @@ class OnlMountManager(object): out = subprocess.check_output("umount %s" % v['device'], shell=True) self.logger.info("%s now unmounted." % (k)) except subprocess.CalledProcessError,e: - self.logger.error("Could not unmount %s @ %s: " % (k, v['device'], e.output)) + self.logger.error("Could not unmount %s @ %s: %s" % (k, v['device'], e.output)) continue # # FS Checks @@ -151,3 +153,155 @@ class OnlMountManager(object): mm.mountall(all_=True, fsck=False) else: mm.mountall() + + + +############################################################ +# +# Fix this stuff +# +############################################################ +class ServiceMixin(object): + + def _execute(self, cmd, root=False, ex=True): + self.logger.debug("Executing: %s" % cmd) + if root is True and os.getuid() != 0: + cmd = "sudo " + cmd + try: + subprocess.check_call(cmd, shell=True) + except Exception, e: + if ex: + self.logger.error("Command failed: %s" % e) + raise + else: + return e.returncode + + def _raise(self, msg, klass): + self.logger.critical(msg) + raise klass(msg) + +class DataMount(ServiceMixin): + + def __init__(self, partition, logger=None): + self.partition = partition + self.logger = logger + + self.mountpoint = None + self.mounted = False + + if os.path.isabs(partition) and not os.path.exists(partition): + # Implicitly a bind mount. It may not exist yet, so create it + os.makedirs(partition) + + if os.path.exists(partition): + # Bind mount + self.device = None + else: + self.device = subprocess.check_output("blkid | grep %s | awk '{print $1}' | tr -d ':'" % self.partition, shell=True).strip() + if self.device is None or len(self.device) is 0: + self._raise("Data partition %s does not exist." % self.partition, + RuntimeError) + self.logger.debug("device is %s" % self.device) + + def _mount(self): + if self.device: + self._execute("mount %s %s" % (self.device, self.mountdir()), root=True) + else: + self._execute("mount --bind %s %s" % (self.partition, + self.mountdir()), root=True) + self.mounted = True + + def _umount(self): + mounted, self.mounted = self.mounted, False + if mounted: + self._execute("umount %s" % self.mountpoint, root=True) + mountpoint, self.mountpoint = self.mountpoint, None + if mountpoint and os.path.exists(mountpoint): + self.logger.debug("+ /bin/rmdir %s", mountpoint) + os.rmdir(mountpoint) + + def __enter__(self): + self._mount() + return self + + def __exit__(self, type_, value, traceback): + self._umount() + + def mountdir(self): + if self.mountpoint is None: + self.mountpoint = tempfile.mkdtemp(prefix="pki-", suffix=".d") + self.logger.debug("mountpoint is %s" % self.mountpoint) + return self.mountpoint + + +class OnlDataStore(ServiceMixin): + + # Data partition containing the persistant store + DATA_PARTITION='ONL-CONFIG' + + # Persistant directory on DATA_PARTITION + P_DIR=None + + # Runtime directory in the root filesystem + R_DIR=None + + def __init__(self, logger=None): + + if logger is None: + logging.basicConfig() + logger = logging.getLogger(str(self.__class__)) + logger.setLevel(logging.WARN) + + self.logger = logger + + self.mount = DataMount(self.DATA_PARTITION, logger=self.logger) + + if self.P_DIR is None: + raise AttributeError("P_DIR must be set in the derived class.") + + if self.R_DIR is None: + raise ValueError("R_DIR must be set in the derived class.") + + # The R_DIR is accessed here + self.r_dir = self.R_DIR + + self.logger.debug("persistant dir: %s" % self.p_dir) + self.logger.debug(" runtime dir: %s" % self.r_dir) + + @property + def p_dir(self): + return os.path.join(self.mount.mountdir(), self.P_DIR) + + def _sync_dir(self, src, dst): + self.logger.debug("Syncing store from %s -> %s" % (src, dst)) + if os.path.exists(dst): + shutil.rmtree(dst) + + if not os.path.exists(src): + os.makedirs(src) + + shutil.copytree(src, dst) + + def init_runtime(self): + with self.mount: + self._sync_dir(self.p_dir, self.r_dir) + + def commit_runtime(self): + with self.mount: + self._sync_dir(self.r_dir, self.p_dir) + + def diff(self): + with self.mount: + rv = self._execute("diff -rNq %s %s" % (self.p_dir, self.r_dir), ex=False) + return rv == 0 + + def ls(self): + with self.mount: + self._execute("cd %s && find ." % (self.p_dir)) + + def rm(self, filename): + with self.mount: + os.unlink(os.path.join(self.p_dir, filename)) + os.unlink(os.path.join(r_dir, filename)) + + diff --git a/packages/base/all/vendor-config-onl/src/python/onl/pki/__init__.py b/packages/base/all/vendor-config-onl/src/python/onl/pki/__init__.py new file mode 100755 index 00000000..2f8dd157 --- /dev/null +++ b/packages/base/all/vendor-config-onl/src/python/onl/pki/__init__.py @@ -0,0 +1,129 @@ +#!/usr/bin/python +############################################################ +# +# ONL PKI Management +# +############################################################ +import sys +import os +import argparse +import logging +import tempfile +import shutil +import subprocess +import tempfile +import yaml + +from onl.mounts import OnlDataStore + +class OnlPKI(OnlDataStore): + P_DIR='private' + R_DIR='/private' + + PRIVATE_KEY='key.pem' + CERTIFICATE='certificate' + + KLEN=2048 + CDAYS=3650 + + CSR_FILE='/etc/onl/config/csr.yml' + + def __init__(self, logger=None): + OnlDataStore.__init__(self, logger) + self.kpath = os.path.join(self.R_DIR, self.PRIVATE_KEY) + self.cpath = os.path.join(self.R_DIR, self.CERTIFICATE) + + def init_cert(self, force=False): + if not os.path.exists(self.cpath) or force: + self.logger.info("Generating self-signed certificate...") + + # + # The csr.yml file allows the system integrator to customize + # the fields for the certificate. + # + fdict = {} + try: + fdict = yaml.load(open(self.CSR_FILE)) + except Exception, e: + self.logger.error(e); + + csr = tempfile.NamedTemporaryFile(prefix="pki-", suffix=".csr", delete=False) + csr.close() + + fields = [ "%s=%s" % (k, v) for k,v in fdict.iteritems() ] + subject = "/" + "/".join(fields) + self.logger.debug("Subject: '%s'", subject) + self.logger.debug("CSR: %s", csr.name) + self._execute("""openssl req -new -batch -subj "%s" -key %s -out %s""" % ( + subject, self.kpath, csr.name)) + self._execute("""openssl x509 -req -days %s -in %s -signkey %s -out %s""" % ( + self.CDAYS, + csr.name, self.kpath, self.cpath)) + os.unlink(csr.name) + else: + self.logger.info("Using existing certificate.") + + def init_key(self, force=False): + if not os.path.exists(self.kpath) or force: + self.logger.info("Generating private key...") + cmd = "openssl genrsa -out %s %s" % (self.kpath, self.KLEN) + self._execute(cmd) + self.init_cert(force=True) + else: + self.logger.info("Using existing private key.") + + def init(self, force=False): + self.init_key(force=force) + self.init_cert(force=force) + self.commit_runtime() + + + @staticmethod + def main(): + ap = argparse.ArgumentParser(description="ONL PKI Management") + ap.add_argument("--init", action='store_true', help="Initialize /private and PKI files if necessary.") + ap.add_argument("--regen-cert", action='store_true', help="Regenerate certificate.") + ap.add_argument("--force", "-f", action='store_true', help="Force regeneration of the key and certificate during initialization (--init)") + ap.add_argument("--commit", action='store_true', help="Commit the runtime /private directory to the persistant storage.") + ap.add_argument("--ls", action='store_true', help="List contents of the peristant directory.") + ap.add_argument("--quiet", "-q", action='store_true', help="Quiet output.") + ap.add_argument("--verbose", "-v", action='store_true', help="Verbose output.") + ap.add_argument("--part", help='Override Data Partition (testing only).') + ap.add_argument("--rd", help='Override /private runtime directory (testing only)') + + ops = ap.parse_args() + + logging.basicConfig() + logger = logging.getLogger("PKI") + + if ops.verbose: + logger.setLevel(logging.DEBUG) + elif ops.quiet: + logger.setLevel(logging.WARNING) + else: + logger.setLevel(logging.INFO) + + if ops.part: + OnlPKI.DATA_PARTITION=ops.part + + if ops.rd: + OnlPKI.R_DIR=ops.rd + + pki = OnlPKI(logger) + + if ops.init: + pki.init_runtime() + pki.init_key(force=ops.force) + pki.init_cert(force=ops.force) + pki.commit_runtime() + + elif ops.regen_cert: + pki.init_cert(force=True) + pki.commit_runtime() + + elif ops.commit: + pki.commit_runtime() + elif ops.ls: + pki.ls() + else: + pki.diff()