Ejemplo n.º 1
0
def setup():
    try:
        conn = get_db("postgres")
        conn.autocommit = True

        cursor = conn.cursor()
        cursor.execute(
            "SELECT 1 FROM pg_database WHERE datname = 'ambassador'")
        results = cursor.fetchall()

        if not results:
            cursor.execute("CREATE DATABASE ambassador")

        conn.close()
    except pg8000.Error as e:
        return RichStatus.fromError("no ambassador database in setup: %s" % e)

    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()
        cursor.execute(AMBASSADOR_TABLE_SQL)
        cursor.execute(PRINCIPAL_TABLE_SQL)
        conn.commit()
        conn.close()
    except pg8000.Error as e:
        return RichStatus.fromError("no data tables in setup: %s" % e)

    return RichStatus.OK()
Ejemplo n.º 2
0
    def generate_envoy_config(self,
                              template=None,
                              template_dir=None,
                              **kwargs):
        # Finally! Render the template to JSON...
        envoy_json = self.to_json(template=template, template_dir=template_dir)

        # We used to use the JSON parser as a final sanity check here. That caused
        # Forge some issues, so it's turned off for now.

        # rc = RichStatus.fromError("impossible")

        # # ...and use the JSON parser as a final sanity check.
        # try:
        #     obj = json.loads(envoy_json)
        #     rc = RichStatus.OK(msg="Envoy configuration OK", envoy_config=obj)
        # except json.decoder.JSONDecodeError as e:
        #     rc = RichStatus.fromError("Invalid Envoy configuration: %s" % str(e),
        #                               raw=envoy_json, exception=e)

        # Go ahead and report that we generated an Envoy config, if we can.
        scout_result = AmbassadorConfig.scout_report(action="config",
                                                     result=True,
                                                     generated=True,
                                                     **kwargs)

        rc = RichStatus.OK(envoy_config=envoy_json, scout_result=scout_result)

        self.logger.debug("Scout reports %s" % json.dumps(rc.scout_result))

        return rc
Ejemplo n.º 3
0
    def _fetch_consumer_basics(self, where):
        if not self:
            return self.status

        try:
            sql = "SELECT consumer_id, username, fullname, shortname FROM consumers WHERE %s" % where.sql

            self.cursor.execute(sql, where.keys)

            if self.cursor.rowcount == 0:
                return RichStatus.fromError("consumer %s not found" % where.hr)

            if self.cursor.rowcount > 1:
                return RichStatus.fromError(
                    "consumer %s matched more than one entry?" % where.hr)

            # We know there's exactly one consumer match. Good.

            consumer_id, username, fullname, shortname = self.cursor.fetchone()

            return RichStatus.OK(consumer_id=consumer_id,
                                 username=username,
                                 fullname=fullname,
                                 shortname=shortname)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "fetch_consumer_basics %s: could not fetch info: %s" %
                (where.hr, e))
Ejemplo n.º 4
0
    def __init__(self):
        pg8000.paramstyle = 'named'

        self.status = RichStatus.OK()

        # Make sure we have tables and such.
        #
        # All of these functions update self.status if something goes wrong, and they're
        # no-ops if not self.status.

        self.conn = self._get_connection()
        logging.info("storage_postgres: conn %sset, status %s" %
                     ("NOT " if not self.conn else "", self.status))

        # Get a cursor and verify our database.
        self.cursor = self._get_cursor()
        self._verify_database()

        # Switch autocommit off...
        self._autocommit(False)

        # ...grab a new cursor...
        self.cursor = self._get_cursor()

        # ...and make sure our tables are OK.
        self._verify_tables()
Ejemplo n.º 5
0
    def store_consumer(self, consumer_id, username, fullname, shortname,
                       modules):
        if not self:
            return self.status

        try:
            rc = self._store_consumer_basics(consumer_id, username, fullname,
                                             shortname)

            if not rc:
                self.conn.rollback()
                return rc

            for module_name in modules.keys():
                rc = self._store_consumer_module(consumer_id, module_name,
                                                 modules[module_name])

                if not rc:
                    self.conn.rollback()
                    return rc

            self.conn.commit()
            return RichStatus.OK(consumer_id=consumer_id)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "store_consumer %s: could not store consumer: %s" %
                (consumer_id, e))
