diff --git a/charts/eric-oss-hello-world-python-app/templates/deployment/deployment.yaml b/charts/eric-oss-hello-world-python-app/templates/deployment/deployment.yaml index 4aaea3c..1e760a8 100644 --- a/charts/eric-oss-hello-world-python-app/templates/deployment/deployment.yaml +++ b/charts/eric-oss-hello-world-python-app/templates/deployment/deployment.yaml @@ -67,6 +67,14 @@ spec: secret: secretName: {{ include "eric-oss-hello-world-python-app.clientSecret" . | quote }} defaultMode: 420 + - name: app-sef-certs + secret: + secretName: {{ index .Values "appSefSecretName" | quote }} + defaultMode: 420 + - name: platform-sef-cacerts + secret: + secretName: {{ index .Values "platformCaCertSecretName" | quote }} + defaultMode: 420 containers: - name: eric-oss-hello-world-python-app image: {{ template "eric-oss-hello-world-python-app.imagePath" (dict "imageId" "eric-oss-hello-world-python-app" "values" .Values "files" .Files) }} @@ -96,6 +104,12 @@ spec: - name: client-creds mountPath: {{ index .Values "clientCredsMountPath" | default .Values.instantiationDefaults.clientCredsMountPath | quote }} readOnly: true + - name: app-sef-certs + mountPath: {{ index .Values "appSefCredsMountPath" | default .Values.instantiationDefaults.appSefCredsMountPath | quote }} + readOnly: true + - name: platform-sef-cacerts + mountPath: {{ index .Values "platformSefCaCertMountPath" | default .Values.instantiationDefaults.platformSefCaCertMountPath | quote }} + readOnly: true env: - name: IAM_CLIENT_ID value: {{ index .Values "clientId" | quote }} @@ -109,12 +123,22 @@ spec: value: {{ index .Values "platformCaCertMountPath" | default .Values.instantiationDefaults.platformCaCertMountPath | quote }} - name: CA_CERT_FILE_NAME value: {{ index .Values "platformCaCertFileName" | quote }} + - name: CA_SEF_CERT_FILE_PATH + value: {{ index .Values "platformSefCaCertMountPath" | default .Values.instantiationDefaults.platformSefCaCertMountPath | quote }} + - name: CA_SEF_CERT_FILE_NAME + value: {{ index .Values "platformSefCaCertFileName" | quote }} - name: APP_KEY value: {{ index .Values "appKeyFileName" | quote }} - name: APP_CERT value: {{ index .Values "appCertFileName" | quote }} - name: APP_CERT_FILE_PATH value: {{ index .Values "appCertMountPath" | default .Values.instantiationDefaults.appCertMountPath | quote }} + - name: APP_SEF_KEY + value: {{ index .Values "appSefKeyFileName" | quote }} + - name: APP_SEF_CERT + value: {{ index .Values "appSefCertFileName" | quote }} + - name: APP_SEF_CERT_FILE_PATH + value: {{ index .Values "appSefCertMountPath" | default .Values.instantiationDefaults.appSefCertMountPath | quote }} - name: CLIENT_CREDS_FILE_PATH value: {{ index .Values "clientCredsMountPath" | default .Values.instantiationDefaults.clientCredsMountPath | quote }} - name: CLIENT_ID_FILE_NAME diff --git a/charts/eric-oss-hello-world-python-app/values.yaml b/charts/eric-oss-hello-world-python-app/values.yaml index 2c4b392..5318c87 100644 --- a/charts/eric-oss-hello-world-python-app/values.yaml +++ b/charts/eric-oss-hello-world-python-app/values.yaml @@ -5,13 +5,13 @@ global: timezone: UTC registry: url: armdocker.rnd.ericsson.se - imagePullPolicy: IfNotPresent + imagePullPolicy: Always pullSecret: internalIPFamily: imageCredentials: repoPath: - pullPolicy: IfNotPresent + pullPolicy: Always registry: url: pullSecret: @@ -127,6 +127,8 @@ instantiationDefaults: platformCaCertMountPath: "/etc/tls-ca/platform/" appCertMountPath: "/etc/tls/log/" clientCredsMountPath: "/etc/client-creds/" + platformSefCaCertMountPath: "/etc/tls-ca/platform/sef/" + appSefCertMountPath: "/etc/tls/log/sef/" global: clientCredentials: diff --git a/eric-oss-hello-world-python-app/config.py b/eric-oss-hello-world-python-app/config.py index 603fa5e..9340a77 100644 --- a/eric-oss-hello-world-python-app/config.py +++ b/eric-oss-hello-world-python-app/config.py @@ -15,8 +15,13 @@ def get_config(): app_key = get_os_env_string("APP_KEY", "") app_cert = get_os_env_string("APP_CERT", "") app_cert_file_path = get_os_env_string("APP_CERT_FILE_PATH", "") + app_sef_key = get_os_env_string("APP_SEF_KEY", "") + app_sef_cert = get_os_env_string("APP_SEF_CERT", "") + app_sef_cert_file_path = get_os_env_string("APP_SEF_CERT_FILE_PATH", "") client_creds_file_path = get_os_env_string("CLIENT_CREDS_FILE_PATH", "") client_id_file_name = get_os_env_string("CLIENT_ID_FILE_NAME", "") + ca_sef_cert_file_name = get_os_env_string("CA_SEF_CERT_FILE_NAME", "") + ca_sef_cert_file_path = get_os_env_string("CA_SEF_CERT_FILE_PATH", "") config = { "iam_client_id": iam_client_id, @@ -31,6 +36,11 @@ def get_config(): "app_cert_file_path": app_cert_file_path, "client_creds_file_path": client_creds_file_path, "client_id_file_name": client_id_file_name, + "app_sef_key": app_sef_key, + "app_sef_cert": app_sef_cert, + "app_sef_cert_file_path": app_sef_cert_file_path, + "ca_sef_cert_file_name": ca_sef_cert_file_name, + "ca_sef_cert_file_path": ca_sef_cert_file_path, } return config diff --git a/eric-oss-hello-world-python-app/login.py b/eric-oss-hello-world-python-app/login.py index 4da2e89..d2254cb 100644 --- a/eric-oss-hello-world-python-app/login.py +++ b/eric-oss-hello-world-python-app/login.py @@ -24,7 +24,7 @@ def login(): login_path = "/auth/realms/master/protocol/openid-connect/token" login_url = urljoin(config.get("iam_base_url"), login_path) headers = {"Content-Type": "application/x-www-form-urlencoded"} - resp = tls_login(login_url, headers) + resp = mtls_login(login_url, headers) resp = json.loads(resp.decode("utf-8")) token, time_until_expiry = resp["access_token"], resp["expires_in"] time_until_expiry -= ( @@ -33,8 +33,8 @@ def login(): return token, time.time() + time_until_expiry -def tls_login(url, headers): - """This function sends an HTTP POST request with TLS for the login operation""" +def mtls_login(url, headers): + """This function sends an HTTP POST request with mTLS for the login operation""" config = get_config() ca_cert = os.path.join( "/", config.get("ca_cert_file_path"), config.get("ca_cert_file_name") diff --git a/eric-oss-hello-world-python-app/main.py b/eric-oss-hello-world-python-app/main.py index 8d3611b..8a5998b 100755 --- a/eric-oss-hello-world-python-app/main.py +++ b/eric-oss-hello-world-python-app/main.py @@ -6,6 +6,9 @@ along with a health check and metrics endpoints. """ import time +import os +import ssl + from flask import abort from flask import Flask from login import login @@ -17,6 +20,7 @@ CollectorRegistry, Counter, ) +from config import get_config SERVICE_PREFIX = "python_hello_world" @@ -91,6 +95,21 @@ def create_metrics(self): self.registry.register(self.requests_failed) -if __name__ == "__main__": +if __name__ == '__main__': instance = Application() - instance.run(host="0.0.0.0", port="8050") + + config = get_config() + + # Build paths from config + ca_sef_cert = os.path.join("/", config.get("ca_sef_cert_file_path"), config.get("ca_sef_cert_file_name")) + app_sef_cert = os.path.join("/", config.get("app_sef_cert_file_path"), config.get("app_sef_cert")) + app_sef_key = os.path.join("/", config.get("app_sef_cert_file_path"), config.get("app_sef_key")) + + # Create SSL context configured for mTLS + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.load_cert_chain(certfile=app_sef_cert, keyfile=app_sef_key) + ssl_context.load_verify_locations(cafile=ca_sef_cert) + ssl_context.verify_mode = ssl.CERT_REQUIRED + + # Start the application with SSL + instance.run(host='0.0.0.0', port=8050, ssl_context=ssl_context) diff --git a/eric-oss-hello-world-python-app/tests/test_mtls_logging.py b/eric-oss-hello-world-python-app/tests/test_mtls_logging.py index 42cffdc..ef1a473 100644 --- a/eric-oss-hello-world-python-app/tests/test_mtls_logging.py +++ b/eric-oss-hello-world-python-app/tests/test_mtls_logging.py @@ -1,5 +1,7 @@ """Tests which cover the app's logging, both to STDOUT and to Log Aggregator""" +import builtins +import json from unittest import mock import requests from mtls_logging import MtlsLogging, Severity @@ -60,3 +62,48 @@ def send_log(message, logger_level, log_level): """Send a log through the MTLS logger""" logger = MtlsLogging(logger_level) logger.log(message, log_level) + +def test_log_handles_invalid_url(caplog): + """Ensure logger logs an error if requests.post raises InvalidURL""" + message = "Test message for InvalidURL" + with mock.patch.object(requests, "post", side_effect=requests.exceptions.InvalidURL("Bad URL")): + logger = MtlsLogging(Severity.DEBUG) + logger.log(message, Severity.CRITICAL) + assert "Request failed for mTLS logging: Bad URL" in caplog.text + + +def test_log_handles_missing_schema(caplog): + """Ensure logger logs an error if requests.post raises MissingSchema""" + message = "Test message for MissingSchema" + with mock.patch.object(requests, "post", side_effect=requests.exceptions.MissingSchema("Missing schema")): + logger = MtlsLogging(Severity.DEBUG) + logger.log(message, Severity.CRITICAL) + assert "Request failed for mTLS logging: Missing schema" in caplog.text + + +def test_init_sets_log_level_from_log_ctrl_file(): + # Sample log control file contents with container mapping + log_ctrl_data = [ + {"container": "test-container", "severity": "critical"}, + {"container": "other-container", "severity": "warning"}, + ] + log_ctrl_json = json.dumps(log_ctrl_data) + + # Mocked config dict including the log_ctrl_file path + mock_config = { + "log_ctrl_file": "/dummy/path/logcontrol.json", + "ca_cert_file_name": "ca.pem", + "ca_cert_file_path": "certs", + "app_cert": "appcert.pem", + "app_key": "appkey.pem", + "app_cert_file_path": "certs", + "log_endpoint": "log.endpoint" + } + + # Patch config and environment variable + with mock.patch("mtls_logging.get_config", return_value=mock_config), \ + mock.patch("mtls_logging.get_os_env_string", return_value="test-container"), \ + mock.patch("builtins.open", mock.mock_open(read_data=log_ctrl_json)): + + logger = MtlsLogging(level=None) + assert logger.logger.level == Severity.CRITICAL