-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
aws_apigatewayv2: Can't define HTTP API Step Functions integration with JWT authorizer #28904
Comments
Thank you for the report. Are you able to provide a full minimal working code snippets that we can just copy/paste into our IDE for reproduction? |
Try this: import { aws_apigatewayv2 as apigwv2, Stack } from 'aws-cdk-lib';
import { ConnectionType, IntegrationType } from 'aws-cdk-lib/aws-apigateway';
import { CfnIntegration, CfnRoute, CorsHttpMethod, HttpIntegrationSubtype } from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpJwtAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { UserPool } from 'aws-cdk-lib/aws-cognito';
import { TableV2 } from 'aws-cdk-lib/aws-dynamodb';
import { Effect, Policy, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
import { StateMachine } from 'aws-cdk-lib/aws-stepfunctions';
import { Construct } from 'constructs';
import { createListJobsFunction } from '../lambda';
export interface HttpApiConstructProps {
readonly userPool: UserPool;
readonly appClientId: string;
readonly dynamoDbTable: TableV2;
readonly stateMachine: StateMachine;
}
export class HttpApiConstruct extends Construct {
constructor(scope: Construct, id: string, props: HttpApiConstructProps) {
super(scope, id);
const region = Stack.of(this).region;
const issuer = `https://cognito-idp.${region}.amazonaws.com/${props.userPool.userPoolId}`;
const jwtAuthorizer = new HttpJwtAuthorizer('JwtAuthorizer', issuer, {
authorizerName: 'MyJwtAuthorizer',
jwtAudience: [props.appClientId],
});
const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
apiName: 'My HTTP API',
defaultAuthorizer: jwtAuthorizer,
corsPreflight: {
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key'],
allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.DELETE, CorsHttpMethod.PUT, CorsHttpMethod.POST],
allowOrigins: ['*'], //TODO restrict to specific domain
},
});
const listJobsFunction = createListJobsFunction(this, props.dynamoDbTable); // this can be anything that returns a lambda.Function
const listJobsIntegration = new HttpLambdaIntegration('ListJobsIntegration', listJobsFunction);
httpApi.addRoutes({
path: '/listJobs',
methods: [apigwv2.HttpMethod.GET],
integration: listJobsIntegration,
authorizer: jwtAuthorizer,
});
const credentialsRole = new Role(this, 'StartExecution', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});
credentialsRole.attachInlinePolicy(
new Policy(this, 'StartExecutionPolicy', {
statements: [
new PolicyStatement({
actions: ['states:StartExecution'],
effect: Effect.ALLOW,
resources: [props.stateMachine.stateMachineArn],
}),
],
}),
);
var apiGatewayRole = new Role(this, 'ApiGatewayRole', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});
apiGatewayRole.addToPolicy(
new PolicyStatement({
effect: Effect.ALLOW,
sid: 'AllowStepFunctionExecution',
actions: ['states:StartExecution'],
resources: [props.stateMachine.stateMachineArn],
}),
);
const integration = new CfnIntegration(this, 'StepFunctionIntegration', {
apiId: httpApi.apiId,
integrationType: IntegrationType.AWS_PROXY,
integrationSubtype: HttpIntegrationSubtype.STEPFUNCTIONS_START_EXECUTION,
credentialsArn: apiGatewayRole.roleArn,
requestParameters: {
Input: '$request.body',
StateMachineArn: props.stateMachine.stateMachineArn,
},
payloadFormatVersion: '1.0',
connectionType: ConnectionType.INTERNET,
});
httpApi.addRoutes({
path: '/jobs',
methods: [apigwv2.HttpMethod.POST],
integration: integration, // <=== ERROR HERE
authorizer: jwtAuthorizer,
});
new CfnRoute(this, 'StepFunctionRoute', {
apiId: httpApi.apiId,
routeKey: 'POST /jobs',
target: `integrations/${integration.ref}`,
authorizerId: jwtAuthorizer, // <===== ERROR HERE
});
}
} |
Hi @mrichman Per our discussion offline, as aws-apigatewayv2-integrations is missing step function integration class, we need to declare one in our code like this and pass it all the way to the addRoute(). Consider the code below(not fully tested): import { aws_apigatewayv2 as apigwv2, Stack } from 'aws-cdk-lib';
import { ConnectionType, IntegrationType } from 'aws-cdk-lib/aws-apigateway';
import { CfnIntegration, CfnRoute, CorsHttpMethod, HttpIntegrationSubtype,
HttpRouteIntegration, HttpMethod, ParameterMapping, HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig,
PayloadFormatVersion, HttpIntegrationType, IntegrationCredentials, HttpConnectionType } from 'aws-cdk-lib/aws-apigatewayv2';
import { HttpJwtAuthorizer } from 'aws-cdk-lib/aws-apigatewayv2-authorizers';
import { HttpLambdaIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations';
import { UserPool } from 'aws-cdk-lib/aws-cognito';
import { TableV2 } from 'aws-cdk-lib/aws-dynamodb';
import { Effect, Policy, PolicyStatement, Role, ServicePrincipal, IRole, } from 'aws-cdk-lib/aws-iam';
import { StateMachine } from 'aws-cdk-lib/aws-stepfunctions';
import { Construct } from 'constructs';
// import { createListJobsFunction } from '../lambda';
export interface HttpApiConstructProps {
readonly userPool: UserPool;
readonly appClientId: string;
readonly dynamoDbTable: TableV2;
readonly stateMachine: StateMachine;
}
interface requestParameters {
input: string,
stateMachineArn: string,
}
interface HttpStepFunctionIntegrationProps {
/**
* The HTTP method that must be used to invoke the underlying HTTP proxy.
* @default HttpMethod.ANY
*/
readonly method?: HttpMethod;
/**
* Specifies how to transform HTTP requests before sending them to the backend
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html
* @default undefined requests are sent to the backend unmodified
*/
readonly parameterMapping?: ParameterMapping;
readonly apiGatewayRole: IRole,
}
export class HttpStepFunctionIntegration extends HttpRouteIntegration {
/**
* @param id id of the underlying integration construct
* @param props properties to configure the integration
*/
constructor(id: string, private readonly props: HttpStepFunctionIntegrationProps) {
super(id);
}
public bind(_options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {
return {
method: this.props.method ?? HttpMethod.ANY,
payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format
type: HttpIntegrationType.AWS_PROXY,
subtype: HttpIntegrationSubtype.STEPFUNCTIONS_START_EXECUTION,
credentials: IntegrationCredentials.fromRole(this.props.apiGatewayRole),
connectionType: HttpConnectionType.INTERNET,
parameterMapping: this.props.parameterMapping,
};
}
}
export class HttpApiConstruct extends Construct {
constructor(scope: Construct, id: string, props: HttpApiConstructProps) {
super(scope, id);
const region = Stack.of(this).region;
const issuer = `https://cognito-idp.${region}.amazonaws.com/${props.userPool.userPoolId}`;
const jwtAuthorizer = new HttpJwtAuthorizer('JwtAuthorizer', issuer, {
authorizerName: 'MyJwtAuthorizer',
jwtAudience: [props.appClientId],
});
const httpApi = new apigwv2.HttpApi(this, 'HttpApi', {
apiName: 'My HTTP API',
defaultAuthorizer: jwtAuthorizer,
corsPreflight: {
allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key'],
allowMethods: [CorsHttpMethod.GET, CorsHttpMethod.DELETE, CorsHttpMethod.PUT, CorsHttpMethod.POST],
allowOrigins: ['*'], //TODO restrict to specific domain
},
});
const listJobsFunction = createListJobsFunction(this, props.dynamoDbTable); // this can be anything that returns a lambda.Function
const listJobsIntegration = new HttpLambdaIntegration('ListJobsIntegration', listJobsFunction);
httpApi.addRoutes({
path: '/listJobs',
methods: [apigwv2.HttpMethod.GET],
integration: listJobsIntegration,
authorizer: jwtAuthorizer,
});
const credentialsRole = new Role(this, 'StartExecution', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});
credentialsRole.attachInlinePolicy(
new Policy(this, 'StartExecutionPolicy', {
statements: [
new PolicyStatement({
actions: ['states:StartExecution'],
effect: Effect.ALLOW,
resources: [props.stateMachine.stateMachineArn],
}),
],
}),
);
var apiGatewayRole = new Role(this, 'ApiGatewayRole', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
});
apiGatewayRole.addToPolicy(
new PolicyStatement({
effect: Effect.ALLOW,
sid: 'AllowStepFunctionExecution',
actions: ['states:StartExecution'],
resources: [props.stateMachine.stateMachineArn],
}),
);
// const integration = new CfnIntegration(this, 'StepFunctionIntegration', {
// apiId: httpApi.apiId,
// integrationType: IntegrationType.AWS_PROXY,
// integrationSubtype: HttpIntegrationSubtype.STEPFUNCTIONS_START_EXECUTION,
// credentialsArn: apiGatewayRole.roleArn,
// requestParameters: {
// Input: '$request.body',
// StateMachineArn: props.stateMachine.stateMachineArn,
// },
// payloadFormatVersion: '1.0',
// connectionType: ConnectionType.INTERNET,
// });
const integration = new HttpStepFunctionIntegration('StepFunctionIntegration', {
apiGatewayRole,
parameterMapping: new apigwv2.ParameterMapping()
.custom('Input', '$request.body')
.custom('StateMachineArn', props.stateMachine.stateMachineArn),
})
httpApi.addRoutes({
path: '/jobs',
methods: [apigwv2.HttpMethod.POST],
integration, // <=== ERROR HERE
authorizer: jwtAuthorizer,
});
// new CfnRoute(this, 'StepFunctionRoute', {
// apiId: httpApi.apiId,
// routeKey: 'POST /jobs',
// target: `integrations/${integration.ref}`,
// authorizerId: jwtAuthorizer, // <===== ERROR HERE
// });
}
} It should be a general workaround before we have a separate PR that contributes the |
### Issue Closes #28904. ### Reason for this change It is not possible to create an integration between Step Functions and HTTP API. ### Description of changes You can create integration by `HttpStepFunctionsIntegration` class: ```ts declare const httpApi: apigwv2.HttpApi; declare const stateMachine: sfn.StateMachine; const integration = new HttpStepFunctionIntegration('StepFunctionIntegration', { stateMachine, }) httpApi.addRoutes({ path: '/jobs', methods: [apigwv2.HttpMethod.POST], integration, }); ``` ### Description of how you validated changes Added unit tests and integ test. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
|
Describe the bug
This should behave similarly to the REST API construct
Expected Behavior
Either of these approaches should work:
Current Behavior
Can't get this to compile because of error:
Reproduction Steps
See code example
Possible Solution
No response
Additional Information/Context
No response
CDK CLI Version
2.124.0 (build 4b6724c)
Framework Version
No response
Node.js Version
v20.11.0
OS
Ubuntu 20.04
Language
TypeScript
Language Version
TypeScript (5.3.3)
Other information
No response
The text was updated successfully, but these errors were encountered: