diff --git a/src/azul/terraform.py b/src/azul/terraform.py index 4c553eff2..a48c99906 100644 --- a/src/azul/terraform.py +++ b/src/azul/terraform.py @@ -800,11 +800,32 @@ def tf_config(self, app_name): # Setting this property using AWS API Gateway extensions to the OpenAPI # specification works around this issue. # + # We also use AWS API extensions to set the API Gateway default + # responses since, unlike Terraform, it automatically triggers the + # deployment of the REST API when the Gateway responses are added and/or + # modified. + # rest_api = resources['aws_api_gateway_rest_api'][app_name] assert 'minimum_compression_size' not in rest_api, rest_api + assert 'aws_api_gateway_gateway_response' not in resources, resources openapi_spec = json.loads(locals[app_name]) key = 'x-amazon-apigateway-minimum-compression-size' openapi_spec[key] = config.minimum_compression_size + openapi_spec['x-amazon-apigateway-gateway-responses'] = { + f'DEFAULT_{response_type}': { + 'responseParameters': { + 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'") + ] + } + } for response_type in ['4XX', '5XX'] + } locals[app_name] = json.dumps(openapi_spec) return { diff --git a/test/integration_test.py b/test/integration_test.py index 537690956..d8aedff26 100644 --- a/test/integration_test.py +++ b/test/integration_test.py @@ -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())