Skip to content

Commit

Permalink
Fix: 4xx and 5xx responses from API Gateway cause Invicti findings (#154
Browse files Browse the repository at this point in the history
)
  • Loading branch information
dsotirho-ucsc committed Jul 16, 2024
1 parent 949f777 commit add5062
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 1 deletion.
14 changes: 13 additions & 1 deletion src/azul/terraform.py
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,19 @@ def tf_config(self, app_name):
del deployment['lifecycle']['create_before_destroy']
assert not deployment['lifecycle'], deployment
del deployment['lifecycle']
deployment['triggers'] = {'redeployment': deployment.pop('stage_description')}
# The gateway deployment not only depends on the gateway responses, but
# also needs to trigger a redeployment of the REST API when the
# responses are updated in order for those responses to be applied.
depends_on = [
'aws_api_gateway_gateway_response.%s_%s' % (app_name, response_type)
for response_type in ('4XX', '5XX')
]
assert 'depends_on' not in deployment
deployment['depends_on'] = depends_on
deployment['triggers'] = {
'redeployment': deployment.pop('stage_description'),
**{v: '${sha1(jsonencode(%s))}' % v for v in depends_on}
}

return {
'resource': resources,
Expand Down
31 changes: 31 additions & 0 deletions terraform/api_gateway.tf.json.template.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,37 @@ def for_domain(cls, domain):
}
} for i, domain in enumerate(app.domains)
},
'aws_api_gateway_gateway_response': {
f'{app.name}_{response_type}': {
'rest_api_id': '${aws_api_gateway_rest_api.%s.id}' % app.name,
'response_type': f'DEFAULT_{response_type}',
'response_parameters': {
f'gatewayresponse.header.{k}': v
for k, v in [
('Content-Security-Policy', "'default-src \'self\''"),
('X-Content-Type-Options', "'nosniff'"),
('X-Frame-Options', "'DENY'"),
('Referrer-Policy', "'strict-origin-when-cross-origin'"),
('Strict-Transport-Security', "'max-age=63072000; includeSubDomains; preload'"),
('X-XSS-Protection', "'1; mode=block'")
]
},
'response_templates': {
'application/json': '{"message":$context.error.messageString}'
},
'lifecycle': {
# When the rest_api body is updated with new API
# definitions, a PutRestApi action is performed that
# causes the gateway_response to be cleared, hence
# the need to trigger a replacement of this
# resource when the rest_api body is updated.
'replace_triggered_by': [
'aws_api_gateway_rest_api.%s.id' % app.name,
'aws_api_gateway_rest_api.%s.body' % app.name
]
}
} for response_type in ['4XX', '5XX']
},
'aws_acm_certificate': {
f'{app.name}_{i}': {
'domain_name': domain,
Expand Down
15 changes: 15 additions & 0 deletions test/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2093,3 +2093,18 @@ def test_response_security_headers(self):
response.raise_for_status()
expected = expected_headers | global_headers
self.assertIsSubset(expected.items(), response.headers.items())

def test_default_4xx_response_headers(self):
headers = {
'Content-Security-Policy': "default-src 'self'",
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',
'X-XSS-Protection': '1; mode=block'
}
for endpoint in (config.service_endpoint, config.indexer_endpoint):
with self.subTest(endpoint=endpoint):
response = requests.get(str(endpoint / 'does-not-exist'))
self.assertEqual(403, response.status_code)
self.assertIsSubset(headers.items(), response.headers.items())

0 comments on commit add5062

Please sign in to comment.