Ejemplo n.º 6
0
    def delete_consumer(self, consumer_id):
        if not self:
            return self.status

        try:
            # Have to delete modules first since it holds a foreign key
            rc = self._delete_consumer_modules(consumer_id)

            if not rc:
                self.conn.rollback()
                return rc

            modules_deleted = rc.modules_deleted

            rc = self._delete_consumer_basics(consumer_id)

            if not rc:
                self.conn.rollback()
                return rc

            self.conn.commit()
            return RichStatus.OK(consumer_id=consumer_id,
                                 deleted=rc.deleted,
                                 modules_deleted=modules_deleted)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "delete_consumer %s: could not delete consumer: %s" %
                (consumer_id, e))
Ejemplo n.º 7
0
def getIncomingJSON(req, *needed):
    """
    Pull incoming arguments from JSON into a RichStatus. 'needed' specifies
    keys that are mandatory, but _all keys are converted_, so for any optional
    things, just check if the key is present in the returned RichStatus.

    If any 'needed' keys are missing, returns a False RichStatus including error
    text. If all 'needed' keys are present, returns a True RichStatus with elements
    copied from the input.
    """

    try:
        incoming = req.get_json()
    except Exception as e:
        return RichStatus.fromError("invalid JSON: %s" % e)

    logging.debug("getIncomingJSON: %s" % incoming)

    if not incoming:
        incoming = {}

    missing = []

    for key in needed:
        if key not in incoming:
            missing.append(key)

    if missing:
        return RichStatus.fromError("Required fields missing: %s" %
                                    " ".join(missing))
    else:
        return RichStatus.OK(**incoming)
Ejemplo n.º 8
0
def new_config(envoy_base_config=None, envoy_tls_config=None,
               envoy_config_path=None, envoy_restarter_pid=None):
    if not envoy_base_config:
        envoy_base_config = app.envoy_base_config

    if not envoy_tls_config:
        envoy_tls_config = app.envoy_tls_config

    if not envoy_config_path:
        envoy_config_path = app.envoy_config_path

    if not envoy_restarter_pid:
        envoy_restarter_pid = app.envoy_restarter_pid

    config = EnvoyConfig(envoy_base_config, envoy_tls_config)

    rc = fetch_all_services()
    num_services = 0

    if rc and rc.services:
        num_services = len(rc.services)

        for service in rc.services:
            config.add_service(service['name'], service['prefix'])

    config.write_config(envoy_config_path)

    if envoy_restarter_pid > 0:
        os.kill(envoy_restarter_pid, signal.SIGHUP)

    return RichStatus.OK(count=num_services)
Ejemplo n.º 9
0
def handle_principal_post(req, name):
    try:
        rc = getIncomingJSON(req, 'fingerprint')

        logging.debug("handle_principal_post %s: got args %s" %
                      (name, rc.toDict()))

        if not rc:
            return rc

        fingerprint = rc.fingerprint

        logging.debug("handle_principal_post %s: fingerprint %s" %
                      (name, fingerprint))

        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute('INSERT INTO principals VALUES(:name, :fingerprint)',
                       locals())
        conn.commit()

        return RichStatus.OK(name=name)
    except pg8000.Error as e:
        return RichStatus.fromError("%s: could not save info: %s" % (name, e))
