Skip to content
Open
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
96 changes: 76 additions & 20 deletions postcode_lookup/lambda_basic_auth.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,77 @@
import ipaddress
import os

DC_AUTH = "Basic ZGM6ZGM=" # dc:dc
ALLOWLIST = os.environ.get("IP_ALLOWLIST", "")


def lambda_handler(event, context):
headers = event.get("headers", {})
auth = headers.get("Authorization")
dc_auth = "Basic ZGM6ZGM=" # dc:dc in base64

if auth == dc_auth:
return {
"principalId": "dc",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "*",
}
],
},
}

raise Exception("Unauthorized")
headers = event.get("headers") or {}

xff = headers.get("X-Forwarded-For", "")
client_ip = xff.split(",")[0].strip() if xff else ""

print(headers)
print(client_ip)
print(ALLOWLIST)
print(_ip_in_allowlist(client_ip))

if client_ip and _ip_in_allowlist(client_ip):
print("Returning a valid policy")
return _allow("ip-allowlisted")

auth = headers.get("Authorization") or headers.get("authorization")
if auth == DC_AUTH:
return _allow("basic-auth")

print("Not authed, raising")
return {
"principalId": "anonymous",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Deny",
"Resource": "*",
}
],
},
}


def _ip_in_allowlist(client_ip: str) -> bool:
try:
ip = ipaddress.ip_address(client_ip)
except ValueError:
return False

for item in [x.strip() for x in ALLOWLIST.split(",") if x.strip()]:
try:
net = (
ipaddress.ip_network(item, strict=False)
if "/" in item
else ipaddress.ip_network(item + "/32")
)
if ip in net:
return True
except ValueError:
pass

return False


def _allow(principal_id: str):
return {
"principalId": principal_id,
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "*",
}
],
},
}
24 changes: 21 additions & 3 deletions template.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform:
Transform:
- AWS::LanguageExtensions
- AWS::Serverless-2016-10-31
Description: "EC Postcode Lookup app: Lambda, API Gateway"
Expand Down Expand Up @@ -42,6 +42,11 @@ Parameters:
Description: "The DC_ENVIRONMENT environment variable passed to the app."
Type: AWS::SSM::Parameter::Value<String>

IPAllowlist:
Default: IP_ALLOWLIST
Description: "Comma-separated list of IPs allowed without basic auth"
Type: AWS::SSM::Parameter::Value<String>

Conditions:
UseBasicAuth: !Or
- !Equals [ !Ref DCEnvironment, development ]
Expand Down Expand Up @@ -117,8 +122,8 @@ Resources:
FunctionPayloadType: REQUEST
Identity:
Headers:
- Authorization
ReauthorizeEvery: 3600
- X-Forwarded-For
ReauthorizeEvery: 0

BasicAuthGatewayResponse:
Condition: UseBasicAuth
Expand All @@ -130,6 +135,16 @@ Resources:
RestApiId: !Ref ECPostcodeLookupFunctionApiGateway
StatusCode: '401'


AccessDeniedGatewayResponse:
Condition: UseBasicAuth
Type: AWS::ApiGateway::GatewayResponse
Properties:
ResponseParameters:
gatewayresponse.header.www-authenticate: "'Basic realm=\"Restricted\"'"
ResponseType: ACCESS_DENIED
RestApiId: !Ref ECPostcodeLookupFunctionApiGateway
StatusCode: '401'
BasicAuthFunction:
Type: AWS::Serverless::Function
Metadata:
Expand All @@ -138,6 +153,9 @@ Resources:
CodeUri: ./postcode_lookup/
Handler: lambda_basic_auth.lambda_handler
Runtime: python3.12
Environment:
Variables:
IP_ALLOWLIST: !Ref IPAllowlist

FailOver:
Type: AWS::S3::Bucket
Expand Down
Loading