Skip to content

Commit ab822eb

Browse files
authored
Merge pull request #49 from vvangestel/feature/static-config
Read config from local json files to not be dependant on S3 rate limits during an attack
2 parents 7392dd4 + cad0e64 commit ab822eb

File tree

4 files changed

+33
-108
lines changed

4 files changed

+33
-108
lines changed

doc/readme.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ since it only contains non-secret information:
208208
* The expiration time of the access
209209

210210
* The username of the person that generated the token
211-
211+
212212
* A free-text field describing the granted access
213213

214214
Since the party issuing tokens, and the party verifying tokens are the same, we
@@ -231,13 +231,7 @@ Cache-key][CF-cache-cookie].
231231

232232
Normally, you can configure a generic Lambda function via Environment Variables.
233233
Lambda@Edge is tricky in this regard, since it does not support Environment
234-
Variables. We worked around this issue by abusing the Tags.
235-
236-
The configuration itself is stored in an S3 bucket as JSON files. The name of
237-
this S3 bucket is passed as a regular environment variable to the Python Lambda
238-
functions, but as a Tag to the Lambda@Edge function. The Lambda@Edge function
239-
performs a `lambda:GetFunction` on itself (it can find out its own name from the
240-
`context` object at runtime) to read its own Tags.
234+
Variables. In order protect this critical internet-facing infrastructure, we limit dependencies to other services. Config is stored locally and can be updated based on environment (account id) with specific config files. It is critical to keep this configuration in sync with reality.
241235

242236

243237
Code

templates/validator.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
"""
22
Validator stack.
33
"""
4-
from troposphere import Template, constants, Parameter, awslambda, Ref, Output, GetAtt
4+
from troposphere import Template, constants, Parameter, awslambda, Ref, Output
55

66
import custom_resources.awslambda
7-
import custom_resources.cloudformation
87
import cfnutils.output
98

109

@@ -36,31 +35,15 @@
3635
))
3736
template.set_parameter_label(param_s3_key, "Lambda S3 key")
3837

39-
param_config_bucket = template.add_parameter(Parameter(
40-
"ConfigBucket",
41-
Default="",
42-
Type=constants.STRING,
43-
Description="Name of the configuration bucket",
44-
))
45-
template.set_parameter_label(param_config_bucket, "Lambda Config S3 bucket")
46-
47-
cloudformation_tags = template.add_resource(custom_resources.cloudformation.Tags(
48-
"CfnTags",
49-
Set={
50-
'ConfigBucket': Ref(param_config_bucket),
51-
},
52-
))
53-
5438
validator_lambda = template.add_resource(awslambda.Function(
5539
"ValidatorLambda",
5640
Code=awslambda.Code(
5741
S3Bucket=Ref(param_s3_bucket_name),
5842
S3Key=Ref(param_s3_key),
5943
),
60-
Runtime='nodejs14.x',
44+
Runtime='nodejs22.x',
6145
Handler='index.handler',
6246
Role=Ref(param_role),
63-
Tags=GetAtt(cloudformation_tags, 'TagList'),
6447
))
6548

