Commit 0c479199 authored by Pelayo Torres's avatar Pelayo Torres
Browse files

Merge branch 'OCF112-security-psk-method-authentication-and-authorization' into 'staging'

Ocf112 security psk method authentication and authorization

See merge request !134
parents abe6a384 274dc60a
Loading
Loading
Loading
Loading
Loading
+107 −3
Original line number Diff line number Diff line
@@ -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
@@ -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"
@@ -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)
@@ -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(
@@ -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)

+1 −1
Original line number Diff line number Diff line
@@ -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
+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
+4 −0
Original line number Diff line number Diff line
@@ -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;
              }
+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."