Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Pylint and Pytest

on:
push:
branches: [ "main", "mtls-staging" ]
branches: [ "main", "*-staging" ]
pull_request:
branches: [ "main", "mtls-staging" ]
branches: [ "main", "*-staging" ]

permissions:
contents: read
Expand Down
17 changes: 17 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Security Policy

## Supported Versions

Only the latest release version of _python-sample-app_ is supported by security
updates.

| Version | Supported |
| ---------------- | ------------------ |
| Latest Release | :white_check_mark: |
| Earlier Releases | :x: |

## Reporting a Vulnerability

If you find a vulnerability in _python-sample-app_, please report it as a security
vulnerability on GitHub:
<https://github.com/ericsson-iap/python-sample-app/security/advisories/new>
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ data:
path: "/sample-app/python/hello"
route:
cluster: eric-oss-hello-world-python-app-cluster
- match:
path: "/sample-app/python/metrics"
route:
cluster: eric-oss-hello-world-python-app-cluster
http_filters:
- name: envoy.filters.http.router
typed_config:
Expand Down Expand Up @@ -74,10 +78,6 @@ data:
path: "/sample-app/python/health"
route:
cluster: eric-oss-hello-world-python-app-cluster
- match:
path: "/sample-app/python/metrics"
route:
cluster: eric-oss-hello-world-python-app-cluster
http_filters:
- name: envoy.filters.http.router
typed_config:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@ spec:
template:
metadata:
labels:
rapp-name: {{ include "eric-oss-hello-world-python-app.name" . }}
app: {{ include "eric-oss-hello-world-python-app.name" . }}
app.kubernetes.io/name: {{ include "eric-oss-hello-world-python-app.name" . }}
app.kubernetes.io/version: {{ include "eric-oss-hello-world-python-app.version" . }}
helm.sh/chart: {{ template "eric-oss-hello-world-python-app.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
service.cleartext/scraping: "true"
rapp.metrics/scraping: "true"
annotations:
{{- if not (semverCompare ">=1.30.0" .Capabilities.KubeVersion.GitVersion) }}
container.apparmor.security.beta.kubernetes.io/eric-oss-hello-world-python-app: {{ include "eric-oss-hello-world-python-app.appArmorProfileAnnotation" . | default "runtime/default" }}
{{- end }}
prometheus.io/port: '{{ index .Values.service "http-port" }}'
prometheus.io/port: '{{ index .Values.service "https-port" }}'
prometheus.io/scrape: "{{ .Values.prometheus.scrape }}"
prometheus.io/path: "{{ .Values.prometheus.path }}"
{{- include "eric-oss-hello-world-python-app.product-info" . | indent 8 }}
Expand Down Expand Up @@ -111,12 +112,8 @@ spec:
mountPath: {{ index .Values "clientCredsMountPath" | default .Values.instantiationDefaults.clientCredsMountPath | quote }}
readOnly: true
env:
- name: IAM_CLIENT_ID
value: {{ index .Values "clientId" | quote }}
- name: IAM_CLIENT_SECRET
value: {{ index .Values "clientSecret" | quote }}
- name: IAM_BASE_URL
value: {{ index .Values "iamBaseUrl" | quote }}
- name: EIC_HOST_URL
value: {{ index .Values "eicHostUrl" | quote }}
- name: LOG_ENDPOINT
value: {{ index .Values "logEndpoint" | quote }}
- name: CA_CERT_FILE_PATH
Expand Down Expand Up @@ -149,7 +146,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.uid
- name: NAMESPACE
- name: APP_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,17 @@ spec:
app: eric-oss-hello-world-python-app
ingress:
- from:
- podSelector:
matchLabels:
app: eric-eo-api-gateway
- podSelector:
matchLabels:
app: eric-sef-exposure-api-gateway
- podSelector:
matchLabels:
app: eric-pm-server
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Values.global.platform.namespace }}
ports:
- protocol: TCP
port: {{ index .Values.service "http-port" }}
- protocol: TCP
port: {{ index .Values.service "https-port" }}

8 changes: 6 additions & 2 deletions charts/eric-oss-hello-world-python-app/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,12 @@ instantiationDefaults:
proxyCaCertMountPath: "/etc/certs/ca"
proxyAppCertMountPath: "/etc/certs/app"

# Below variables values are populated by App Manager automatically

global:
clientCredentials:
secret:
clientIdKey: "clientId"
name: "<instance id>-cc"
clientIdKey:
name:
platform:
namespace:
10 changes: 4 additions & 6 deletions eric-oss-hello-world-python-app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

def get_config():
"""get env and return config with all env vals required"""
iam_client_id = get_os_env_string("IAM_CLIENT_ID", "")
iam_client_secret = get_os_env_string("IAM_CLIENT_SECRET", "")
iam_base_url = get_os_env_string("IAM_BASE_URL", "")
eic_host_url = get_os_env_string("EIC_HOST_URL", "")
ca_cert_file_name = get_os_env_string("CA_CERT_FILE_NAME", "")
ca_cert_file_path = get_os_env_string("CA_CERT_FILE_PATH", "")
log_ctrl_file = get_os_env_string("LOG_CTRL_FILE", "")
Expand All @@ -17,11 +15,10 @@ def get_config():
app_cert_file_path = get_os_env_string("APP_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", "")
app_namespace = get_os_env_string("APP_NAMESPACE", "")