6649
validator_version = template.add_resource(custom_resources.awslambda.Version(

λ@E/config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"parameter_store_region": "eu-west-1",
3+
"parameter_store_parameter_name": "/authorizer/jwt-secret",
4+
"set_cookie_path": "/auth-89CE3FEF-FCF6-43B3-9DBA-7C410CAAE220/set-cookie",
5+
"cookie_name_access_token": "authorizer_access",
6+
"cookie_name_no_redirect": "authorizer_no_redirect",
7+
"authorize_url": "https://authorizer.example.org/authorize"
8+
}

λ@E/index.js

Lines changed: 21 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -12,77 +12,20 @@
1212
"use strict";
1313

1414
const JWT = require('jsonwebtoken');
15-
const AWS = require('aws-sdk');
15+
const { SSMClient, GetParameterCommand } = require('@aws-sdk/client-ssm');const fs = require('fs')
1616
const querystring = require('querystring');
1717
const url = require('url');
1818

1919

20-
function asyncLambdaGetFunction(param, service_param = {}) {
21-
// Async wrapper around the Lambda GetFunction API call
22-
return new Promise(function(resolve, reject) {
23-
const lambda = new AWS.Lambda(service_param);
24-
lambda.getFunction(param, function(err, data) {
25-
if(err !== null) { reject(err); }
26-
else { resolve(data); }
27-
});
28-
});
29-
}
30-
31-
function asyncS3GetObject(param) {
32-
// Async wrapper around the S3 GetObject API call
33-
return new Promise(function(resolve, reject) {
34-
const s3 = new AWS.S3();
35-
s3.getObject(param, function(err, data) {
36-
if(err !== null) { reject(err); }
37-
else { resolve(data); }
38-
});
39-
});
40-
}
41-
42-
async function get_config_bucket(context) {
43-
/* Lambda@Edge does not support environment parameters.
44-
* We use Tags as workaround. This function gets the value of the tag.
45-
*/
46-
const dot_location = context.functionName.indexOf('.');
47-
const functionName_without_region = context.functionName.substring(dot_location + 1);
48-
const lambda_description = await asyncLambdaGetFunction({
49-
'FunctionName': functionName_without_region,
50-
}, {
51-
region: 'us-east-1', // Lambda@Edge is always us-east-1
52-
}
53-
);
54-
return lambda_description.Tags['ConfigBucket'];
55-
}
5620
async function get_config_(context) {
57-
let config = { // Default settings, keep in sync with Lambda-code!
58-
'function_arn': context['invokedFunctionArn'],
59-
60-
'parameter_store_region': 'eu-west-1',
61-
'parameter_store_parameter_name': '/authorizer/jwt-secret',
62-
63-
'set_cookie_path': '/auth-89CE3FEF-FCF6-43B3-9DBA-7C410CAAE220/set-cookie',
64-
'cookie_name_access_token': 'authorizer_access',
65-
'cookie_name_no_redirect': 'authorizer_no_redirect',
66-
67-
'authorize_url': 'https://authorizer.example.org/authorize',
21+
const accountId = context.invokedFunctionArn.split(':')[4];
22+
const accountSpecificConfigPath = `./config-${accountId}.json`;
23+
let config = require('./config.json');
24+
if(fs.existsSync(accountSpecificConfigPath)) {
25+
let accountSpecificConfig = require(accountSpecificConfigPath);
26+
config = { ...config, ...accountSpecificConfig};
6827
};
69-
const config_bucket = await get_config_bucket(context);
70-
try {
71-
const config_response = await asyncS3GetObject({
72-
'Bucket': config_bucket,
73-
'Key': 'config.json',
74-
});
75-
const body = config_response.Body.toString('utf-8');
76-
console.log("Retrieved config from S3:");
77-
console.log(body);
78-
const parsed_body = JSON.parse(body);
79-
for(let key in parsed_body) {
80-
config[key] = parsed_body[key];
81-
}
82-
} catch(e) {
83-
console.log("Could not retrieve config from S3. Using defaults.");
84-
console.log(e);
85-
}
28+
config['function_arn'] = context['invokedFunctionArn'];
8629
return config;
8730
}
8831
function get_config_promise(context) {
@@ -92,22 +35,19 @@ function get_config_promise(context) {
9235
return get_config_promise.cache
9336
}
9437

95-
function get_jwt_secret_promise(region, param_name) {
96-
if(typeof get_jwt_secret_promise.cache === 'undefined') {
97-
get_jwt_secret_promise.cache = new Promise(function(resolve, reject) {
98-
const ssm = new AWS.SSM({
99-
'region': region,
100-
});
101-
ssm.getParameter({
102-
'Name': param_name,
103-
'WithDecryption': true,
104-
},
105-
function(err, data) {
106-
if(err !== null) { reject(err); }
107-
else { resolve(data['Parameter']['Value']); }
108-
}
109-
);
110-
});
38+
async function getSSMParameter(parameter_name, parameter_region) {
39+
const client = new SSMClient({ region: parameter_region });
40+
const command = new GetParameterCommand({
41+
Name: parameter_name,
42+
WithDecryption: true,
43+
});
44+
const response = await client.send(command);
45+
return response.Parameter.Value;
46+
}
47+
48+
async function get_jwt_secret_promise(region, param_name) {
49+
if (typeof get_jwt_secret_promise.cache === 'undefined') {
50+
get_jwt_secret_promise.cache = await getSSMParameter(param_name, region)
11151
}
11252
return get_jwt_secret_promise.cache;
11353
}

0 commit comments

Comments
 (0)