Commit 74dc0ef4 authored by Jorge Moratinos's avatar Jorge Moratinos
Browse files

PSK and PKI support for interconnected APIS

parent 9ac7567b
Loading
Loading
Loading
Loading
Loading
+3 −2
Changes for services/TS29222_CAPIF_Publish_Service_API/published_apis/core/serviceapidescriptions.py: 3 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -448,7 +448,7 @@ class PublishServiceOperations(Resource):
                "Interconnection: service api {} updated on {}".format(shared_api_name, dom))

            # Update mapping between local and remote API IDs
            remote_api_id = response.json().get("api_id")
            remote_api_id = response.json().get("apiId")
            self.update_api_remote_id_map(dom, local_api_id, remote_api_id)
            # End of update api remote ID map
        return None
@@ -509,7 +509,7 @@ class PublishServiceOperations(Resource):
                    service_api.get("api_name"), dom))

            # Update mapping between local and remote API IDs
            remote_api_id = response.json().get("api_id")
            remote_api_id = response.json().get("apiId")
            self.update_api_remote_id_map(dom, local_api_id, remote_api_id)
            # End of update api remote ID map

@@ -983,6 +983,7 @@ class PublishServiceOperations(Resource):

    def update_api_remote_id_map(self, dom, local_api_id, remote_api_id):
        """Update the mapping between local and remote API IDs for a given CAPIF domain."""
        current_app.logger.debug("Interconnection: Update API remote ID map for domain {}, local_id {}, remote_id {}".format(dom, local_api_id, remote_api_id))
        # Update mapping between local and remote API IDs
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom})
+142 −8
Changes for services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py: 142 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -51,17 +51,18 @@ def return_negotiated_supp_feat_dict(supp_feat):
class SecurityOperations(Resource):

    def __check_invoker(self, api_invoker_id):
        invokers_col = self.db.get_col_by_name(self.db.capif_invokers)

        current_app.logger.debug(
            "Checking api invoker with id: " + api_invoker_id)
        invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id})
        invoker = self.__retrieve_invoker_from_db(api_invoker_id)
        if invoker is None:
            current_app.logger.warning("Invoker not found")
            return not_found_error(detail="Invoker not found", cause="API Invoker not exists or invalid ID")

        return None

    def __retrieve_invoker_from_db(self, api_invoker_id):
        invokers_col = self.db.get_col_by_name(self.db.capif_invokers)
        invoker = invokers_col.find_one({"api_invoker_id": api_invoker_id})
        return invoker

    def __check_scope(self, scope, security_context):

        try:
@@ -166,10 +167,18 @@ class SecurityOperations(Resource):

            current_app.logger.debug(
                "Obtainig security context with id: " + api_invoker_id)
            result = self.__check_invoker(api_invoker_id)
            if result != None:
                return result
            
            invoker = self.__retrieve_invoker_from_db(api_invoker_id)
            if invoker is None:
                current_app.logger.warning("Invoker not found Locally")
                ccf_id = self.get_remote_invoker_ccf(api_invoker_id)
                if ccf_id:
                    current_app.logger.info(f"Invoker found in remote CCF: {ccf_id}")
                    return self.request_security_information_to_other_ccf(api_invoker_id, ccf_id, authentication_info, authorization_info)
                else:
                    current_app.logger.warning("Invoker not found in any remote CCFs")
                    return not_found_error(detail="Invoker not found", cause="API Invoker not exists or invalid ID")

            services_security_object = mycol.find_one({"api_invoker_id": api_invoker_id}, {
                                                        "_id": 0, "api_invoker_id": 0})

@@ -177,10 +186,22 @@ class SecurityOperations(Resource):
                current_app.logger.warning("Not found security context")
                return not_found_error(detail=security_context_not_found_detail, cause=api_invoker_no_context_cause)

            # Check if invoker belongs to this CCF
            invoker = self.__retrieve_invoker_from_db(api_invoker_id)
            if invoker is None:
                current_app.logger.warning("Invoker not found on this CCF")
                # TODO -> Comprobar si el invoker está registrado en algún CCF remoto.
                remote_ccf = self.get_remote_invoker_ccf(api_invoker_id)
                if remote_ccf:
                    current_app.logger.info(f"Invoker found in remote CCFs: {remote_ccf}")
                else:
                    current_app.logger.warning("Invoker not found in any remote CCFs")

            for security_info_obj in services_security_object['security_info']:
                if security_info_obj.get('sel_security_method') == "PKI":
                    current_app.logger.debug("PKI security method selected")
                    if authentication_info:
                        current_app.logger.debug("Authentication info requested, reading CA certificate")
                        # Read the CA certificate from the file
                        with open("/usr/src/app/capif_security/certs/ca.crt", "rb") as key_file:
                            key_data = key_file.read()
