Skip to content

Commit 96da806

Browse files
WIP :-(
1 parent f6cc307 commit 96da806

File tree

4 files changed

+196
-25
lines changed

4 files changed

+196
-25
lines changed

src/eligibility_signposting_api/repos/factory.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,9 @@ def firehose_client_factory(
4343
) -> BaseClient:
4444
endpoint_url = str(firehose_endpoint) if firehose_endpoint is not None else None
4545
return session.client("firehose", endpoint_url=endpoint_url)
46+
47+
48+
# @service(qualifier="secretsmanager")
49+
# def secretsmanager_client_factory(session: Session) -> BaseClient:
50+
# return session.client("secretsmanager")
51+
Lines changed: 83 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,90 @@
11
from wireup import service
2+
import boto3
3+
from botocore.client import BaseClient
4+
from botocore.exceptions import ClientError
5+
from wireup import Inject, service
6+
from typing import Annotated
27

3-
@service(qualifier="nhs_hmac_key")
4-
def nhs_hmac_key_factory() -> bytes:
5-
return b"abc123" # salt
8+
from boto3 import Session
69

7-
# import boto3
8-
# from botocore.exceptions import ClientError
10+
# @service(qualifier="nhs_hmac_key")
11+
# def nhs_hmac_key_factory() -> bytes: # -> dict[str, bytes]:
12+
# # return b"abc123"
13+
# keys: dict[str, bytes] = {}
14+
# #return keys
915
#
10-
# secret_name = "eligibility-signposting-api-dev/hashing_secret"
11-
# region_name = "eu-west-2"
16+
# # return {
17+
# # "AWSCURRENT": b"current_dummy_key",
18+
# # "AWSPREVIOUS": b"previous_dummy_key"
19+
# # }
1220
#
13-
# # Create a Secrets Manager client
14-
# session = boto3.session.Session()
15-
# client = session.client(
16-
# service_name='secretsmanager',
17-
# region_name=region_name
18-
# )
21+
# for stage in ("AWSCURRENT", "AWSPREVIOUS"):
22+
# value = get_secret_by_stage()
23+
# if value:
24+
# keys[stage] = value.encode("utf-8")
1925
#
20-
# try:
21-
# get_secret_value_response = client.get_secret_value(
22-
# SecretId=secret_name
23-
# )
24-
# except ClientError as e:
25-
# # For a list of exceptions thrown, see
26-
# # https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
27-
# raise e
26+
# # if keys = {}, then raise error
27+
# if not keys:
28+
# raise RuntimeError("No valid NHS HMAC secret keys available")
2829
#
29-
# secret = get_secret_value_response['SecretString']
30+
# return keys
31+
32+
@service(qualifier="secretsmanager")
33+
def secretsmanager_client_factory(session: Session) -> BaseClient:
34+
return session.client("secretsmanager")
35+
36+
37+
@service(qualifier="nhs_hmac_key")
38+
def nhs_hmac_key_factory(
39+
secrets_client: Annotated[BaseClient, Inject(qualifier="secretsmanager")],
40+
) -> bytes:
41+
keys: dict[str, bytes] = {}
42+
43+
for stage in ("AWSCURRENT", "AWSPREVIOUS"):
44+
value = get_secret_by_stage(secrets_client)
45+
if value:
46+
keys[stage] = value.encode("utf-8")
47+
48+
if not keys:
49+
raise RuntimeError("No valid NHS HMAC secret keys available")
50+
51+
return keys
52+
53+
54+
def get_secret_by_stage(secrets_client: BaseClient
55+
#secrets_client: Annotated[BaseClient, Inject(qualifier="secretsmanager")],
56+
) -> str:
57+
secret_name = "eligibility-signposting-api-dev/hashing_secret"
58+
region_name = "eu-west-2"
59+
# client = boto3.client("secretsmanager", region_name=region_name)
60+
stage="AWSCURRENT"
61+
try:
62+
secrets_client.describe_secret(SecretId=secret_name)
63+
except ClientError as e:
64+
raise RuntimeError(f"Secret '{secret_name}' does not exist") from e
65+
66+
version_stages = ["AWSCURRENT", "AWSPREVIOUS"] # add "AWSPENDING" later
67+
68+
#for stage in version_stages:
69+
try:
70+
response = secrets_client.get_secret_value(
71+
SecretId=secret_name,
72+
VersionStage=stage
73+
)
74+
secret = response.get("SecretString") # response.get("SecretBinary")
75+
if secret:
76+
return secret
77+
# except ClientError as e:
78+
# raise e # Needs expanding as it would raise after 1 iteration currently
79+
80+
except ClientError as e:
81+
if e.response["Error"]["Code"] not in (
82+
"ResourceNotFoundException",
83+
"ResourceNotFound",
84+
"ValidationException",
85+
):
86+
raise e
87+
88+
raise RuntimeError(f"No valid version found for secret '{secret_name}'")
89+
90+

test.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#!/usr/bin/env python3
2+
3+
import boto3
4+
from botocore.exceptions import ClientError
5+
6+
7+
def get_secret_by_stage(
8+
stage: str,
9+
secret_name: str = "eligibility-signposting-api-dev/hashing_secret",
10+
region_name: str = "eu-west-2"
11+
) -> str:
12+
"""
13+
Fetch a specific version of a secret from AWS Secrets Manager
14+
by stage (AWSCURRENT or AWSPREVIOUS).
15+
"""
16+
17+
# Ensure fixed values override passed parameters
18+
secret_name = "eligibility-signposting-api-dev/hashing_secret"
19+
region_name = "eu-west-2"
20+
21+
client = boto3.client("secretsmanager", region_name=region_name)
22+
23+
# First check secret exists
24+
try:
25+
client.describe_secret(SecretId=secret_name)
26+
except ClientError as e:
27+
error_code = e.response["Error"].get("Code")
28+
error_msg = e.response["Error"].get("Message")
29+
raise RuntimeError(
30+
f"Unable to describe secret '{secret_name}'. "
31+
f"AWS ErrorCode={error_code}, Message={error_msg}"
32+
) from e
33+
34+
# Try to fetch the specified stage
35+
try:
36+
response = client.get_secret_value(
37+
SecretId=secret_name,
38+
VersionStage=stage,
39+
)
40+
print(response)
41+
secret = response.get("SecretString")
42+
43+
if secret:
44+
return secret
45+
46+
except ClientError as e:
47+
err = e.response.get("Error", {})
48+
code = err.get("Code")
49+
msg = err.get("Message")
50+
status = e.response.get("ResponseMetadata", {}).get("HTTPStatusCode")
51+
52+
# These usually mean: the secret exists, but that *stage* does not
53+
if code in (
54+
"ResourceNotFoundException",
55+
"ResourceNotFound",
56+
"ValidationException",
57+
):
58+
raise RuntimeError(
59+
f"No version found for stage '{stage}' on secret '{secret_name}'.\n"
60+
f"- AWS ErrorCode: {code}\n"
61+
f"- AWS Message: {msg}\n"
62+
f"- HTTP Status: {status}"
63+
) from e
64+
65+
# Anything else is unexpected → bubble up with details
66+
raise RuntimeError(
67+
f"Error calling GetSecretValue for stage '{stage}' on secret '{secret_name}'.\n"
68+
f"- AWS ErrorCode: {code}\n"
69+
f"- AWS Message: {msg}\n"
70+
f"- HTTP Status: {status}"
71+
) from e
72+
73+
74+
75+
76+
# Optional: allow CLI execution for quick testing
77+
if __name__ == "__main__":
78+
print("Testing get_secret_by_stage:")
79+
try:
80+
current = get_secret_by_stage("AWSCURRENT")
81+
print("AWSCURRENT =", current)
82+
except Exception as e:
83+
print("Failed to fetch AWSCURRENT:", e)
84+
85+
try:
86+
previous = get_secret_by_stage("AWSPREVIOUS")
87+
print("AWSPREVIOUS =", previous)
88+
except Exception as e:
89+
print("Failed to fetch AWSPREVIOUS:", e)

tests/unit/repos/test_secrets.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,22 @@
44

55

66
def test_nhs_hmac_key_factory_returns_bytes():
7-
key = nhs_hmac_key_factory()
7+
keys = nhs_hmac_key_factory()
8+
assert True
89

9-
assert isinstance(key, bytes)
10-
assert key == b"abc123"
10+
11+
# from moto import mock_aws
12+
# import boto3
13+
#
14+
# @mock_aws
15+
# def test_secret():
16+
# client = boto3.client("secretsmanager", region_name="us-east-1")
17+
#
18+
# client.create_secret(
19+
# Name="my-secret",
20+
# SecretString="value"
21+
# )
22+
#
23+
# resp = client.get_secret_value(SecretId="my-secret")
24+
#
25+
# assert resp["SecretString"] == "value"

0 commit comments

Comments
 (0)