Skip to content

Commit

Permalink
Improved sequence_run_manager API Gateway setup
Browse files Browse the repository at this point in the history
Related #139
  • Loading branch information
victorskl committed Mar 21, 2024
1 parent 24280e8 commit c57422e
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 20 deletions.
2 changes: 1 addition & 1 deletion lib/workload/stateless/sequence_run_manager/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@


def handler(event, context):
return serverless_wsgi.handle_request(application.app, event, context)
return serverless_wsgi.handle_request(application, event, context)
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import { Construct } from 'constructs';
import { aws_ssm, Duration } from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { HttpUserPoolAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { HttpJwtAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { CorsHttpMethod, HttpApi } from 'aws-cdk-lib/aws-apigatewayv2';
import { IStringParameter } from 'aws-cdk-lib/aws-ssm';

export class SRMApiGatewayConstruct extends Construct {
private readonly SSM_USER_POOL_ID: string = '/data_portal/client/cog_user_pool_id'; // FIXME one fine day in future
private readonly _httpApi: HttpApi;

constructor(scope: Construct, id: string) {
constructor(scope: Construct, id: string, apiName: string, region: string) {
super(scope, id);

const userPoolParam: IStringParameter = aws_ssm.StringParameter.fromStringParameterName(this, id + 'SSMStringParameter', this.SSM_USER_POOL_ID);
const userPool = cognito.UserPool.fromUserPoolId(scope, id + 'UserPool', userPoolParam.stringValue);

this._httpApi = new HttpApi(this, id + 'HttpApi', {
apiName: 'OrcaBus SequenceRunManager API',
apiName: 'OrcaBusAPI' + apiName,
corsPreflight: {
allowHeaders: ['Authorization'],
allowMethods: [
Expand All @@ -28,13 +23,39 @@ export class SRMApiGatewayConstruct extends Construct {
allowOrigins: ['*'], // FIXME allowed origins from config constant
maxAge: Duration.days(10),
},
defaultAuthorizer: new HttpUserPoolAuthorizer(id + 'HttpUserPoolAuthorizer', userPool),
defaultAuthorizer: this.getAuthorizer(id, region),
// defaultDomainMapping: ... TODO
});

// TODO Configure access logging. See https://github.com/aws/aws-cdk/issues/11100

// TODO setup cloud map service discovery perhaps
}

private getAuthorizer(id: string, region: string): HttpJwtAuthorizer {
/**
* FIXME One fine day in future when we have proper Cognito AAI setup.
* For the moment, we leverage Portal and established Cognito infrastructure.
* See https://github.com/umccr/orcabus/issues/102
*/
const SSM_USER_POOL_ID: string = '/data_portal/client/cog_user_pool_id';
const SSM_PORTAL_CLIENT_ID: string = '/data_portal/client/data2/cog_app_client_id_stage'; // i.e. JWT from UI client https://portal.[dev|stg|prod].umccr.org
const SSM_STATUS_PAGE_CLIENT_ID: string = '/data_portal/status_page/cog_app_client_id_stage'; // i.e. JWT from UI client https://status.[dev|stg|prod].umccr.org

const userPoolIdParam: IStringParameter = aws_ssm.StringParameter.fromStringParameterName(this, id + 'CognitoUserPoolIdParameter', SSM_USER_POOL_ID);
const portalClientIdParam: IStringParameter = aws_ssm.StringParameter.fromStringParameterName(this, id + 'CognitoPortalClientIdParameter', SSM_PORTAL_CLIENT_ID);
const statusPageClientIdParam: IStringParameter = aws_ssm.StringParameter.fromStringParameterName(this, id + 'CognitoStatusPageClientIdParameter', SSM_STATUS_PAGE_CLIENT_ID);

const issuer = 'https://cognito-idp.' + region + '.amazonaws.com/' + userPoolIdParam.stringValue;

return new HttpJwtAuthorizer(id + 'PortalAuthorizer', issuer, {
jwtAudience: [
portalClientIdParam.stringValue,
statusPageClientIdParam.stringValue,
],
});
}

get httpApi(): HttpApi {
return this._httpApi;
}
Expand Down
19 changes: 14 additions & 5 deletions lib/workload/stateless/sequence_run_manager/deploy/component.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import path from 'path';
import * as cdk from 'aws-cdk-lib';
import { aws_lambda, aws_secretsmanager, Stack } from 'aws-cdk-lib';
import { aws_lambda, aws_secretsmanager, Duration, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2';
import { IEventBus } from 'aws-cdk-lib/aws-events';
import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { HttpMethod, HttpRoute, HttpRouteKey, HttpStage } from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpMethod, HttpRoute, HttpRouteKey } from 'aws-cdk-lib/aws-apigatewayv2';
import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { SRMApiGatewayConstruct } from './apigw/component';
import { Architecture } from 'aws-cdk-lib/aws-lambda';
Expand All @@ -20,7 +20,7 @@ export interface SequenceRunManagerProps {
export class SequenceRunManagerStack extends Stack {
// Follow by naming convention. See https://github.com/umccr/orcabus/pull/149
private readonly secretId: string = 'orcabus/sequence_run_manager/rdsLoginCredential';
private readonly apiNamespace: string = '/srm/v1';
private readonly apiName: string = 'SequenceRunManager'; // apiNamespace `/srm/v1` is handled by Django Router
private readonly id: string;
private readonly props: SequenceRunManagerProps;
private readonly baseLayer: PythonLayerVersion;
Expand All @@ -38,12 +38,18 @@ export class SequenceRunManagerStack extends Stack {
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
description: 'Lambda execution role for ' + this.id,
});
// FIXME it is best practise to such that we do not use AWS managed policy
// we should improve this at some point down the track.
// See https://github.com/umccr/orcabus/issues/174
this.lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
);
this.lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole'),
);
this.lambdaRole.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMReadOnlyAccess'),
);

const dbSecret = aws_secretsmanager.Secret.fromSecretNameV2(this, this.id + 'dbSecret', this.secretId);
dbSecret.grantRead(this.lambdaRole);
Expand Down Expand Up @@ -84,31 +90,34 @@ export class SequenceRunManagerStack extends Stack {
this.createPythonFunction('Migration', {
index: 'migrate.py',
handler: 'handler',
timeout: Duration.minutes(2),
});
}

private createApiHandlerAndIntegration() {
const apiFn: PythonFunction = this.createPythonFunction('Api', {
index: 'api.py',
handler: 'handler',
timeout: Duration.seconds(28),
});

const srmApi = new SRMApiGatewayConstruct(this, this.id + 'SRMApiGatewayConstruct');
const srmApi = new SRMApiGatewayConstruct(this, this.id + 'SRMApiGatewayConstruct', this.apiName, this.region);
const httpApi = srmApi.httpApi;

const apiIntegration = new HttpLambdaIntegration(this.id + 'ApiIntegration', apiFn);

new HttpRoute(this, this.id + 'HttpRoute', {
httpApi: httpApi,
integration: apiIntegration,
routeKey: HttpRouteKey.with(this.apiNamespace + '/{proxy+}', HttpMethod.ANY),
routeKey: HttpRouteKey.with('/{proxy+}', HttpMethod.ANY),
});
}

private createProcSqsHandler() {
const procSqsFn = this.createPythonFunction('ProcHandler', {
index: 'sequence_run_manager_proc/lambdas/bssh_event.py',
handler: 'sqs_handler',
timeout: Duration.minutes(2),
});

this.props.mainBus.grantPutEventsTo(procSqsFn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
from sequence_run_manager.routers import OptionalSlashDefaultRouter
from sequence_run_manager.viewsets.sequence import SequenceViewSet

api_namespace = "srm"
api_version = "v1"
api_base = f"{api_namespace}/{api_version}/"

router = OptionalSlashDefaultRouter()
router.register(r"sequence", SequenceViewSet, basename="sequence")

urlpatterns = [
# path("iam/", include(router.urls)),
path("", include(router.urls)),
path(f"{api_base}", include(router.urls)),
]

handler500 = "rest_framework.exceptions.server_error"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
from drf_yasg.views import get_schema_view
from rest_framework import permissions

from .base import urlpatterns as base_urlpatterns, router
from .base import urlpatterns as base_urlpatterns, router, api_base, api_version

schema_view = get_schema_view(
openapi.Info(
title="UMCCR OrcaBus sequence_run_manager API",
default_version="v1",
default_version=f"{api_version}",
description="UMCCR OrcaBus sequence_run_manager API",
terms_of_service="https://umccr.org/",
contact=openapi.Contact(email="[email protected]"),
Expand All @@ -19,7 +19,7 @@
permissions.AllowAny,
],
patterns=[
path("", include(router.urls)),
path(f"{api_base}", include(router.urls)),
],
)

Expand Down
2 changes: 1 addition & 1 deletion skel/django-api/src/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@


def handler(event, context):
return serverless_wsgi.handle_request(application.app, event, context)
return serverless_wsgi.handle_request(application, event, context)

0 comments on commit c57422e

Please sign in to comment.