Ejemplo n.º 10
0
def handle_mapping_post(req, name):
    try:
        rc = getIncomingJSON(req, 'prefix', 'service')

        logging.debug("handle_mapping_post %s: got args %s" %
                      (name, rc.toDict()))

        if not rc:
            return rc

        prefix = rc.prefix
        service = rc.service
        rewrite = '/'

        if 'rewrite' in rc:
            rewrite = rc.rewrite

        logging.debug("handle_mapping_post %s: pfx %s => svc %s (rewrite %s)" %
                      (name, prefix, service, rewrite))

        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute(
            'INSERT INTO mappings VALUES(:name, :prefix, :service, :rewrite)',
            locals())
        conn.commit()

        app.reconfigurator.trigger()

        return RichStatus.OK(name=name)
    except pg8000.Error as e:
        return RichStatus.fromError("%s: could not save info: %s" % (name, e))
Ejemplo n.º 11
0
    def _fetch_mapping_basics(self, name):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                "SELECT prefix, service, rewrite FROM mappings WHERE name = :name",
                locals())

            if self.cursor.rowcount == 0:
                return RichStatus.fromError("mapping %s not found" % name)

            if self.cursor.rowcount > 1:
                return RichStatus.fromError(
                    "mapping %s matched more than one entry?" % name)

            # We know there's exactly one mapping match. Good.

            prefix, service, rewrite = self.cursor.fetchone()

            return RichStatus.OK(name=name,
                                 prefix=prefix,
                                 service=service,
                                 rewrite=rewrite)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "fetch_mapping_basics %s: could not fetch info: %s" %
                (name, e))
Ejemplo n.º 12
0
    def delete_mapping(self, name):
        if not self:
            return self.status

        try:
            # Have to delete modules first since it holds a foreign key
            rc = self._delete_mapping_modules(name)

            if not rc:
                self.conn.rollback()
                return rc

            modules_deleted = rc.modules_deleted

            rc = self._delete_mapping_basics(name)

            if not rc:
                self.conn.rollback()
                return rc

            self.conn.commit()
            return RichStatus.OK(name=name,
                                 deleted=rc.deleted,
                                 modules_deleted=modules_deleted)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "delete_mapping %s: could not delete mapping: %s" % (name, e))
Ejemplo n.º 13
0
def handle_service_get(req, name):
    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute("SELECT prefix FROM services WHERE name = :name", locals())
        [ prefix ] = cursor.fetchone()

        return RichStatus.OK(name=name, prefix=prefix)
    except pg8000.Error as e:
        return RichStatus.fromError("%s: could not fetch info: %s" % (name, e))
Ejemplo n.º 14
0
def handle_mappings_deprecated():
    if request.method == 'PUT':
        app.reconfigurator.trigger()
        return RichStatus.OK(
            msg="DEPRECATED: was reconfigure requested, now basically no-op")
    else:
        rc = handle_mapping_list(request)

        # XXX Hackery!
        rc.info['deprecated'] = 'use /ambassador/mapping (singular) instead'
        return rc
Ejemplo n.º 15
0
def handle_approved():
    rc = handle_principal_list(request)

    if rc:
        principals = [{
            "fingerprint_sha256": x['fingerprint']
        } for x in rc.principals]

        rc = RichStatus.OK(certificates=principals)

    return rc
Ejemplo n.º 16
0
def ambassador_stats():
    rc = handle_mapping_list(request)

    active_mapping_names = []

    if rc and rc.mappings:
        active_mapping_names = [x['name'] for x in rc.mappings]

    app.stats.update(active_mapping_names)

    return RichStatus.OK(stats=app.stats.stats)
Ejemplo n.º 17
0
def handle_principal_get(req, name):
    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute("SELECT fingerprint FROM principals WHERE name = :name",
                       locals())
        [fingerprint] = cursor.fetchone()

        return RichStatus.OK(name=name, fingerprint=fingerprint)
    except pg8000.Error as e:
        return RichStatus.fromError("%s: could not fetch info: %s" % (name, e))
Ejemplo n.º 18
0
def handle_service_del(req, name):
    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute("DELETE FROM services WHERE name = :name", locals())
        conn.commit()

        app.reconfigurator.trigger()

        return RichStatus.OK(name=name)
    except pg8000.Error as e:
        return RichStatus.fromError("%s: could not delete service: %s" % (name, e))
