Commit 9f0b164a authored by Stavros-Anastasios Charismiadis's avatar Stavros-Anastasios Charismiadis
Browse files

Use server certificates for helper connect and publish interconnection

parent 44333de0
Loading
Loading
Loading
Loading
Loading
+44 −21
Original line number Diff line number Diff line
mongo: {
  'user': 'root',
  'password': 'example',
  'db': 'capif',
  'col': 'serviceapidescriptions',
  'certs_col': "certs",
  'capif_provider_col': "providerenrolmentdetails",
  'col_interconnected': "interconnected",
  'host': 'mongo',
  'port': "27017"
}
mongo:
  user: root
  password: example
  db: capif
  col: serviceapidescriptions
  certs_col: certs
  capif_provider_col: providerenrolmentdetails
  col_interconnected: interconnected
  col_capif_configuration: capif_configuration
  host: mongo
  port: 27017

monitoring: {
  "fluent_bit_host": fluent-bit,
  "fluent_bit_port": 24224,
  "opentelemetry_url": "otel-collector",
  "opentelemetry_port": "55680",
  "opentelemetry_max_queue_size": 8192,
  "opentelemetry_schedule_delay_millis": 20000,
  "opentelemetry_max_export_batch_size": 2048,
  "opentelemetry_export_timeout_millis": 60000
}
 No newline at end of file
monitoring:
  fluent_bit_host: fluent-bit
  fluent_bit_port: 24224
  opentelemetry_url: otel-collector
  opentelemetry_port: 55680
  opentelemetry_max_queue_size: 8192
  opentelemetry_schedule_delay_millis: 20000
  opentelemetry_max_export_batch_size: 2048
  opentelemetry_export_timeout_millis: 60000

ca_factory:
  url: !ENV ${VAULT_HOSTNAME}
  port: 8200
  token: dev-only-token
  verify: False

capif_configuration:
  config_description: Default CAPIF Configuration
  config_name: default
  config_version: "1.0"
  settings:
    acl_policy_settings:
      allowed_invocation_time_range_days: 365
      allowed_invocations_per_second: 10
      allowed_total_invocations: 5
    certificates_expiry:
      ttl_invoker_cert: 4300h
      ttl_provider_cert: 4300h
      ttl_superadmin_cert: 4300h
    security_method_priority:
      oauth: 1
      pki: 2
      psk: 3
 No newline at end of file
+32 −0
Original line number Diff line number Diff line
#!/bin/bash

VAULT_ADDR="http://$VAULT_HOSTNAME:$VAULT_PORT"
VAULT_TOKEN=$VAULT_ACCESS_TOKEN
CERTS_DIR=/usr/src/app/published_apis/certs
mkdir -p "$CERTS_DIR"
MAX_RETRIES=30; RETRY_DELAY=10; ATTEMPT=0
# 1) get this CCF's id from helper
CCF_ID=""
while [ -z "$CCF_ID" ] && [ $ATTEMPT -lt $MAX_RETRIES ]; do
    ATTEMPT=$((ATTEMPT+1))
    CCF_ID=$(curl -sS --max-time 10 "http://helper:8080/helper/api/getCcfId" | jq -r '.ccf_id // empty')
    [ -z "$CCF_ID" ] && sleep $RETRY_DELAY
done
[ -z "$CCF_ID" ] && echo "no ccf_id" && exit 1
# 2) pull the nginx server identity + CA from Vault (retry until nginx has stored it)
ATTEMPT=0
while [ $ATTEMPT -lt $MAX_RETRIES ]; do
    ATTEMPT=$((ATTEMPT+1))
    RESP=$(curl -s -k --header "X-Vault-Token: $VAULT_TOKEN" \
        --request GET "$VAULT_ADDR/v1/secret/data/capif/${CCF_ID}/nginx")
    CRT=$(echo "$RESP" | jq -r '.data.data.server_crt // empty')
    KEY=$(echo "$RESP" | jq -r '.data.data.server_key // empty')
    CA=$(echo  "$RESP" | jq -r '.data.data.ca // empty')
    if [ -n "$CRT" ] && [ -n "$KEY" ] && [ -n "$CA" ]; then
        printf '%s\n' "$CRT" > "$CERTS_DIR/server.crt"
        printf '%s\n' "$KEY" > "$CERTS_DIR/server.key"
        printf '%s\n' "$CA"  > "$CERTS_DIR/ca.crt"
        chmod 600 "$CERTS_DIR/server.key"
        break
    fi
    sleep $RETRY_DELAY
done

gunicorn -k uvicorn.workers.UvicornH11Worker --timeout 120 --bind 0.0.0.0:8080 \
         --chdir /usr/src/app/published_apis wsgi:app
+69 −14
Original line number Diff line number Diff line
import os

import yaml
import re


# pattern for global vars: look for ${word}
pattern = re.compile(r'.*?\${(\w+)}.*?')
loader = yaml.SafeLoader


def constructor_env_variables(loader, node):
    """
    Extracts the environment variable from the node's value
    :param yaml.Loader loader: the yaml loader
    :param node: the current node in the yaml
    :return: the parsed string that contains the value of the environment
    variable
    """
    value = loader.construct_scalar(node)
    match = pattern.findall(value)  # to find all env variables in line
    if match:
        full_value = value
        for g in match:
            full_value = full_value.replace(
                f'${{{g}}}', os.environ.get(g, g)
            )
        return full_value
    return value

