Loading services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py +107 −3 Original line number Diff line number Diff line Loading @@ -5,9 +5,13 @@ from datetime import datetime, timedelta import rfc3987 from bson import json_util from flask import current_app from flask import current_app, request from flask_jwt_extended import create_access_token from pymongo import ReturnDocument import hmac import hashlib import unicodedata from ..core.publisher import Publisher from ..models.access_token_claims import AccessTokenClaims Loading Loading @@ -94,6 +98,47 @@ class SecurityOperations(Resource): token_error = AccessTokenErr(error="invalid_scope", error_description="malformed scope") return make_response(object=clean_empty(token_error.to_dict()), status=400) def __derive_psk(self, master_key:str, session_id:str, interface:dict): ## Derive the PSK using the provided master key, session ID, and interface information # Interface information host = None if 'fqdn' in interface: host = interface['fqdn'] elif 'ipv4Addr' in interface: host = interface['ipv4Addr'] elif 'ipv6Addr' in interface: host = interface['ipv6Addr'] port = interface.get('port', None) api_prefix = interface.get('apiPrefix', '') scheme = "https" if port in (None, 443) else "http" interface_info = f"{scheme}://{host}" if port and port != 443: interface_info += f":{port}" interface_info += api_prefix # Normalize the strings to NFKC form p0_string = unicodedata.normalize("NFKC", interface_info).encode("utf-8") p1_string = unicodedata.normalize("NFKC", session_id).encode("utf-8") # Convert to octet format (0xFF) p0_octet_string = ' '.join(f'0x{byte:02X}' for byte in p0_string) p1_octet_string = ' '.join(f'0x{byte:02X}' for byte in p1_string) # Convert number of bytes to 16-bit big-endian l0 = ' '.join(f'0x{byte:02X}' for byte in len(p0_octet_string).to_bytes(2, 'big')) l1 = ' '.join(f'0x{byte:02X}' for byte in len(p1_octet_string).to_bytes(2, 'big')) # Create S string using FC (0x7A) and the octet strings with their lengths S = "0x7A" + ' ' + p0_octet_string + ' ' + l0 + ' ' + p1_octet_string + ' ' + l1 psk = hmac.new(master_key.encode("utf-8"), S.encode("utf-8"), hashlib.sha256).digest() return psk def __init__(self): Resource.__init__(self) self.filter_aef_id = "aef_profiles.aef_id" Loading Loading @@ -274,7 +319,7 @@ class SecurityOperations(Resource): self.db.capif_service_col) services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) current_app.logger.debug("Aef profile: " + str(services_security_object)) if services_security_object is None: current_app.logger.error( "Not found service with this aef id: " + service_instance.aef_id) Loading Loading @@ -306,6 +351,36 @@ class SecurityOperations(Resource): # Select the highest-priority security method service_instance.sel_security_method = sorted_methods[0] if service_instance.sel_security_method == "PSK": request.headers.get('X-TLS-Protocol', 'N/A') sesionId = request.headers.get('X-TLS-Session-ID', 'N/A') Mkey = request.headers.get('X-TLS-MKey', 'N/A') current_app.logger.info(f"TLS Protocol: {request.headers.get('X-TLS-Protocol', 'N/A')}, Session id: {sesionId}, Master Key: {Mkey}") interface = None if service_instance.interface_details: current_app.logger.debug("Interface details found") interface = service_instance.interface_details.to_dict() else: current_app.logger.error("Interface details not found") services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id}, {"aef_profiles": {"$elemMatch": {"aef_id": service_instance.aef_id}}, "_id": 0}) current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0])) if "interface_descriptions" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["interface_descriptions"])) interface = services_security_object["aef_profiles"][0]["interface_descriptions"][0] elif "domain_name" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["domain_name"])) interface = services_security_object["aef_profiles"][0]["domain_name"] if interface: current_app.logger.debug("Deriving PSK") psk = self.__derive_psk(Mkey, sesionId, interface) current_app.logger.debug("PSK derived : " + str(psk)) service_instance.authorization_info = str(psk) # Send service instance to ACL current_app.logger.debug("Sending message to create ACL") publish_ops.publish_message("acls-messages", "create-acl:"+str( Loading Loading @@ -501,6 +576,35 @@ class SecurityOperations(Resource): valid_security_method)[0] update_acls.append({"api_id": service_instance.api_id, "aef_id": service_instance.aef_id}) if service_instance.sel_security_method == "PSK": request.headers.get('X-TLS-Protocol', 'N/A') sesionId = request.headers.get('X-TLS-Session-ID', 'N/A') Mkey = request.headers.get('X-TLS-MKey', 'N/A') current_app.logger.info(f"TLS Protocol: {request.headers.get('X-TLS-Protocol', 'N/A')}, Session id: {sesionId}, Master Key: {Mkey}") interface = None if service_instance.interface_details: current_app.logger.debug("Interface details found") interface = service_instance.interface_details.to_dict() else: current_app.logger.error("Interface details not found") services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id}, {"aef_profiles": {"$elemMatch": {"aef_id": service_instance.aef_id}}, "_id": 0}) current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0])) if "interface_descriptions" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["interface_descriptions"])) interface = services_security_object["aef_profiles"][0]["interface_descriptions"][0] elif "domain_name" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["domain_name"])) interface = services_security_object["aef_profiles"][0]["domain_name"] if interface: current_app.logger.debug("Deriving PSK") psk = self.__derive_psk(Mkey, sesionId, interface) current_app.logger.debug("PSK derived : " + str(psk)) service_instance.authorization_info = str(psk) service_security = service_security.to_dict() service_security = clean_empty(service_security) Loading services/docker-compose-capif.yml +1 −1 Original line number Diff line number Diff line Loading @@ -308,7 +308,7 @@ services: ports: - "8080:8080" - "443:443" image: ${REGISTRY_BASE_URL}/nginx:${OCF_VERSION} image: labs.etsi.org:5050/ocf/capif/nginx-ocf-patched:1.27.1 environment: - CAPIF_HOSTNAME=${CAPIF_HOSTNAME} - VAULT_HOSTNAME=vault Loading services/nginx/Dockerfile +1 −1 Original line number Diff line number Diff line FROM labs.etsi.org:5050/ocf/capif/nginx:1.27.1 FROM labs.etsi.org:5050/ocf/capif/nginx-ocf-patched:1.27.1 RUN apt-get update && apt-get install -y jq && apt-get clean RUN apt-get install -y openssl RUN apt-get install -y curl Loading services/nginx/nginx.conf +4 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,10 @@ http { return 401 $security_error_message; } proxy_set_header X-TLS-Protocol $ssl_protocol; proxy_set_header X-TLS-Session-ID $ssl_session_id; proxy_set_header X-TLS-MKey $sslkeylog_mk; proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_pass http://capif-security:8080; } Loading tools/base_images_scripts/create_nginx_patched_images.sh 0 → 100755 +120 −0 Original line number Diff line number Diff line #!/bin/bash set -euo pipefail NGINX_VERSION=1.27.1 PLATFORMS=("linux/arm64" "linux/amd64") PATCH_REPO="https://labs.etsi.org/rep/ocf/tools/nginx-sslkeylog.git" REGISTRY="labs.etsi.org:5050/ocf/capif" MANIFEST_AMEND="" for platform in "${PLATFORMS[@]}";do image_name="nginx-ocf-patched:$NGINX_VERSION" echo "$image_name pulled for platform $platform" container_id=$(docker run -d --platform=$platform --name build-nginx debian:bullseye sleep infinity) docker exec $container_id bash -c " set -e echo 'Installing build dependencies...' apt-get update && apt-get install -y \ build-essential \ libpcre3-dev \ libssl-dev \ zlib1g-dev \ curl \ patch \ ca-certificates \ openssl \ git \ jq \ gettext echo 'Creating nginx user...' useradd -r -d /etc/nginx -s /sbin/nologin nginx echo 'Downloading NGINX $NGINX_VERSION...' curl -LO https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz tar -xzf nginx-${NGINX_VERSION}.tar.gz cd nginx-${NGINX_VERSION} echo 'Cloning and applying patch...' git clone ${PATCH_REPO} ../nginx-sslkeylog patch -p1 < ../nginx-sslkeylog/nginx-patches/${NGINX_VERSION}.patch echo 'Configuring NGINX...' ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --http-client-body-temp-path=/var/cache/nginx/client_temp \ --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ --user=nginx \ --group=nginx \ --with-compat \ --with-file-aio \ --with-threads \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_dav_module \ --with-http_flv_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_mp4_module \ --with-http_random_index_module \ --with-http_realip_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_sub_module \ --with-http_v2_module \ --with-http_v3_module \ --with-mail \ --with-mail_ssl_module \ --with-stream \ --with-stream_realip_module \ --with-stream_ssl_module \ --with-stream_ssl_preread_module \ --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-${NGINX_VERSION}/debian/debuild-base/nginx-${NGINX_VERSION}=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \ --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \ --add-module=../nginx-sslkeylog echo 'Building NGINX...' make -j\$(nproc) && make install echo 'Creating required temporary directories...' mkdir -p /var/cache/nginx/{client_temp,proxy_temp,fastcgi_temp,uwsgi_temp,scgi_temp} echo '✅ NGINX build completed.' " tag=$(echo $platform | awk -F'/' '{print $NF}') docker commit $container_id $image_name docker tag $image_name $REGISTRY/$image_name-$tag echo "$REGISTRY/$image_name-$tag tagged" docker push $REGISTRY/$image_name-$tag echo "$REGISTRY/$image_name-$tag pushed" MANIFEST_AMEND="$MANIFEST_AMEND --amend $REGISTRY/$image_name-$tag" docker stop $container_id docker rm $container_id done docker manifest create $REGISTRY/$image_name $MANIFEST_AMEND echo "$REGISTRY/$image_name Manifest created with amend $MANIFEST_AMEND" docker manifest push $REGISTRY/$image_name echo "$REGISTRY/$image_name Manifest pushed" echo "🎉 All builds completed successfully." Loading
services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py +107 −3 Original line number Diff line number Diff line Loading @@ -5,9 +5,13 @@ from datetime import datetime, timedelta import rfc3987 from bson import json_util from flask import current_app from flask import current_app, request from flask_jwt_extended import create_access_token from pymongo import ReturnDocument import hmac import hashlib import unicodedata from ..core.publisher import Publisher from ..models.access_token_claims import AccessTokenClaims Loading Loading @@ -94,6 +98,47 @@ class SecurityOperations(Resource): token_error = AccessTokenErr(error="invalid_scope", error_description="malformed scope") return make_response(object=clean_empty(token_error.to_dict()), status=400) def __derive_psk(self, master_key:str, session_id:str, interface:dict): ## Derive the PSK using the provided master key, session ID, and interface information # Interface information host = None if 'fqdn' in interface: host = interface['fqdn'] elif 'ipv4Addr' in interface: host = interface['ipv4Addr'] elif 'ipv6Addr' in interface: host = interface['ipv6Addr'] port = interface.get('port', None) api_prefix = interface.get('apiPrefix', '') scheme = "https" if port in (None, 443) else "http" interface_info = f"{scheme}://{host}" if port and port != 443: interface_info += f":{port}" interface_info += api_prefix # Normalize the strings to NFKC form p0_string = unicodedata.normalize("NFKC", interface_info).encode("utf-8") p1_string = unicodedata.normalize("NFKC", session_id).encode("utf-8") # Convert to octet format (0xFF) p0_octet_string = ' '.join(f'0x{byte:02X}' for byte in p0_string) p1_octet_string = ' '.join(f'0x{byte:02X}' for byte in p1_string) # Convert number of bytes to 16-bit big-endian l0 = ' '.join(f'0x{byte:02X}' for byte in len(p0_octet_string).to_bytes(2, 'big')) l1 = ' '.join(f'0x{byte:02X}' for byte in len(p1_octet_string).to_bytes(2, 'big')) # Create S string using FC (0x7A) and the octet strings with their lengths S = "0x7A" + ' ' + p0_octet_string + ' ' + l0 + ' ' + p1_octet_string + ' ' + l1 psk = hmac.new(master_key.encode("utf-8"), S.encode("utf-8"), hashlib.sha256).digest() return psk def __init__(self): Resource.__init__(self) self.filter_aef_id = "aef_profiles.aef_id" Loading Loading @@ -274,7 +319,7 @@ class SecurityOperations(Resource): self.db.capif_service_col) services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id, self.filter_aef_id: service_instance.aef_id}, {"aef_profiles.security_methods.$": 1}) current_app.logger.debug("Aef profile: " + str(services_security_object)) if services_security_object is None: current_app.logger.error( "Not found service with this aef id: " + service_instance.aef_id) Loading Loading @@ -306,6 +351,36 @@ class SecurityOperations(Resource): # Select the highest-priority security method service_instance.sel_security_method = sorted_methods[0] if service_instance.sel_security_method == "PSK": request.headers.get('X-TLS-Protocol', 'N/A') sesionId = request.headers.get('X-TLS-Session-ID', 'N/A') Mkey = request.headers.get('X-TLS-MKey', 'N/A') current_app.logger.info(f"TLS Protocol: {request.headers.get('X-TLS-Protocol', 'N/A')}, Session id: {sesionId}, Master Key: {Mkey}") interface = None if service_instance.interface_details: current_app.logger.debug("Interface details found") interface = service_instance.interface_details.to_dict() else: current_app.logger.error("Interface details not found") services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id}, {"aef_profiles": {"$elemMatch": {"aef_id": service_instance.aef_id}}, "_id": 0}) current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0])) if "interface_descriptions" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["interface_descriptions"])) interface = services_security_object["aef_profiles"][0]["interface_descriptions"][0] elif "domain_name" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["domain_name"])) interface = services_security_object["aef_profiles"][0]["domain_name"] if interface: current_app.logger.debug("Deriving PSK") psk = self.__derive_psk(Mkey, sesionId, interface) current_app.logger.debug("PSK derived : " + str(psk)) service_instance.authorization_info = str(psk) # Send service instance to ACL current_app.logger.debug("Sending message to create ACL") publish_ops.publish_message("acls-messages", "create-acl:"+str( Loading Loading @@ -501,6 +576,35 @@ class SecurityOperations(Resource): valid_security_method)[0] update_acls.append({"api_id": service_instance.api_id, "aef_id": service_instance.aef_id}) if service_instance.sel_security_method == "PSK": request.headers.get('X-TLS-Protocol', 'N/A') sesionId = request.headers.get('X-TLS-Session-ID', 'N/A') Mkey = request.headers.get('X-TLS-MKey', 'N/A') current_app.logger.info(f"TLS Protocol: {request.headers.get('X-TLS-Protocol', 'N/A')}, Session id: {sesionId}, Master Key: {Mkey}") interface = None if service_instance.interface_details: current_app.logger.debug("Interface details found") interface = service_instance.interface_details.to_dict() else: current_app.logger.error("Interface details not found") services_security_object = capif_service_col.find_one( {"api_id": service_instance.api_id}, {"aef_profiles": {"$elemMatch": {"aef_id": service_instance.aef_id}}, "_id": 0}) current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0])) if "interface_descriptions" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["interface_descriptions"])) interface = services_security_object["aef_profiles"][0]["interface_descriptions"][0] elif "domain_name" in services_security_object["aef_profiles"][0]: current_app.logger.debug("Aef profile: " + str(services_security_object["aef_profiles"][0]["domain_name"])) interface = services_security_object["aef_profiles"][0]["domain_name"] if interface: current_app.logger.debug("Deriving PSK") psk = self.__derive_psk(Mkey, sesionId, interface) current_app.logger.debug("PSK derived : " + str(psk)) service_instance.authorization_info = str(psk) service_security = service_security.to_dict() service_security = clean_empty(service_security) Loading
services/docker-compose-capif.yml +1 −1 Original line number Diff line number Diff line Loading @@ -308,7 +308,7 @@ services: ports: - "8080:8080" - "443:443" image: ${REGISTRY_BASE_URL}/nginx:${OCF_VERSION} image: labs.etsi.org:5050/ocf/capif/nginx-ocf-patched:1.27.1 environment: - CAPIF_HOSTNAME=${CAPIF_HOSTNAME} - VAULT_HOSTNAME=vault Loading
services/nginx/Dockerfile +1 −1 Original line number Diff line number Diff line FROM labs.etsi.org:5050/ocf/capif/nginx:1.27.1 FROM labs.etsi.org:5050/ocf/capif/nginx-ocf-patched:1.27.1 RUN apt-get update && apt-get install -y jq && apt-get clean RUN apt-get install -y openssl RUN apt-get install -y curl Loading
services/nginx/nginx.conf +4 −0 Original line number Diff line number Diff line Loading @@ -142,6 +142,10 @@ http { return 401 $security_error_message; } proxy_set_header X-TLS-Protocol $ssl_protocol; proxy_set_header X-TLS-Session-ID $ssl_session_id; proxy_set_header X-TLS-MKey $sslkeylog_mk; proxy_set_header X-SSL-Client-Cert $ssl_client_cert; proxy_pass http://capif-security:8080; } Loading
tools/base_images_scripts/create_nginx_patched_images.sh 0 → 100755 +120 −0 Original line number Diff line number Diff line #!/bin/bash set -euo pipefail NGINX_VERSION=1.27.1 PLATFORMS=("linux/arm64" "linux/amd64") PATCH_REPO="https://labs.etsi.org/rep/ocf/tools/nginx-sslkeylog.git" REGISTRY="labs.etsi.org:5050/ocf/capif" MANIFEST_AMEND="" for platform in "${PLATFORMS[@]}";do image_name="nginx-ocf-patched:$NGINX_VERSION" echo "$image_name pulled for platform $platform" container_id=$(docker run -d --platform=$platform --name build-nginx debian:bullseye sleep infinity) docker exec $container_id bash -c " set -e echo 'Installing build dependencies...' apt-get update && apt-get install -y \ build-essential \ libpcre3-dev \ libssl-dev \ zlib1g-dev \ curl \ patch \ ca-certificates \ openssl \ git \ jq \ gettext echo 'Creating nginx user...' useradd -r -d /etc/nginx -s /sbin/nologin nginx echo 'Downloading NGINX $NGINX_VERSION...' curl -LO https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz tar -xzf nginx-${NGINX_VERSION}.tar.gz cd nginx-${NGINX_VERSION} echo 'Cloning and applying patch...' git clone ${PATCH_REPO} ../nginx-sslkeylog patch -p1 < ../nginx-sslkeylog/nginx-patches/${NGINX_VERSION}.patch echo 'Configuring NGINX...' ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --http-client-body-temp-path=/var/cache/nginx/client_temp \ --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ --user=nginx \ --group=nginx \ --with-compat \ --with-file-aio \ --with-threads \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_dav_module \ --with-http_flv_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_mp4_module \ --with-http_random_index_module \ --with-http_realip_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_sub_module \ --with-http_v2_module \ --with-http_v3_module \ --with-mail \ --with-mail_ssl_module \ --with-stream \ --with-stream_realip_module \ --with-stream_ssl_module \ --with-stream_ssl_preread_module \ --with-cc-opt='-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-${NGINX_VERSION}/debian/debuild-base/nginx-${NGINX_VERSION}=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \ --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \ --add-module=../nginx-sslkeylog echo 'Building NGINX...' make -j\$(nproc) && make install echo 'Creating required temporary directories...' mkdir -p /var/cache/nginx/{client_temp,proxy_temp,fastcgi_temp,uwsgi_temp,scgi_temp} echo '✅ NGINX build completed.' " tag=$(echo $platform | awk -F'/' '{print $NF}') docker commit $container_id $image_name docker tag $image_name $REGISTRY/$image_name-$tag echo "$REGISTRY/$image_name-$tag tagged" docker push $REGISTRY/$image_name-$tag echo "$REGISTRY/$image_name-$tag pushed" MANIFEST_AMEND="$MANIFEST_AMEND --amend $REGISTRY/$image_name-$tag" docker stop $container_id docker rm $container_id done docker manifest create $REGISTRY/$image_name $MANIFEST_AMEND echo "$REGISTRY/$image_name Manifest created with amend $MANIFEST_AMEND" docker manifest push $REGISTRY/$image_name echo "$REGISTRY/$image_name Manifest pushed" echo "🎉 All builds completed successfully."