config = {
"iam_client_id": iam_client_id,
"iam_client_secret": iam_client_secret,
"iam_base_url": iam_base_url,
"eic_host_url": eic_host_url,
"ca_cert_file_name": ca_cert_file_name,
"ca_cert_file_path": ca_cert_file_path,
"log_ctrl_file": log_ctrl_file,
Expand All @@ -32,6 +29,7 @@ def get_config():
"client_creds_file_path": client_creds_file_path,
"client_id_file_name": client_id_file_name,
"chosen_unique_name": "eric-oss-hello-world-python-app",
"app_namespace": app_namespace,
}
return config

Expand Down
2 changes: 1 addition & 1 deletion eric-oss-hello-world-python-app/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def login():
"""
config = get_config()
login_path = "/auth/realms/master/protocol/openid-connect/token"
login_url = urljoin(config.get("iam_base_url"), login_path)
login_url = urljoin(config.get("eic_host_url"), login_path)
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = tls_login(login_url, headers)
resp = json.loads(resp.decode("utf-8"))
Expand Down
1 change: 1 addition & 0 deletions eric-oss-hello-world-python-app/mtls_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def log(self, message, severity):
"message": message,
"service_id": "rapp-" + self.config.get("chosen_unique_name"),
"severity": severity.name.lower(),
"metadata": {"namespace": self.config.get("app_namespace")},
}

# print to console
Expand Down
26 changes: 10 additions & 16 deletions eric-oss-hello-world-python-app/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,13 @@ def match_request_data(request):
]
]
)
uses_legacy = all(
[
parameter in request.text
for parameter in [
"grant_type=client_credentials",
"tenant_id=master",
"client_id=IAM_CLIENT_ID",
"client_secret=IAM_CLIENT_SECRET",
]
]
)
return uses_x509 or uses_legacy
return uses_x509


@pytest.fixture(name="mock_login_api")
def fixture_mock_login_api(config):
login_endpoint = urljoin(
config.get("iam_base_url"), "/auth/realms/master/protocol/openid-connect/token"
config.get("eic_host_url"), "/auth/realms/master/protocol/openid-connect/token"
)
with requests_mock.Mocker() as request_mocker:
request_mocker.post(
Expand Down Expand Up @@ -107,11 +96,15 @@ def no_log_certs():
populate_environment_variables()


@pytest.fixture(name="with_log_ctrl_file")
def with_log_ctrl_file():
log_ctrl_config = get_config()
log_ctrl_config["log_ctrl_file"] = "/dummy/path/logcontrol.json"
return log_ctrl_config


def populate_environment_variables():
os.environ["IAM_CLIENT_ID"] = "IAM_CLIENT_ID"
os.environ["IAM_CLIENT_SECRET"] = "IAM_CLIENT_SECRET"
os.environ["IAM_BASE_URL"] = "https://www.iam-base-url.com"
os.environ["EIC_HOST_URL"] = "https://www.eic-host-url.com"
os.environ["CA_CERT_FILE_NAME"] = "CA_CERT_FILE_NAME"
os.environ["CA_CERT_FILE_PATH"] = "CA_CERT_MOUNT_PATH"
os.environ["LOG_ENDPOINT"] = "LOG_ENDPOINT"
Expand All @@ -120,3 +113,4 @@ def populate_environment_variables():
os.environ["APP_CERT_FILE_PATH"] = "APP_CERT_FILE_PATH"
os.environ["CLIENT_CREDS_FILE_PATH"] = os.path.relpath(os.path.dirname(__file__), "/")
os.environ["CLIENT_ID_FILE_NAME"] = "client_id_example"
os.environ["APP_NAMESPACE"] = "test-namespace"
2 changes: 1 addition & 1 deletion eric-oss-hello-world-python-app/tests/test_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def test_login_receives_token_x509(mock_login_api, config):
def test_login_bad_credentials(requests_mock, config):
"""Ensure we get an error if credentials are bad"""
login_url = urljoin(
config.get("iam_base_url"), "/auth/realms/master/protocol/openid-connect/token"
config.get("eic_host_url"), "/auth/realms/master/protocol/openid-connect/token"
)
requests_mock.post(
login_url, status_code=400, json={"error": "invalid_request"}
Expand Down
30 changes: 16 additions & 14 deletions eric-oss-hello-world-python-app/tests/test_mtls_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from mtls_logging import MtlsLogging, Severity



def test_log_stdout_and_mtls(caplog):
"""Ensure any log is sent both to STDOUT and through HTTPS"""
message = "Message which should appear in both STDOUT and sent as a POST request"
Expand Down Expand Up @@ -80,30 +81,31 @@ def test_log_handles_missing_schema(caplog):
assert "Request failed for mTLS logging: Missing schema" in caplog.text


def test_init_sets_log_level_from_log_ctrl_file():
def test_init_sets_log_level_from_log_ctrl_file(with_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",
"chosen_unique_name": "eric-oss-hello-world-python-app"
}

# Patch config and environment variable
with mock.patch("mtls_logging.get_config", return_value=mock_config), \
with mock.patch("mtls_logging.get_config", return_value=with_log_ctrl_file), \
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


def test_namespace_is_set_in_mtls_log(config):
"""Ensure the namespace is set in the mTLS log"""
message = "Message that should be included in a request payload that includes the namespace"

with mock.patch("mtls_logging.get_config", return_value=config):
mock_post = with_mocked_post(send_log, message, Severity.INFO, Severity.INFO)
mock_post.assert_called()

request_body_object = mock_post.call_args.kwargs.get('json')

assert request_body_object['metadata']['namespace'] == 'test-namespace'