def parse_config(path=None, data=None, tag='!ENV'):
    """
    Load a yaml configuration file and resolve any environment variables
    The environment variables must have !ENV before them and be in this format
    to be parsed: ${VAR_NAME}.
    E.g.:
    database:
        host: !ENV ${HOST}
        port: !ENV ${PORT}
    app:
        log_path: !ENV '/var/${LOG_PATH}'
        something_else: !ENV '${AWESOME_ENV_VAR}/var/${A_SECOND_AWESOME_VAR}'
    :param str path: the path to the yaml file
    :param str data: the yaml data itself as a stream
    :param str tag: the tag to look for
    :return: the dict configuration
    :rtype: dict[str, T]
    """

    # the tag will be used to mark where to start searching for the pattern
    # e.g. somekey: !ENV somestring${MYENVVAR}blah blah blah
    loader.add_implicit_resolver(tag, pattern, None)
    loader.add_constructor(tag, constructor_env_variables)

    if path:
        with open(path) as conf_data:
            return yaml.load(conf_data, Loader=loader)
    elif data:
        return yaml.load(data, Loader=loader)
    else:
        raise ValueError('Either a path or data should be defined as input')

#Config class to get config
class Config:
    def __init__(self):
@@ -13,9 +70,7 @@ class Config:
        stamp = os.stat(self.file).st_mtime
        if stamp != self.cached:
            self.cached = stamp
			f = open(self.file)
			self.my_config = yaml.safe_load(f)
			f.close()
            self.my_config = parse_config(path=self.file)

    def get_config(self):
        return self.my_config
+35 −12
Original line number Diff line number Diff line
@@ -15,6 +15,9 @@ from .resources import Resource
from .responses import (bad_request_error, forbidden_error,
                        internal_server_error, make_response, not_found_error,
                        unauthorized_error)
import requests
import json
import encoder

TOTAL_FEATURES = 10
SUPPORTED_FEATURES_HEX = "120"
@@ -182,23 +185,43 @@ class PublishServiceOperations(Resource):
                return bad_request_error(
                    detail="Set apiStatus with apiStatusMonitoring feature inactive at supportedFeatures if not allowed",
                    cause="apiStatus can't be set if apiStatusMonitoring is inactive",
                    invalid_params=[{"param": "apiStatus", "reason": "defined but apiStatusMoniroting feature not active"}]
                    invalid_params=[{"param": "apiStatus", "reason": "defined but apiStatusMonitoring feature not active"}]
                )

            # Here code to check publish on the interconnected CCF
            # 1. Check shareableInfo 
            # 2. If true then iterate through capifProvDoms
            # 3. For each domain, check if there is record in the "interconnected" table
            # 4. If yes, make a publish request with CCF... certificate
            # 5. When get an ACK that API was published then proceed

            if serviceapidescription.shareable_info.is_sharable:
            if serviceapidescription.shareable_info.is_shareable: 
                interconnected_col = self.db.get_col_by_name(self.db.interconnected)
                # 2. Iterate through capifProvDoms
                for dom in serviceapidescription.shareable_info.capif_prov_doms:   
                    # 3. For each domain, check if there is record in the "interconnected" table
                    interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom}) 
                    # 4. If yes, make a publish request with CCF certificate
                    if interconnected_ccf: 
                        current_app.logger.debug("CCF interconnected and API can be shared: {}".format(dom))

                        config_col = self.db.get_col_by_name(self.db.capif_configuration)
                        config = config_col.find_one({}, {"_id": 0})
                        ccf_id = config['ccf_id']
                        serviceapidescription_dict['ccf_id'] = ccf_id
                        serviceapidescription_dict['shareable_info'] = {"is_shareable": False}

                        url = 'https://{}/published-apis/v1/{}/service-apis'.format(dom, ccf_id)

                        current_app.logger.debug("{}".format(url))

                        headers = {
                            'accept': 'application/json',
                            'Content-Type': 'application/json'
                        }

                        current_app.logger.debug("Add variables to request")

                        response = requests.request("POST", url, headers=headers, data=json.dumps(clean_n_camel_case(serviceapidescription_dict), cls=encoder.CustomJSONEncoder), cert=('certs/superadmin.crt', 'certs/superadmin.key'), verify='certs/ca_root.crt')

                        if not (response.status_code == 201 or response.status_code == 200):
                            rec['shareable_info']['capif_prov_doms'].remove(dom)


            mycol.insert_one(rec)

            self.auth_manager.add_auth_service(api_id, apf_id)
+9 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ class MongoDatabse():
        self.capif_provider_col = self.config['mongo']['capif_provider_col']
        self.certs_col = self.config['mongo']['certs_col']
        self.interconnected = self.config['mongo']['col_interconnected']
        self.capif_configuration = self.config['mongo']['col_capif_configuration']

    def get_col_by_name(self, name):
        return self.db[name].with_options(codec_options=CodecOptions(tz_aware=True))
@@ -51,3 +52,11 @@ class MongoDatabse():
    def close_connection(self):
        if self.db.client:
            self.db.client.close()


_singleton = None
def get_mongo():
    global _singleton
    if _singleton is None:
        _singleton = MongoDatabse()
    return _singleton
 No newline at end of file
Loading