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 98d396499dd9e5b056d1d63d9fbaa3dcf8fae442..30fc3a4612a77905a91af7e09fb248d7eaac24fc 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 11e1c4f3626d7440c6cd999ed118afb3c93caa2e..01f9914cc141eb3c49fd73506e9b1c5470edd781 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 aee7098c5de2963bb41ce27b110b5e0fbbd6394a..6713db133306fd689b329c380467f4f926bf8b08 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 fd9c328e47b82191780c4b88d69f4ab9fdd5b524..749f57ed7914b5cb1954b9ee8ef259ee930a0a93 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 d5cbd968beee2cae29b63b8ad679f0e6d776da1f..d1b28d5938fa8f7c1d2c5d2bf9d4a3a9b3c68fda 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 5d69adc7b79d47ce637309e3046f3937856e58cc..2efad5f7f28459e561ddcf9d22612275e1435dea 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 f7b0c3c4b25f9abbe24d2dc734a72a93ffe0af01..8292de4d4330b14c17be74e7448403b56fc5b9e3 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 2ba2a0f944c9ff47009a2d461aafde25751fcd88..53a35e5d60f0fbe6fd7735e0c766bee233f84b47 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 26e82b68c92cc01fbd9682f119d5d6e3d58711a8..9c2020c79bb6e0cc6e68a084607a61d563c48657 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 69475ac99a1d3fd4fca004bea2e98913571e2e68..f553b4d6112aa50af7563edf79018ed87b270664 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 dbbb99e7899c2bb68867f1c3f99b869e578c1efa..4a009dbf6a4712da2fac7020841a2e7e4ec10962 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 9d6964e4ccb171ae9174d22c6d790d186f42cebf..80bad8fa9220ab873e044b7adc0a849746088ad5 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 873c290d1d0bd4a1b6e0a9ff18e60630cc836f14..00ceb152b3c95233fec243ca5e3b8304c4b3611c 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 0000000000000000000000000000000000000000..6026b0fa96078634d3455ab93d71dcdc78774276 --- /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 220adafdb62aa6e9343fde8c22dc7b2f9c7e0ba7..835b8ca6c9732a845de5129511f58e62f4e7b4a5 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 c28b38929ae793399b64b70fab1ccbcccc6aeab0..94bece0582e614c49d6fbab23307984a2ad9875d 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