Ejemplo n.º 19
0
    def store_principal(self, name, fingerprint):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                'INSERT INTO principals VALUES(:name, :fingerprint)', locals())
            self.conn.commit()

            return RichStatus.OK(name=name)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "store_principal %s: could not save info: %s" % (name, e))
Ejemplo n.º 20
0
    def _delete_mapping_basics(self, name):
        if not self:
            return self.status

        try:
            self.cursor.execute("DELETE FROM mappings WHERE name = :name",
                                locals())
            deleted = self.cursor.rowcount

            return RichStatus.OK(name=name, deleted=deleted)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "delete_mapping %s: could not delete mapping info: %s" %
                (name, e))
Ejemplo n.º 21
0
    def fetch_all_modules(self):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                "SELECT name, config FROM modules ORDER BY name")

            modules = {name: config for name, config in self.cursor}

            return RichStatus.OK(modules=modules, count=len(modules.keys()))
        except pg8000.Error as e:
            return RichStatus.fromError(
                "fetch_all_modules: could not fetch info: %s" % e)
Ejemplo n.º 22
0
    def delete_module(self, name):
        if not self:
            return self.status

        try:
            self.cursor.execute("DELETE FROM modules WHERE name = :name",
                                locals())
            deleted = self.cursor.rowcount
            self.conn.commit()

            return RichStatus.OK(name=name, deleted=deleted)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "delete_module %s: could not delete module: %s" % (name, e))
Ejemplo n.º 23
0
def fetch_all_services():
    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute("SELECT name, prefix FROM services ORDER BY name, prefix")

        services = []

        for name, prefix in cursor:
            services.append({ 'name': name, 'prefix': prefix })

        return RichStatus.OK(services=services, count=len(services))
    except pg8000.Error as e:
        return RichStatus.fromError("services: could not fetch info: %s" % e)
Ejemplo n.º 24
0
    def _delete_consumer_basics(self, consumer_id):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                "DELETE FROM consumers WHERE consumer_id = :consumer_id",
                locals())
            deleted = self.cursor.rowcount

            return RichStatus.OK(consumer_id=consumer_id, deleted=deleted)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "delete_consumer %s: could not delete consumer info: %s" %
                (consumer_id, e))
Ejemplo n.º 25
0
    def generate_envoy_config(self, template=None, template_dir=None):
        # Finally! Render the template to JSON...
        envoy_json = self.to_json(template=template, template_dir=template_dir)
        return RichStatus.OK(msg="Envoy configuration OK",
                             envoy_config=envoy_json)
        #        rc = RichStatus.fromError("impossible")

        # ...and use the JSON parser as a final sanity check.
        #        try:
        #            obj = json.loads(envoy_json)
        #            rc = RichStatus.OK(msg="Envoy configuration OK", envoy_config=obj)
        #        except json.decoder.JSONDecodeError as e:
        #            rc = RichStatus.fromError("Invalid Envoy configuration: %s" % str(e),
        #                                      raw=envoy_json, exception=e)

        return rc
Ejemplo n.º 26
0
    def process_object(self, obj):
        obj_version = obj['apiVersion']
        obj_kind = obj['kind']
        obj_name = obj['name']

        # ...save the source info...
        source_key = "%s.%d" % (self.filename, self.ocount)
        self.sources[source_key] = {
            'kind': obj_kind,
            'version': obj_version,
            'name': obj_name,
            'filename': self.filename,
            'index': self.ocount,
            'yaml': yaml.safe_dump(obj, default_flow_style=False)
        }

        source_map = self.source_map.setdefault(self.filename, {})
        source_map[source_key] = True

        # OK. What is this thing?
        rc = self.validate_object(obj)

        if not rc:
            # Well that's no good.
            return rc

        # OK, so far so good. Grab the handler for this object type.
        handler_name = "handle_%s" % obj_kind.lower()
        handler = getattr(self, handler_name, None)

        if not handler:
            handler = self.save_object
            self.logger.warning("%s[%d]: no handler for %s, just saving" %
                                (self.filename, self.ocount, obj_kind))
        else:
            self.logger.debug("%s[%d]: handling %s..." %
                              (self.filename, self.ocount, obj_kind))

        try:
            handler(source_key, obj, obj_name, obj_kind, obj_version)
        except Exception as e:
            # Bzzzt.
            return RichStatus.fromError("could not process %s object: %s" %
                                        (obj_kind, e))

        # OK, all's well.
        return RichStatus.OK(msg="%s object processed successfully" % obj_kind)