@@ -188,13 +209,18 @@ class SecurityOperations(Resource):
                        key_data = key_data.decode('utf-8')
                        # Add the CA certificate to the authentication_info
                        security_info_obj['authentication_info'] = key_data
                        current_app.logger.debug("CA certificate added to authentication_info: {}".format(security_info_obj))
                    else:
                        # If authentication_info is not needed, remove the key_data
                        current_app.logger.debug("Authentication info not requested, removing from security_info_obj")
                        del security_info_obj['authentication_info']

                    if authorization_info:
                        current_app.logger.debug("Authorization info requested")
                        security_info_obj['authorization_info'] = security_info_obj.get('authorization_info', "")
                        current_app.logger.debug("Authorization info added to security_info_obj: {}".format(security_info_obj))
                    else:
                        current_app.logger.debug("Authorization info not requested, removing from security_info_obj")
                        # If authorization_info is not needed, remove the key_data
                        del security_info_obj['authorization_info']

@@ -237,6 +263,10 @@ class SecurityOperations(Resource):
                    current_app.logger.warning("Bad format security method")
                    return bad_request_error(detail="Bad format security method", cause="Bad format security method", invalid_params=[{"param": "securityMethod", "reason": "Bad format security method"}])

                current_app.logger.debug("Processed security info object: {}".format(security_info_obj))

            current_app.logger.debug("Processed all security info objects")
            current_app.logger.debug("services_security_object: {}".format(services_security_object))

            properyly_json = json.dumps(
                services_security_object, default=json_util.default)
@@ -288,6 +318,10 @@ class SecurityOperations(Resource):
                    publish_ops.publish_message("acls-messages", "create-acl:"+str(
                        api_invoker_id)+":"+str(service_instance.api_id)+":"+str(service_instance.aef_id))


                # Add invoker to list of api_invokers_id at interconnected CCF
                self.add_invoker_api_map(api_invoker_id, g.cert_cn)

                res = make_response(object=serialize_clean_camel_case(service_security), status=201)
                res.headers['Location'] = f"https://{os.getenv("CAPIF_HOSTNAME")}/capif-security/v1/trustedInvokers/{str(api_invoker_id)}"
                return res
@@ -547,6 +581,18 @@ class SecurityOperations(Resource):

            current_app.logger.debug("Removing security context")

            invoker = self.__retrieve_invoker_from_db(api_invoker_id)
            if invoker is None:
                current_app.logger.warning("Invoker not found Locally")
                ccf_id = self.get_remote_invoker_ccf(api_invoker_id)
                if ccf_id:
                    current_app.logger.info(f"Invoker found in remote CCF: {ccf_id}")
                    return self.delete_service_security_to_other_ccf(api_invoker_id, ccf_id)
                else:
                    current_app.logger.warning("Invoker not found in any remote CCFs")
                    return not_found_error(detail="Invoker not found", cause="API Invoker not exists or invalid ID")
            
            # Local Invoker
            result = self.__check_invoker(api_invoker_id)
            if result != None:
                return result
