Skip to content

Commit 13db508

Browse files
authored
Adding CA certificates logic for verification (#92)
1 parent a9347e2 commit 13db508

File tree

9 files changed

+79
-187
lines changed

9 files changed

+79
-187
lines changed

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,22 @@ Example (sanitized):
8282
"token_provider_url": "https://<token-ui.example>",
8383
"token_public_keys_url": "https://<token-api.example>/token/public-keys",
8484
"kafka_bootstrap_server": "broker1:9092,broker2:9092",
85-
"event_bus_arn": "arn:aws:events:region:acct:event-bus/your-bus"
85+
"event_bus_arn": "arn:aws:events:region:acct:event-bus/your-bus",
86+
"ssl_ca_bundle": "/path/to/ca-bundle.pem"
8687
}
8788
```
89+
90+
Configuration keys:
91+
- `access_config` – local file path or S3 URI for access control map.
92+
- `token_provider_url` – external URL for obtaining JWT tokens.
93+
- `token_public_keys_url` – endpoint serving JWT verification public keys (RS256).
94+
- `kafka_bootstrap_server` – comma-separated Kafka broker addresses.
95+
- `event_bus_arn` – AWS EventBridge event bus ARN for EventBridge writer.
96+
- `ssl_ca_bundle` (optional) – SSL certificate verification for S3 access and token public key requests.
97+
- `true` - default, uses system CA bundle
98+
- `false` - disables verification, not recommended for production
99+
- `"/path/to/ca-bundle.pem"` - custom CA bundle
100+
88101
Supporting configs:
89102
- `access.json` – map: topicName -> array of authorized subjects (JWT `sub`). May reside locally or at S3 path referenced by `access_config`.
90103
- `topic_*.json` – each file contains a JSON Schema for a topic. In the current code these are explicitly loaded inside `event_gate_lambda.py`. (Future enhancement: auto-discover or index file.)

conf/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"token_provider_url": "https://<redacted>",
44
"token_public_keys_url": "https://<redacted>",
55
"kafka_bootstrap_server": "localhost:9092",
6-
"event_bus_arn": "arn:aws:events:<redacted>"
6+
"event_bus_arn": "arn:aws:events:<redacted>",
7+
"ssl_ca_bundle": "/path/to/ca-bundle.pem"
78
}

scripts/notebook.ipynb

Lines changed: 0 additions & 170 deletions
This file was deleted.

scripts/prepare.deplyoment.sh

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/event_gate_lambda.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@
2323

2424
import boto3
2525
import jwt
26-
import urllib3
26+
from botocore.exceptions import BotoCoreError, NoCredentialsError
2727
from jsonschema import validate
2828
from jsonschema.exceptions import ValidationError
2929

3030
from src.handlers.handler_token import HandlerToken
31+
from src.utils.constants import SSL_CA_BUNDLE_KEY
3132
from src.writers import writer_eventbridge, writer_kafka, writer_postgres
3233
from src.utils.conf_path import CONF_DIR, INVALID_CONF_ENV
3334

3435
# Internal aliases used by rest of module
3536
_CONF_DIR = CONF_DIR
3637
_INVALID_CONF_ENV = INVALID_CONF_ENV
3738

38-
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
3939

4040
logger = logging.getLogger(__name__)
4141
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -64,8 +64,14 @@
6464
config = json.load(file)
6565
logger.debug("Loaded main CONFIG")
6666

67-
aws_s3 = boto3.Session().resource("s3", verify=False) # nosec Boto verify disabled intentionally
68-
logger.debug("Initialized AWS S3 Client")
67+
# Initialize S3 client with SSL verification
68+
try:
69+
ssl_verify = config.get(SSL_CA_BUNDLE_KEY, True)
70+
aws_s3 = boto3.Session().resource("s3", verify=ssl_verify)
71+
logger.debug("Initialized AWS S3 Client")
72+
except (BotoCoreError, NoCredentialsError) as exc:
73+
logger.exception("Failed to initialize AWS S3 client")
74+
raise RuntimeError("AWS S3 client initialization failed") from exc
6975

7076
if config["access_config"].startswith("s3://"):
7177
name_parts = config["access_config"].split("/")

src/handlers/handler_token.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030
from cryptography.hazmat.primitives import serialization
3131
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
3232

33-
from src.utils.constants import TOKEN_PROVIDER_URL_KEY, TOKEN_PUBLIC_KEYS_URL_KEY, TOKEN_PUBLIC_KEY_URL_KEY
33+
from src.utils.constants import (
34+
TOKEN_PROVIDER_URL_KEY,
35+
TOKEN_PUBLIC_KEYS_URL_KEY,
36+
TOKEN_PUBLIC_KEY_URL_KEY,
37+
SSL_CA_BUNDLE_KEY,
38+
)
3439

3540
logger = logging.getLogger(__name__)
3641
log_level = os.environ.get("LOG_LEVEL", "INFO")
@@ -49,6 +54,7 @@ def __init__(self, config):
4954
self.public_keys_url: str = config.get(TOKEN_PUBLIC_KEYS_URL_KEY) or config.get(TOKEN_PUBLIC_KEY_URL_KEY)
5055
self.public_keys: list[RSAPublicKey] = []
5156
self._last_loaded_at: datetime | None = None
57+
self.ssl_ca_bundle: str | bool = config.get(SSL_CA_BUNDLE_KEY, True)
5258

5359
def _refresh_keys_if_needed(self) -> None:
5460
"""
@@ -79,7 +85,7 @@ def load_public_keys(self) -> "HandlerToken":
7985
logger.debug("Loading token public keys from %s", self.public_keys_url)
8086

8187
try:
82-
response_json = requests.get(self.public_keys_url, verify=False, timeout=5).json()
88+
response_json = requests.get(self.public_keys_url, verify=self.ssl_ca_bundle, timeout=5).json()
8389
raw_keys: list[str] = []
8490

8591
if isinstance(response_json, dict):

src/utils/constants.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
This module contains all constants and enums used across the project.
1919
"""
2020

21-
# Token related configuration keys
21+
# Configuration keys
2222
TOKEN_PROVIDER_URL_KEY = "token_provider_url"
2323
TOKEN_PUBLIC_KEY_URL_KEY = "token_public_key_url"
2424
TOKEN_PUBLIC_KEYS_URL_KEY = "token_public_keys_url"
25+
SSL_CA_BUNDLE_KEY = "ssl_ca_bundle"

tests/handlers/test_handler_token.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,17 @@ def test_decode_jwt_triggers_refresh_check(token_handler):
142142
with patch("jwt.decode", return_value={"sub": "TestUser"}):
143143
token_handler.decode_jwt("dummy-token")
144144
mock_refresh.assert_called_once()
145+
146+
147+
def test_handler_token_default_ssl_ca_bundle():
148+
"""HandlerToken should default to True for ssl_ca_bundle when not specified."""
149+
config = {"token_public_keys_url": "https://example.com/keys"}
150+
handler = HandlerToken(config)
151+
assert handler.ssl_ca_bundle is True
152+
153+
154+
def test_handler_token_custom_ssl_ca_bundle_path():
155+
"""HandlerToken should accept custom CA bundle path."""
156+
config = {"token_public_keys_url": "https://example.com/keys", "ssl_ca_bundle": "/path/to/custom/ca-bundle.pem"}
157+
handler = HandlerToken(config)
158+
assert handler.ssl_ca_bundle == "/path/to/custom/ca-bundle.pem"

tests/test_event_gate_lambda.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
#
1616

1717
import json
18-
from unittest.mock import patch
18+
from unittest.mock import patch, MagicMock
1919

2020

2121
# --- GET flows ---
@@ -230,3 +230,31 @@ def test_post_invalid_json_body(event_gate_module, make_event):
230230
assert resp["statusCode"] == 500
231231
body = json.loads(resp["body"])
232232
assert any(e["type"] == "internal" for e in body["errors"]) # internal error path
233+
234+
235+
def test_boto3_s3_client_default_ssl_verification():
236+
"""Test that boto3 S3 client uses default SSL verification when ssl_ca_bundle not specified."""
237+
config = {}
238+
239+
with patch("boto3.Session") as mock_session:
240+
mock_session_instance = MagicMock()
241+
mock_session.return_value = mock_session_instance
242+
243+
ssl_verify = config.get("ssl_ca_bundle", True)
244+
mock_session_instance.resource("s3", verify=ssl_verify)
245+
246+
mock_session_instance.resource.assert_called_once_with("s3", verify=True)
247+
248+
249+
def test_boto3_s3_client_custom_ca_bundle():
250+
"""Test that boto3 S3 client uses custom CA bundle when ssl_ca_bundle is specified."""
251+
config = {"ssl_ca_bundle": "/path/to/custom-ca-bundle.pem"}
252+
253+
with patch("boto3.Session") as mock_session:
254+
mock_session_instance = MagicMock()
255+
mock_session.return_value = mock_session_instance
256+
257+
ssl_verify = config.get("ssl_ca_bundle", True)
258+
mock_session_instance.resource("s3", verify=ssl_verify)
259+
260+
mock_session_instance.resource.assert_called_once_with("s3", verify="/path/to/custom-ca-bundle.pem")

0 commit comments

Comments
 (0)