Ejemplo n.º 27
0
def fetch_all_principals():
    try:
        conn = get_db("ambassador")
        cursor = conn.cursor()

        cursor.execute(
            "SELECT name, fingerprint FROM principals ORDER BY name, fingerprint"
        )

        principals = []

        for name, fingerprint in cursor:
            principals.append({'name': name, 'fingerprint': fingerprint})

        return RichStatus.OK(principals=principals, count=len(principals))
    except pg8000.Error as e:
        return RichStatus.fromError("principals: could not fetch info: %s" % e)
Ejemplo n.º 28
0
    def validate_object(self, obj):
        # Each object must be a dict, and must include "apiVersion"
        # and "type" at toplevel.

        if not isinstance(obj, collections.Mapping):
            return RichStatus.fromError("not a dictionary")

        if not (("apiVersion" in obj) and ("kind" in obj) and ("name" in obj)):
            return RichStatus.fromError("must have apiVersion, kind, and name")

        obj_version = obj['apiVersion']
        obj_kind = obj['kind']
        obj_name = obj['name']

        if obj_version.startswith("ambassador/"):
            obj_version = obj_version.split('/')[1]
        else:
            return RichStatus.fromError("apiVersion %s unsupported" %
                                        obj_version)

        schema_key = "%s-%s" % (obj_version, obj_kind)

        schema = self.schemas.get(schema_key, None)

        if not schema:
            schema_path = os.path.join(self.schema_dir_path, obj_version,
                                       "%s.schema" % obj_kind)

            try:
                schema = json.load(open(schema_path, "r"))
            except OSError:
                self.logger.debug("no schema at %s, skipping" % schema_path)
            except json.decoder.JSONDecodeError as e:
                self.logger.warning("corrupt schema at %s, skipping (%s)" %
                                    (schema_path, e))

        if schema:
            self.schemas[schema_key] = schema
            try:
                jsonschema.validate(obj, schema)
            except jsonschema.exceptions.ValidationError as e:
                return RichStatus.fromError("not a valid %s: %s" %
                                            (obj_kind, e))

        return RichStatus.OK(msg="valid %s" % obj_kind,
                             details=(obj_kind, obj_version, obj_name))
Ejemplo n.º 29
0
    def _store_mapping_basics(self, name, prefix, service, rewrite):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                '''
                INSERT INTO mappings VALUES(:name, :prefix, :service, :rewrite)
                    ON CONFLICT (name) DO UPDATE SET
                        name=EXCLUDED.name, prefix=EXCLUDED.prefix, 
                        service=EXCLUDED.service, rewrite=EXCLUDED.rewrite
            ''', locals())

            return RichStatus.OK(name=name)
        except pg8000.Error as e:
            return RichStatus.fromError(
                "store_mapping %s: could not save info: %s" % (name, e))
Ejemplo n.º 30
0
    def fetch_all_principals(self):
        if not self:
            return self.status

        try:
            self.cursor.execute(
                "SELECT name, fingerprint FROM principals ORDER BY name, fingerprint"
            )

            principals = []

            for name, fingerprint in self.cursor:
                principals.append({'name': name, 'fingerprint': fingerprint})

            return RichStatus.OK(principals=principals, count=len(principals))
        except pg8000.Error as e:
            return RichStatus.fromError(
                "fetch_all_principals: could not fetch info: %s" % e)