Skip to content

Commit

Permalink
- Fixed failing build in Publish Lambda Functions GHA
Browse files Browse the repository at this point in the history
- Added Authorizer and Rotate Key Lambda Functions
- Update Terraform code to support Authorizer
- Added implementation to restart ECS service when key is rotated
- Updates to TF to allow lambda to update secret and restart ECS service
- Use x-origin-verify header and pass to http client
- Update devcontainer to force .tf files to be formatted by TF extension
- Added APIGW Settings for logging
  • Loading branch information
Ronaldo Macapobre committed Nov 15, 2024
1 parent 40113ca commit 3deed24
Show file tree
Hide file tree
Showing 29 changed files with 1,374 additions and 278 deletions.
13 changes: 11 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@
"ms-dotnettools.csdevkit",
"ms-dotnettools.csharp",
"ms-dotnettools.vscode-dotnet-runtime",
"amazonwebservices.aws-toolkit-vscode"
"amazonwebservices.aws-toolkit-vscode",
"hashicorp.terraform"
],
"settings": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
},
"editor.formatOnSave": true
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[csharp]": {
"editor.defaultFormatter": "ms-dotnettools.csharp"
},
"[terraform]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "hashicorp.terraform"
}
}
}
},
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/actions/deploy-lambda/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ runs:
id: ecr-check
shell: bash
run: |
IMAGE_TAG=${{ inputs.lambda_name }}-${{ inputs.short_sha }}
IMAGE_TAG=${{ inputs.image_name }}-${{ inputs.short_sha }}
REPOSITORY_NAME=${{ inputs.app_name }}-lambda-repo-${{ inputs.environment }}
IMAGE_EXISTS=$(aws ecr describe-images --repository-name $REPOSITORY_NAME --query "imageDetails[?contains(imageTags, '$IMAGE_TAG')]" --output text)
Expand All @@ -94,4 +94,4 @@ runs:
run: |
aws lambda update-function-code \
--function-name ${{ inputs.app_name }}-${{ inputs.lambda_name }}-lambda-${{ inputs.environment }} \
--image-uri ${{ env.full_ecr_repo_url }}:${{ inputs.resource }}.${{ inputs.lambda_name }}-${{ inputs.short_sha }}
--image-uri ${{ steps.vars.outputs.full_ecr_repo_url }}:${{ inputs.image_name }}-${{ inputs.short_sha }}
7 changes: 5 additions & 2 deletions .github/workflows/publish-lambdas.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
lambda_dir_list: ${{ steps.convert.outputs.LAMBDA_DIR_LIST }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
Expand All @@ -51,6 +52,8 @@ jobs:
deploy2gchr:
needs: get-lambdas
environment: ${{ inputs.environment }}
outputs:
short_sha: ${{ steps.short_sha.outputs.SHORT_SHA }}
permissions:
id-token: write
packages: write
Expand Down Expand Up @@ -82,7 +85,7 @@ jobs:
- name: Get short SHA
id: short_sha
run: |
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Parse Resource and Lambda Name
id: parse
Expand All @@ -100,7 +103,7 @@ jobs:
images: |
${{ env.GITHUB_IMAGE_REPO }}/${{ env.RESOURCE }}.${{ env.LAMBDA }}
tags: |
type=raw,value=${{ env.SHORT_SHA }}
type=raw,value=${{ steps.short_sha.outputs.SHORT_SHA }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
Expand Down
10 changes: 8 additions & 2 deletions api/Infrastructure/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ namespace Scv.Api.Infrastructure
{
public static class ServiceCollectionExtensions
{
const string X_APIGW_KEY_HEADER = "x-api-key";
const string X_ORIGIN_VERIFY_HEADER = "x-origin-verify";

public static IServiceCollection AddMapster(this IServiceCollection services, Action<TypeAdapterConfig> options = null)
{
var config = TypeAdapterConfig.GlobalSettings;
Expand All @@ -37,6 +40,9 @@ public static IServiceCollection AddMapster(this IServiceCollection services, Ac

public static IServiceCollection AddHttpClientsAndScvServices(this IServiceCollection services, IConfiguration configuration)
{
var apigwKey = configuration.GetNonEmptyValue("AWS_API_GATEWAY_API_KEY");
var authorizerKey = configuration.GetNonEmptyValue("AuthorizerKey");

services.AddTransient<TimingHandler>();
services.AddHttpClient<FileServicesClient>(client =>
{
Expand All @@ -61,8 +67,8 @@ public static IServiceCollection AddHttpClientsAndScvServices(this IServiceColle
// configuration.GetNonEmptyValue("LocationServicesClient:Username"),
// configuration.GetNonEmptyValue("LocationServicesClient:Password"));
client.BaseAddress = new Uri(configuration.GetNonEmptyValue("LocationServicesClient:Url").EnsureEndingForwardSlash());
var apiKey = configuration.GetNonEmptyValue("AWS_API_GATEWAY_API_KEY");
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
client.DefaultRequestHeaders.Add(X_APIGW_KEY_HEADER, apigwKey);
client.DefaultRequestHeaders.Add(X_ORIGIN_VERIFY_HEADER, authorizerKey);
}).AddHttpMessageHandler<TimingHandler>();

services.AddHttpClient<UserServiceClient>(client =>
Expand Down
3 changes: 3 additions & 0 deletions aws/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"semi": true
}
24 changes: 0 additions & 24 deletions aws/helpers/getSecret.ts

This file was deleted.

105 changes: 105 additions & 0 deletions aws/lambdas/auth/authorizer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Logger } from "@aws-lambda-powertools/logger";
import {
APIGatewayAuthorizerResult,
APIGatewayRequestAuthorizerEvent,
Context,
PolicyDocument,
StatementEffect,
} from "aws-lambda";
import { v4 as uuidv4 } from "uuid";
import SecretsManagerService from "../../../services/secretsManagerService";

const X_ORIGIN_VERIFY_HEADER = "x-origin-verify";

export const handler = async (
event: APIGatewayRequestAuthorizerEvent,
context: Context
): Promise<APIGatewayAuthorizerResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);

const correlationId: string = event.requestContext.requestId || uuidv4();
const logger = new Logger({
serviceName: "auth.authorizer",
});

try {
if (!event.headers) {
logger.error("headers is missing.");
throw new Error("Error: invalid token");
}

// x-verify-origin should be set by the caller
if (!(X_ORIGIN_VERIFY_HEADER in event.headers)) {
logger.error(`${X_ORIGIN_VERIFY_HEADER} not found in headers.`);
throw new Error("Error: invalid token");
}

// Extract the token from the request
const verifyToken = event.headers[X_ORIGIN_VERIFY_HEADER];
const smService = new SecretsManagerService();
const secretStringJson = await smService.getSecret(
process.env.VERIFY_SECRET_NAME!
);

let verifyTokenfromSecretManager = "";
if (secretStringJson) {
verifyTokenfromSecretManager = JSON.parse(secretStringJson).verifyKey;
logger.info(
"Authorization token from secret manager",
verifyTokenfromSecretManager
);
} else {
logger.error("Secret not found in secret manager");
throw new Error("Error: invalid token");
}

if (verifyToken !== verifyTokenfromSecretManager) {
logger.error("Authorization token not valid");
throw new Error("Error: invalid token");
}

const policy = generatePolicy(
correlationId,
"user",
"Allow",
event.methodArn
);

logger.info(JSON.stringify(policy));

return policy;
} catch (error) {
logger.error(error);

throw new Error("Unauthorized");
}
};

const generatePolicy = (
correlationId: string,
principalId: string,
effect: StatementEffect,
resource: string
): APIGatewayAuthorizerResult => {
const policyDocument: PolicyDocument = {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: effect,
Resource: resource,
},
],
};

const authResponse: APIGatewayAuthorizerResult = {
principalId,
context: {
correlation_id: correlationId,
},
policyDocument,
};

return authResponse;
};
63 changes: 63 additions & 0 deletions aws/lambdas/auth/rotate-key/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Logger } from "@aws-lambda-powertools/logger";
import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { v4 as uuidv4 } from "uuid";
import ECSService from "../../../services/ecsService";
import SecretsManagerService from "../../../services/secretsManagerService";

export const handler = async (
event: APIGatewayEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);

const logger = new Logger({
serviceName: "auth.rotate-key",
});

try {
logger.info("Rotating verifyKey.");
await updateSecret();
logger.info("Successfully rotated verifyKey");

logger.info("Restarting ECS Services to pickup updated VerifyKey.");
await restartECSServices();
logger.info("Restart completed.");

return {
statusCode: 200,
body: JSON.stringify({
message:
"Successfully rotated the key and restarted the ECS Service/TD",
}),
};
} catch (error) {
logger.error(error);

return {
statusCode: 500,
body: JSON.stringify({
message:
"Something went wrong when updating the key and restarting the ECS Service/TD",
}),
};
}
};

const updateSecret = async () => {
const smService = new SecretsManagerService();
const newGuid = uuidv4();

await smService.updateSecret(
process.env.VERIFY_SECRET_NAME!,
JSON.stringify({ verifyKey: newGuid })
);
};

const restartECSServices = async () => {
const ecsService = new ECSService(process.env.CLUSTER_NAME!);

const services = await ecsService.getECSServices();

await ecsService.restartServices(services);
};
Loading

0 comments on commit 3deed24

Please sign in to comment.