Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move from API keys to route-based authentication for HTTP gateways #1418

Merged
merged 12 commits into from
Sep 9, 2022
Merged
2 changes: 0 additions & 2 deletions packages/airnode-deployer/config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@
},
"httpGateway": {
"enabled": true,
"apiKey": "${HTTP_GATEWAY_API_KEY}",
"maxConcurrency": 20,
"corsOrigins": []
},
"httpSignedDataGateway": {
"enabled": true,
"apiKey": "${HTTP_SIGNED_DATA_GATEWAY_API_KEY}",
"maxConcurrency": 20,
"corsOrigins": []
},
Expand Down
3 changes: 0 additions & 3 deletions packages/airnode-deployer/config/secrets.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ PROVIDER_URL=http://127.0.0.1:8545

HEARTBEAT_API_KEY=00000000-0000-0000-0000-000000000000
HEARTBEAT_URL=https://example.com/convert

HTTP_GATEWAY_API_KEY=12345678-0000-0000-0000-000000000000
HTTP_SIGNED_DATA_GATEWAY_API_KEY=87654321-0000-0000-0000-000000000000
30 changes: 0 additions & 30 deletions packages/airnode-deployer/src/handlers/gcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Request, Response } from '@google-cloud/functions-framework/build/src/f
import {
handlers,
providers,
config,
InitializeProviderPayload,
CallApiPayload,
ProcessTransactionsPayload,
Expand All @@ -12,7 +11,6 @@ import {
EnabledGateway,
verifyHttpSignedDataRequest,
verifyHttpRequest,
VerificationResult,
verifyRequestOrigin,
} from '@api3/airnode-node';
import { logger, randomHexString, setLogOptions, addMetadata, caching } from '@api3/airnode-utilities';
Expand Down Expand Up @@ -111,20 +109,6 @@ async function processTransactions(payload: ProcessTransactionsPayload, res: Res
res.status(200).send(body);
}

// We need to check for an API key manually because GCP HTTP Gateway doesn't support managing API keys via API
function verifyGcpApiKey(
req: Request,
apiKeyName: 'HTTP_GATEWAY_API_KEY' | 'HTTP_SIGNED_DATA_GATEWAY_API_KEY'
): VerificationResult<{}> {
const apiKey = req.header('x-api-key');
if (!apiKey || apiKey !== config.getEnvValue(apiKeyName)) {
// Mimics the behavior of AWS HTTP Gateway
return { success: false, statusCode: 403, error: { message: 'Forbidden' } };
}

return { success: true };
}

// We do not want to enable ".strict()" - we want to allow extra fields in the request body
const httpRequestBodySchema = z.object({
parameters: z.any(), // Parameter validation is performed later
Expand Down Expand Up @@ -154,13 +138,6 @@ export async function processHttpRequest(req: Request, res: Response) {
// Set headers for the responses
res.set(originVerification.headers);

const apiKeyVerification = verifyGcpApiKey(req, 'HTTP_GATEWAY_API_KEY');
if (!apiKeyVerification.success) {
const { statusCode, error } = apiKeyVerification;
res.status(statusCode).send(error);
return;
}

const parsedBody = httpRequestBodySchema.safeParse(req.body);
if (!parsedBody.success) {
// This error and status code is returned by AWS gateway when the request does not match the openAPI
Expand Down Expand Up @@ -224,13 +201,6 @@ export async function processHttpSignedDataRequest(req: Request, res: Response)
// Set headers for the responses
res.set(originVerification.headers);

const apiKeyVerification = verifyGcpApiKey(req, 'HTTP_SIGNED_DATA_GATEWAY_API_KEY');
if (!apiKeyVerification.success) {
const { statusCode, error } = apiKeyVerification;
res.status(statusCode).send(error);
return;
}

const parsedBody = httpSignedDataBodySchema.safeParse(req.body);
if (!parsedBody.success) {
// This error and status code is returned by AWS gateway when the request does not match the openAPI
Expand Down
4 changes: 2 additions & 2 deletions packages/airnode-deployer/src/infrastructure/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,14 +186,14 @@ async function terraformAirnodeManage(
}

if (httpGateway?.enabled) {
commonArguments.push(['var', 'http_api_key', httpGateway.apiKey!]);
commonArguments.push(['var', 'http_gateway_enabled', 'true']);
if (httpGateway.maxConcurrency) {
commonArguments.push(['var', 'http_max_concurrency', `${httpGateway.maxConcurrency}`]);
}
}

if (httpSignedDataGateway?.enabled) {
commonArguments.push(['var', 'http_signed_data_api_key', httpSignedDataGateway.apiKey!]);
commonArguments.push(['var', 'http_signed_data_gateway_enabled', 'true']);
if (httpSignedDataGateway.maxConcurrency) {
commonArguments.push(['var', 'http_signed_data_max_concurrency', `${httpSignedDataGateway.maxConcurrency}`]);
}
Expand Down
22 changes: 14 additions & 8 deletions packages/airnode-deployer/terraform/airnode/aws/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
# * switch between local and remote lambda source
# * variable validation

resource "random_uuid" "http_path_key" {
}

resource "random_uuid" "http_signed_data_path_key" {
}

module "run" {
source = "./modules/function"

Expand Down Expand Up @@ -31,8 +37,8 @@ module "startCoordinator" {
secrets_file = var.secrets_file

environment_variables = {
HTTP_GATEWAY_URL = var.http_api_key == null ? null : "${module.httpGw[0].api_url}"
HTTP_SIGNED_DATA_GATEWAY_URL = var.http_signed_data_api_key == null ? null : "${module.httpSignedGw[0].api_url}"
HTTP_GATEWAY_URL = var.http_gateway_enabled == false ? null : "${module.httpGw[0].api_url}/${random_uuid.http_path_key.result}"
HTTP_SIGNED_DATA_GATEWAY_URL = var.http_signed_data_gateway_enabled == false ? null : "${module.httpSignedGw[0].api_url}/${random_uuid.http_signed_data_path_key.result}"
AIRNODE_WALLET_PRIVATE_KEY = var.airnode_wallet_private_key
}

Expand All @@ -45,7 +51,7 @@ module "startCoordinator" {

module "httpReq" {
source = "./modules/function"
count = var.http_api_key == null ? 0 : 1
count = var.http_gateway_enabled == false ? 0 : 1

name = "${local.name_prefix}-httpReq"
handler = "index.httpReq"
Expand All @@ -59,24 +65,24 @@ module "httpReq" {

module "httpGw" {
source = "./modules/apigateway"
count = var.http_api_key == null ? 0 : 1
count = var.http_gateway_enabled == false ? 0 : 1

name = "${local.name_prefix}-httpGw"
stage = "v1"
template_file = "./templates/httpGw.yaml.tpl"
template_variables = {
proxy_lambda = module.httpReq[0].lambda_arn
region = var.aws_region
path_key = random_uuid.http_path_key.result
}
lambdas = [
module.httpReq[0].lambda_arn
]
api_key = var.http_api_key
}

module "httpSignedReq" {
source = "./modules/function"
count = var.http_signed_data_api_key == null ? 0 : 1
count = var.http_signed_data_gateway_enabled == false ? 0 : 1

name = "${local.name_prefix}-httpSignedReq"
handler = "index.httpSignedReq"
Expand All @@ -94,17 +100,17 @@ module "httpSignedReq" {

module "httpSignedGw" {
source = "./modules/apigateway"
count = var.http_signed_data_api_key == null ? 0 : 1
count = var.http_signed_data_gateway_enabled == false ? 0 : 1

name = "${local.name_prefix}-httpSignedGw"
stage = "v1"
template_file = "./templates/httpSignedGw.yaml.tpl"
template_variables = {
proxy_lambda = module.httpSignedReq[0].lambda_arn
region = var.aws_region
path_key = random_uuid.http_signed_data_path_key.result
}
lambdas = [
module.httpSignedReq[0].lambda_arn
]
api_key = var.http_signed_data_api_key
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,3 @@ resource "aws_api_gateway_usage_plan" "usage_plan" {
stage = aws_api_gateway_stage.stage.stage_name
}
}

resource "aws_api_gateway_api_key" "api_key" {
name = var.name
value = var.api_key
}

resource "aws_api_gateway_usage_plan_key" "main" {
key_id = aws_api_gateway_api_key.api_key.id
key_type = "API_KEY"
usage_plan_id = aws_api_gateway_usage_plan.usage_plan.id
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,3 @@ variable "lambdas" {
type = list(string)
default = []
}

variable "api_key" {
description = "API key to access the APIGateway"
type = string
}
4 changes: 2 additions & 2 deletions packages/airnode-deployer/terraform/airnode/aws/outputs.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
output "http_gateway_url" {
value = var.http_api_key == null ? null : "${module.httpGw[0].api_url}"
value = var.http_gateway_enabled == false ? null : "${module.httpGw[0].api_url}/${random_uuid.http_path_key.result}"
}

output "http_signed_data_gateway_url" {
value = var.http_signed_data_api_key == null ? null : "${module.httpSignedGw[0].api_url}"
value = var.http_signed_data_gateway_enabled == false ? null : "${module.httpSignedGw[0].api_url}/${random_uuid.http_signed_data_path_key.result}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,8 @@ components:
example:
$ref: "#/components/examples/EndpointIdParameterExample"

securitySchemes:
apiKey:
description: Airnode API Gateway API key
type: apiKey
name: x-api-key
in: header

paths:
/{endpointId}:
/${path_key}/{endpointId}:
post:
parameters:
- $ref: "#/components/parameters/endpointId"
Expand Down Expand Up @@ -92,8 +85,6 @@ paths:
examples:
example:
$ref: "#/components/examples/EndpointResponseExample"
security:
- apiKey: []
x-amazon-apigateway-integration:
type: aws_proxy
uri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${proxy_lambda}/invocations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,8 @@ components:
example:
$ref: "#/components/examples/EndpointIdParameterExample"

securitySchemes:
apiKey:
description: Airnode API Gateway API key
type: apiKey
name: x-api-key
in: header

paths:
/{endpointId}:
/${path_key}/{endpointId}:
post:
parameters:
- $ref: "#/components/parameters/endpointId"
Expand Down Expand Up @@ -86,8 +79,6 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/EndpointResponse"
security:
- apiKey: []
x-amazon-apigateway-integration:
type: aws_proxy
uri: arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${proxy_lambda}/invocations
Expand Down
14 changes: 6 additions & 8 deletions packages/airnode-deployer/terraform/airnode/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,9 @@ variable "disable_concurrency_reservation" {
default = false
}

variable "http_api_key" {
description = "API key to access Airnode HTTP Gateway"
type = string
default = null
variable "http_gateway_enabled" {
description = "Flag to enable HTTP Gateway"
default = false
}

variable "http_max_concurrency" {
Expand All @@ -62,10 +61,9 @@ variable "http_max_concurrency" {
default = -1
}

variable "http_signed_data_api_key" {
description = "API key to access Airnode Signed Data Gateway"
type = string
default = null
variable "http_signed_data_gateway_enabled" {
description = "Flag to enable Signed Data Gateway"
default = false
}

variable "http_signed_data_max_concurrency" {
Expand Down
Loading