From 2500a7d3e8a6ef1e5d3a078f13d1075286a82df7 Mon Sep 17 00:00:00 2001 From: Stavros-Anastasios Charismiadis Date: Tue, 11 Jun 2024 10:18:47 +0300 Subject: [PATCH] Move Security Context API from testing to production server and remove unnecessary imports --- .../capif_security/{__main__.py => app.py} | 58 +++++++++---------- .../capif_security/config.py | 2 +- .../controllers/default_controller.py | 24 +++----- .../capif_security/core/consumer_messager.py | 5 -- .../core/internal_security_ops.py | 1 - .../capif_security/core/notification.py | 4 +- .../capif_security/core/publisher.py | 2 +- .../capif_security/core/resources.py | 5 +- .../capif_security/core/responses.py | 28 ++++++++- .../capif_security/core/servicesecurity.py | 40 ++++++------- .../capif_security/db/db.py | 2 +- .../capif_security/encoder.py | 2 +- .../capif_security/util.py | 3 +- .../capif_security/wsgi.py | 4 ++ .../requirements.txt | 4 +- .../security_prepare.sh | 4 +- 16 files changed, 99 insertions(+), 89 deletions(-) rename services/TS29222_CAPIF_Security_API/capif_security/{__main__.py => app.py} (78%) create mode 100644 services/TS29222_CAPIF_Security_API/capif_security/wsgi.py diff --git a/services/TS29222_CAPIF_Security_API/capif_security/__main__.py b/services/TS29222_CAPIF_Security_API/capif_security/app.py similarity index 78% rename from services/TS29222_CAPIF_Security_API/capif_security/__main__.py rename to services/TS29222_CAPIF_Security_API/capif_security/app.py index 98d39649..30fc3a46 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/__main__.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/app.py @@ -2,14 +2,11 @@ import connexion import logging -from capif_security import encoder +import encoder from flask_jwt_extended import JWTManager -from .config import Config -from .core.consumer_messager import Subscriber -from threading import Thread -from flask_executor import Executor +from config import Config +from core.consumer_messager import Subscriber from logging.handlers import RotatingFileHandler -import sys import os from fluent import sender from flask_executor import Executor @@ -22,10 +19,9 @@ from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.instrumentation.redis import RedisInstrumentor - - NAME = "Security-Service" + def configure_monitoring(app, config): resource = Resource(attributes={"service.name": NAME}) @@ -110,39 +106,37 @@ def verbose_formatter(): datefmt='%d/%m/%Y %H:%M:%S' ) -def main(): - - with open("/usr/src/app/capif_security/server.key", "rb") as key_file: - key_data = key_file.read() - app = connexion.App(__name__, specification_dir='./openapi/') - app.app.json_encoder = encoder.JSONEncoder +with open("/usr/src/app/capif_security/server.key", "rb") as key_file: + key_data = key_file.read() +app = connexion.App(__name__, specification_dir='./openapi/') +app.app.json_encoder = encoder.JSONEncoder - app.app.config['JWT_ALGORITHM'] = 'RS256' - app.app.config['JWT_PRIVATE_KEY'] = key_data - app.add_api('openapi.yaml', - arguments={'title': 'CAPIF_Security_API'}, - pythonic_params=True) - JWTManager(app.app) - subscriber = Subscriber() +app.app.config['JWT_ALGORITHM'] = 'RS256' +app.app.config['JWT_PRIVATE_KEY'] = key_data +app.add_api('openapi.yaml', + arguments={'title': 'CAPIF_Security_API'}, + pythonic_params=True) - config = Config() - configure_logging(app.app) +JWTManager(app.app) +subscriber = Subscriber() - if eval(os.environ.get("MONITORING").lower().capitalize()): - configure_monitoring(app.app, config.get_config()) +config = Config() +configure_logging(app.app) - executor = Executor(app.app) +if eval(os.environ.get("MONITORING").lower().capitalize()): + configure_monitoring(app.app, config.get_config()) - @app.app.before_first_request - def up_listener(): - executor.submit(subscriber.listen) +executor = Executor(app.app) +@app.app.before_first_request +def up_listener(): + executor.submit(subscriber.listen) - app.run(port=8080, debug=True) -if __name__ == '__main__': - main() +# +# if __name__ == '__main__': +# main() diff --git a/services/TS29222_CAPIF_Security_API/capif_security/config.py b/services/TS29222_CAPIF_Security_API/capif_security/config.py index 11e1c4f3..01f9914c 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/config.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/config.py @@ -5,7 +5,7 @@ import os class Config: def __init__(self): self.cached = 0 - self.file="./config.yaml" + self.file="../config.yaml" self.my_config = {} stamp = os.stat(self.file).st_mtime diff --git a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py index aee7098c..6713db13 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/controllers/default_controller.py @@ -1,26 +1,20 @@ import connexion -import six - -from capif_security.models.access_token_err import AccessTokenErr # noqa: E501 -from capif_security.models.access_token_rsp import AccessTokenRsp # noqa: E501 -from capif_security.models.access_token_req import AccessTokenReq # noqa: E501 -from capif_security.models.security_notification import SecurityNotification # noqa: E501 -from capif_security.models.service_security import ServiceSecurity # noqa: E501 -from capif_security import util + +from ..models.access_token_req import AccessTokenReq # noqa: E501 +from ..models.security_notification import SecurityNotification # noqa: E501 +from ..models.service_security import ServiceSecurity # noqa: E501 + from ..core.servicesecurity import SecurityOperations -from ..core.consumer_messager import Subscriber + from ..core.publisher import Publisher -import json + from flask import Response, request, current_app -from flask_jwt_extended import jwt_required, get_jwt_identity -from ..encoder import JSONEncoder -from ..models.problem_details import ProblemDetails -import sys + from cryptography import x509 from cryptography.hazmat.backends import default_backend from ..core.validate_user import ControlAccess from functools import wraps -import pymongo + service_security_ops = SecurityOperations() publish_ops = Publisher() diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py index fd9c328e..749f57ed 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/consumer_messager.py @@ -1,10 +1,5 @@ # subscriber.py import redis -import time -import sys -import json -import asyncio -from threading import Thread from .internal_security_ops import InternalSecurityOps from flask import current_app diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py index d5cbd968..d1b28d59 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/internal_security_ops.py @@ -1,5 +1,4 @@ -from flask import current_app from .resources import Resource class InternalSecurityOps(Resource): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/notification.py b/services/TS29222_CAPIF_Security_API/capif_security/core/notification.py index 5d69adc7..2efad5f7 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/notification.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/notification.py @@ -1,7 +1,5 @@ import requests -from ..encoder import JSONEncoder -import sys -import json + class Notifications(): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/publisher.py b/services/TS29222_CAPIF_Security_API/capif_security/core/publisher.py index f7b0c3c4..8292de4d 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/publisher.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/publisher.py @@ -1,5 +1,5 @@ import redis -import sys + class Publisher(): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/resources.py b/services/TS29222_CAPIF_Security_API/capif_security/core/resources.py index 2ba2a0f9..53a35e5d 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/resources.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/resources.py @@ -1,7 +1,8 @@ -from abc import ABC, abstractmethod -from ..db.db import MongoDatabse +from abc import ABC +from db.db import MongoDatabse from .notification import Notifications + class Resource(ABC): def __init__(self): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/responses.py b/services/TS29222_CAPIF_Security_API/capif_security/core/responses.py index 26e82b68..9c2020c7 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/responses.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/responses.py @@ -2,36 +2,62 @@ from ..models.problem_details import ProblemDetails from ..encoder import JSONEncoder from flask import Response import json -from bson import json_util +from ..util import dict_to_camel_case, clean_empty mimetype = "application/json" + def make_response(object, status): res = Response(json.dumps(object, cls=JSONEncoder), status=status, mimetype=mimetype) return res + def internal_server_error(detail, cause): prob = ProblemDetails(title="Internal Server Error", status=500, detail=detail, cause=cause) + prob = prob.to_dict() + prob = clean_empty(prob) + prob = dict_to_camel_case(prob) + return Response(json.dumps(prob, cls=JSONEncoder), status=500, mimetype=mimetype) + def forbidden_error(detail, cause): prob = ProblemDetails(title="Forbidden", status=403, detail=detail, cause=cause) + prob = prob.to_dict() + prob = clean_empty(prob) + prob = dict_to_camel_case(prob) + return Response(json.dumps(prob, cls=JSONEncoder), status=403, mimetype=mimetype) + def bad_request_error(detail, cause, invalid_params): prob = ProblemDetails(title="Bad Request", status=400, detail=detail, cause=cause, invalid_params=invalid_params) + prob = prob.to_dict() + prob = clean_empty(prob) + prob = dict_to_camel_case(prob) + return Response(json.dumps(prob, cls=JSONEncoder), status=400, mimetype=cause) + def not_found_error(detail, cause): prob = ProblemDetails(title="Not Found", status=404, detail=detail, cause=cause) + prob = prob.to_dict() + prob = clean_empty(prob) + prob = dict_to_camel_case(prob) + return Response(json.dumps(prob, cls=JSONEncoder), status=404, mimetype=mimetype) + def unauthorized_error(detail, cause): prob = ProblemDetails(title="Unauthorized", status=401, detail=detail, cause=cause) + prob = prob.to_dict() + prob = clean_empty(prob) + prob = dict_to_camel_case(prob) + return Response(json.dumps(prob, cls=JSONEncoder), status=401, mimetype=mimetype) \ No newline at end of file diff --git a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py index 69475ac9..f553b4d6 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/core/servicesecurity.py @@ -1,28 +1,19 @@ -import sys -import pymongo from pymongo import ReturnDocument -import secrets -import re + import rfc3987 from flask import current_app, Flask, Response from flask_jwt_extended import create_access_token from datetime import datetime, timedelta import json -from ..db.db import MongoDatabse -from ..encoder import JSONEncoder -from ..models.problem_details import ProblemDetails from ..models.access_token_rsp import AccessTokenRsp from ..models.access_token_claims import AccessTokenClaims from bson import json_util -import requests from ..core.publisher import Publisher from ..models.access_token_err import AccessTokenErr -from ..models.service_security import ServiceSecurity from ..util import dict_to_camel_case, clean_empty from .responses import not_found_error, make_response, bad_request_error, internal_server_error, forbidden_error -from .notification import Notifications from .resources import Resource import os @@ -53,7 +44,8 @@ class SecurityOperations(Resource): if header != "3gpp": current_app.logger.error("Bad format scope") token_error = AccessTokenErr(error="invalid_scope", error_description="The first characters must be '3gpp'") - return make_response(object=token_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(token_error.to_dict())), status=400) + return make_response(object=clean_empty(token_error.to_dict()), status=400) _, body = scope.split("#") @@ -67,21 +59,24 @@ class SecurityOperations(Resource): if aef_id not in aef_security_context: current_app.logger.error("Bad format Scope, not valid aef id ") token_error = AccessTokenErr(error="invalid_scope", error_description="One of aef_id not belongs of your security context") - return make_response(object=token_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(token_error.to_dict())), status=400) + return make_response(object=clean_empty(token_error.to_dict()), status=400) api_names = api_names.split(",") for api_name in api_names: service = capif_service_col.find_one({"$and": [{"api_name":api_name},{self.filter_aef_id:aef_id}]}) if service is None: current_app.logger.error("Bad format Scope, not valid api name") token_error = AccessTokenErr(error="invalid_scope", error_description="One of the api names does not exist or is not associated with the aef id provided") - return make_response(object=token_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(token_error.to_dict())), status=400) + return make_response(object=clean_empty(token_error.to_dict()), status=400) return None except Exception as e: current_app.logger.error("Bad format Scope: " + e) token_error = AccessTokenErr(error="invalid_scope", error_description="malformed scope") - return make_response(object=token_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(token_error.to_dict())), status=400) + return make_response(object=clean_empty(token_error.to_dict()), status=400) def __init__(self): Resource.__init__(self) @@ -182,7 +177,7 @@ class SecurityOperations(Resource): rec.update(service_security.to_dict()) mycol.insert_one(rec) - res = make_response(object=service_security, status=201) + res = make_response(object=dict_to_camel_case(clean_empty(service_security.to_dict())), status=201) res.headers['Location'] = "https://{}/capif-security/v1/trustedInvokers/{}".format(os.getenv('CAPIF_HOSTNAME'),str(api_invoker_id)) return res @@ -244,17 +239,19 @@ class SecurityOperations(Resource): invoker = invokers_col.find_one({"api_invoker_id": access_token_req["client_id"]}) if invoker is None: client_id_error = AccessTokenErr(error="invalid_client", error_description="Client Id not found") - return make_response(object=client_id_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(client_id_error.to_dict())), status=400) + return make_response(object=clean_empty(client_id_error.to_dict()), status=400) if access_token_req["grant_type"] != "client_credentials": client_id_error = AccessTokenErr(error="unsupported_grant_type", error_description="Invalid value for `grant_type` ({0}), must be one of ['client_credentials'] - 'grant_type'" .format(access_token_req["grant_type"])) - return make_response(object=client_id_error, status=400) + # return make_response(object=dict_to_camel_case(clean_empty(client_id_error.to_dict())), status=400) + return make_response(object=clean_empty(client_id_error.to_dict()), status=400) service_security = mycol.find_one({"api_invoker_id": security_id}) if service_security is None: - current_app.logger.error("Not found securoty context with id: " + security_id) + current_app.logger.error("Not found security context with id: " + security_id) return not_found_error(detail= security_context_not_found_detail, cause=api_invoker_no_context_cause) result = self.__check_scope(access_token_req["scope"], service_security) @@ -271,7 +268,8 @@ class SecurityOperations(Resource): current_app.logger.debug("Created access token") - res = make_response(object=access_token_resp, status=200) + # res = make_response(object=dict_to_camel_case(clean_empty(access_token_resp.to_dict())), status=200) + res = make_response(object=clean_empty(access_token_resp.to_dict()), status=200) return res except Exception as e: exception = "An exception occurred in return token" @@ -318,11 +316,11 @@ class SecurityOperations(Resource): result = mycol.find_one_and_update(old_object, {"$set":service_security}, projection={'_id': 0, "api_invoker_id":0},return_document=ReturnDocument.AFTER ,upsert=False) - result = clean_empty(result) + # result = clean_empty(result) current_app.logger.debug("Updated security context") - res= make_response(object=dict_to_camel_case(result), status=200) + res= make_response(object=dict_to_camel_case(clean_empty(result)), status=200) res.headers['Location'] = "https://${CAPIF_HOSTNAME}/capif-security/v1/trustedInvokers/" + str( api_invoker_id) return res diff --git a/services/TS29222_CAPIF_Security_API/capif_security/db/db.py b/services/TS29222_CAPIF_Security_API/capif_security/db/db.py index dbbb99e7..4a009dbf 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/db/db.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/db/db.py @@ -2,7 +2,7 @@ import atexit import time from pymongo import MongoClient from pymongo.errors import AutoReconnect -from ..config import Config +from config import Config from bson.codec_options import CodecOptions import os from opentelemetry.instrumentation.pymongo import PymongoInstrumentor diff --git a/services/TS29222_CAPIF_Security_API/capif_security/encoder.py b/services/TS29222_CAPIF_Security_API/capif_security/encoder.py index 9d6964e4..80bad8fa 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/encoder.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/encoder.py @@ -1,7 +1,7 @@ from connexion.apps.flask_app import FlaskJSONEncoder import six -from capif_security.models.base_model_ import Model +from models.base_model_ import Model class JSONEncoder(FlaskJSONEncoder): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/util.py b/services/TS29222_CAPIF_Security_API/capif_security/util.py index 873c290d..00ceb152 100644 --- a/services/TS29222_CAPIF_Security_API/capif_security/util.py +++ b/services/TS29222_CAPIF_Security_API/capif_security/util.py @@ -1,8 +1,7 @@ import datetime import six -import typing -from capif_security import typing_utils +import typing_utils def clean_empty(d): diff --git a/services/TS29222_CAPIF_Security_API/capif_security/wsgi.py b/services/TS29222_CAPIF_Security_API/capif_security/wsgi.py new file mode 100644 index 00000000..6026b0fa --- /dev/null +++ b/services/TS29222_CAPIF_Security_API/capif_security/wsgi.py @@ -0,0 +1,4 @@ +from app import app + +if __name__ == "__main__": + app.run() diff --git a/services/TS29222_CAPIF_Security_API/requirements.txt b/services/TS29222_CAPIF_Security_API/requirements.txt index 220adafd..835b8ca6 100644 --- a/services/TS29222_CAPIF_Security_API/requirements.txt +++ b/services/TS29222_CAPIF_Security_API/requirements.txt @@ -19,4 +19,6 @@ fluent == 0.10.0 fluent-logger == 0.10.0 opentelemetry-api == 1.17.0 opentelemetry-sdk == 1.17.0 -flask_executor == 1.0.0 \ No newline at end of file +flask_executor == 1.0.0 +gunicorn==22.0.0 +packaging==24.0 \ No newline at end of file diff --git a/services/TS29222_CAPIF_Security_API/security_prepare.sh b/services/TS29222_CAPIF_Security_API/security_prepare.sh index c28b3892..94bece05 100644 --- a/services/TS29222_CAPIF_Security_API/security_prepare.sh +++ b/services/TS29222_CAPIF_Security_API/security_prepare.sh @@ -15,5 +15,5 @@ curl -k -retry 30 \ --request GET "$VAULT_ADDR/v1/secret/data/server_cert/private" 2>/dev/null | jq -r '.data.data.key' -j > /usr/src/app/capif_security/server.key -cd /usr/src/app/ -python3 -m capif_security \ No newline at end of file +gunicorn --bind 0.0.0.0:8080 \ + --chdir /usr/src/app/capif_security wsgi:app \ No newline at end of file -- GitLab