@@ -980,3 +1026,91 @@ class SecurityOperations(Resource):

            return make_response(object=clean_empty(response.json()), status=200)

    def add_invoker_api_map(self, api_invoker_id, ccf_id):
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"ccf_id": ccf_id})

        if interconnected_ccf is None:
            current_app.logger.warning("Interconnected CCF with ID {} not found".format(ccf_id))
            return None

        remote_invokers_id = interconnected_ccf.get("remote_invokers_id", [])
        if api_invoker_id not in remote_invokers_id:
            remote_invokers_id.append(api_invoker_id)
            interconnected_col.update_one({"ccf_id": ccf_id}, {"$set": {"remote_invokers_id": remote_invokers_id}})

        return api_invoker_id if api_invoker_id in remote_invokers_id else None

    def get_remote_invoker_ccf(self, api_invoker_id):
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"remote_invokers_id": api_invoker_id})
        return interconnected_ccf.get("ccf_id", None) if interconnected_ccf else None

    def interconnection_security_information_url(self, ccf_id, api_invoker_id, authentication_info=None, authorization_info=None):
        """Endpoint holding the token service APIs this CCF published on the given domain."""
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"ccf_id": ccf_id})

        if interconnected_ccf is None:
            # Error
            current_app.logger.warning("Interconnected CCF with ID {} not found".format(ccf_id))
            return None
        dom = interconnected_ccf.get("dst_prov_dom")

        base_url = 'https://{}//capif-security/v1/trustedInvokers/{}'.format(dom, api_invoker_id)
        first_parameter=True
        if authentication_info is not None:
            base_url += '?authenticationInfo={}'.format(authentication_info)
            first_parameter=False
        if authorization_info is not None:
            base_url += '&' if not first_parameter else '?'
            base_url += 'authorizationInfo={}'.format(authorization_info)
        return base_url

    def request_security_information_to_other_ccf(self, api_invoker_id, ccf_id, authentication_info=None, authorization_info=None):
        """Request a security information from another interconnected CCF.
        """
        url = self.interconnection_security_information_url(ccf_id, api_invoker_id, authentication_info, authorization_info)
        try:
            response = requests.request("GET", url, headers={'accept': 'application/json'},
                                        cert=INTERCONNECTION_CERT, verify=INTERCONNECTION_CA,
                                        timeout=INTERCONNECTION_TIMEOUT)
        except requests.exceptions.RequestException as exc:
            current_app.logger.exception(
                "Interconnection: Request Security Information on {} failed: {}".format(url, str(exc)))
            return internal_server_error(
                detail="Could not reach interconnected CCF {}".format(url), cause=str(exc))

        if response.status_code != 200:
            current_app.logger.error(
                "Interconnection: {} answered the Security Information request with status {} and body: {}".format(
                    url, response.status_code, response.text))
            return internal_server_error(
                detail="Interconnected CCF {} did not return its Security Information correctly".format(url),
                cause="Response status code: {} with body: {}".format(response.status_code, response.text))
        current_app.logger.debug(
            "Interconnection: Security Information received from {}: {}".format(url, response.json()))
        return make_response(object=clean_empty(response.json()), status=response.status_code)

    def delete_service_security_to_other_ccf(self,api_invoker_id, ccf_id):
        url = self.interconnection_security_information_url(ccf_id, api_invoker_id)
        try:
            response = requests.request("DELETE", url, headers={'accept': 'application/json'},
                                        cert=INTERCONNECTION_CERT, verify=INTERCONNECTION_CA,
                                        timeout=INTERCONNECTION_TIMEOUT)
        except requests.exceptions.RequestException as exc:
            current_app.logger.exception(
                "Interconnection: Delete Security Information on {} failed: {}".format(url, str(exc)))
            return internal_server_error(
                detail="Could not reach interconnected CCF {}".format(url), cause=str(exc))

        if response.status_code != 204:
            current_app.logger.error(
                "Interconnection: {} answered the Delete Security Information request with status {} and body: {}".format(
                    url, response.status_code, response.text))
            return internal_server_error(
                detail="Interconnected CCF {} did not delete its Security Information correctly".format(url),
                cause="Response status code: {} with body: {}".format(response.status_code, response.text))

        return make_response(object=None, status=response.status_code)
        
 No newline at end of file
+1 −0
Changes for services/docker-compose-capif.yml: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -165,6 +165,7 @@ services:
    - mongo

  open-api-disc:
    container_name: ${CAPIF_OPEN_DISCOVER_CONTAINER_NAME:-open-api-disc}
    build:
      context: ${SERVICES_DIR}/TS29222_CAPIF_Open_Discover_Service_API
    expose:
+2 −2
Changes for services/helper/helper_service/services/interconnection/core/capifdomaindetails.py: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ class CapifDomainOperations(Resource):
        current_app.logger.debug("Interconnection: Get domains")

        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccfs = interconnected_col.find({}, {"_id": 0, "api_remote_id_map": 0})
        interconnected_ccfs = interconnected_col.find({}, {"_id": 0, "api_remote_id_map": 0, "remote_invokers_id": 0})

        ccfinstances = [
            serialize_clean_camel_case(CcfInstanceDetails().from_dict(dict_to_camel_case(interconnected_ccf)))
@@ -58,7 +58,7 @@ class CapifDomainOperations(Resource):
        current_app.logger.debug("Interconnection: Get domain")

        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"ccf_id": ccf_id}, {"_id": 0, "api_remote_id_map": 0})
        interconnected_ccf = interconnected_col.find_one({"ccf_id": ccf_id}, {"_id": 0, "api_remote_id_map": 0, "remote_invokers_id": 0})
        if interconnected_ccf is None:
            return not_found_error(
                detail="CAPIF domain with ccf_id {} not found".format(ccf_id),
+2 −2
Changes for services/helper/helper_service/services/interconnection/core/ccfinstancedetails.py: 2 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -53,7 +53,6 @@ class CcfInstanceOperations(Resource):
            server_pub = server_cert.read()
            server_cert.close()

        
        ccfinstancedetails_dict = ccfinstancedetails.to_dict()

        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
@@ -153,7 +152,7 @@ class CcfInstanceOperations(Resource):
                    continue

                # Update mapping between local and remote API IDs
                remote_api_id = response.json().get("api_id")
                remote_api_id = response.json().get("apiId")
                self.update_api_remote_id_map(peer_dom, local_api_id, remote_api_id)
                # End of update api remote ID map

@@ -254,6 +253,7 @@ class CcfInstanceOperations(Resource):

    def update_api_remote_id_map(self, dom, local_api_id, remote_api_id):
        """Update the mapping between local and remote API IDs for a given CAPIF domain."""
        current_app.logger.debug("Interconnection: Update API remote ID map for domain {}, local_id {}, remote_id {}".format(dom, local_api_id, remote_api_id))
        # Update mapping between local and remote API IDs
        interconnected_col = self.db.get_col_by_name(self.db.interconnected)
        interconnected_ccf = interconnected_col.find_one({"dst_prov_dom": dom})
Loading