DEV Community

Cover image for Private API Gateway as EventBridge API Destination
Benoît Bouré for AWS Community Builders

Posted on • Originally published at benoitboure.com

Private API Gateway as EventBridge API Destination

In a previous post, I explained how to connect AWS Step Functions to a private API Gateway endpoint thanks to the new integration with AWS PrivateLink and Amazon VPC Lattice. In this issue, I’ll show you how to use the same integration to use a private API Gateway API as an EventBridge target using the CDK, removing the need for an intermediary Lambda function.

Overview

EventBridge Private API Gateway Integration

Source

The setup is similar to the one for Step Functions. A Resource Gateway is used as the entry point into the VPC. It is associated with a Resource Configuration, which defines the Private API Gateway resource, and the EventBridge Connection is configured to use the Resource Config as the final destination.

For more details about this setup, see my previous post about the Step Functions Integration.

CDK Stack Definition

We need to define the Resource Gateway and the Resource Definition.

    // Security Group for the Resource Gateway
    const rgSecurityGroup = new SecurityGroup(this, 'ResourceGatewaySG', {
      vpc: vpc,
      allowAllOutbound: false,
    });

    rgSecurityGroup.addEgressRule(
      Peer.ipv4(vpc.vpcCidrBlock),
      Port.tcp(443),
      'Allow HTTPS traffic from Resource Gateway',
    );

    // Resource Gateway
    const resourceGateway = new CfnResourceGateway(this, 'ResourceGateway', {
      name: 'private-api-access',
      ipAddressType: 'IPV4',
      vpcIdentifier: vpc.vpcId,
      subnetIds: vpc.isolatedSubnets.map((subnet) => subnet.subnetId),
      securityGroupIds: [rgSecurityGroup.securityGroupId],
    });

    // Resource Configuration
    const resourceConfig = new CfnResourceConfiguration(
      this,
      'ResourceConfig',
      {
        name: 'sf-private-api',
        portRanges: ['443'],
        resourceGatewayId: resourceGateway.ref,
        resourceConfigurationType: 'SINGLE',
      },
    );

    // Use the global DNS name of the API gateway's VPC endpoint
    // in the Resource Configuration
    resourceConfig.addPropertyOverride(
      'ResourceConfigurationDefinition.DnsResource',
      {
        DomainName: Fn.select(
          1,
          Fn.split(':', Fn.select(0, api.vpcEndpoint.vpcEndpointDnsEntries)),
        ),
        IpAddressType: 'IPV4',
      },
    );

    // Event Bus
    const eventBus = new EventBus(this, 'EventBus', {});

    // Connection to the API
    const connection = new Connection(this, 'ApiConnection', {
      authorization: Authorization.apiKey(
        'x-api-key',
        SecretValue.unsafePlainText('demo'),
      ),
    });

    // Setup the Connection with the Resouce Config
    (connection.node.children[0] as CfnConnection).addPropertyOverride(
      'InvocationConnectivityParameters',
      {
        ResourceParameters: {
          ResourceConfigurationArn: resourceConfig.attrArn,
        },
      },
    );
Enter fullscreen mode Exit fullscreen mode

EventBridge is now able to connect to the private API Gateway. We can now create a rule and set the API as the target.

    const rule = new Rule(this, 'RequestAccountRule', {
      eventBus,
      eventPattern: {
        source: ['my-source'],
      },
    });

    const apiDestination = new ApiDestination(this, 'ApiDestination', {
      endpoint: `${api.api.url}/hello`,
      httpMethod: HttpMethod.POST,
      connection: connection,
    });

    rule.addTarget(
      new targets.ApiDestination(apiDestination, {
        event: RuleTargetInput.fromEventPath('$.detail'),
      }),
    );
Enter fullscreen mode Exit fullscreen mode

Find the full code on GitHub.

Testing the Integration

Putting the following event on the bus.

{
  "DetailType": "somethingHappened",
  "Source": "my-source",
  "EventBusName":"EventBusVendingMachine308DEFEB",
  "Detail": {
    "foo": "bar"
  }
}
Enter fullscreen mode Exit fullscreen mode

I can see that the Lambda function used as the handler of the endpoint is invoked with the following event.

{
    "resource": "/hello",
    "path": "/hello",
    "httpMethod": "POST",
    "headers": {
        "Accept-Encoding": "gzip, x-gzip, deflate, br",
        "Content-Type": "application/json; charset=utf-8",
        "Host": "899aggxh3a.execute-api.us-east-1.amazonaws.com",
        "Range": "bytes=0-1048575",
        "User-Agent": "Amazon/EventBridge/ApiDestinations",
        "x-amzn-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256",
        "x-amzn-tls-version": "TLSv1.2",
        "x-amzn-vpc-id": "vpc-0a1db1c1701e137ca",
        "x-amzn-vpce-config": "1",
        "x-amzn-vpce-id": "vpce-09fc3c0c5173d919b",
        "x-api-key": "demo",
        "X-Forwarded-For": "10.0.195.243"
    },
    "multiValueHeaders": {
        "Accept-Encoding": [
            "gzip, x-gzip, deflate, br"
        ],
        "Content-Type": [
            "application/json; charset=utf-8"
        ],
        "Host": [
            "899aggxh3a.execute-api.us-east-1.amazonaws.com"
        ],
        "Range": [
            "bytes=0-1048575"
        ],
        "User-Agent": [
            "Amazon/EventBridge/ApiDestinations"
        ],
        "x-amzn-cipher-suite": [
            "ECDHE-RSA-AES128-GCM-SHA256"
        ],
        "x-amzn-tls-version": [
            "TLSv1.2"
        ],
        "x-amzn-vpc-id": [
            "vpc-0a1db1c1701e137ca"
        ],
        "x-amzn-vpce-config": [
            "1"
        ],
        "x-amzn-vpce-id": [
            "vpce-09fc3c0c5173d919b"
        ],
        "x-api-key": [
            "demo"
        ],
        "X-Forwarded-For": [
            "10.0.195.243"
        ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
        "resourceId": "yjzggg",
        "resourcePath": "/hello",
        "httpMethod": "POST",
        "extendedRequestId": "Efl1aFY5oAMFoYA=",
        "requestTime": "16/Jan/2025:18:29:54 +0000",
        "path": "/prod/hello",
        "accountId": "438465158289",
        "protocol": "HTTP/1.1",
        "stage": "prod",
        "domainPrefix": "899aggxh3a",
        "requestTimeEpoch": 1737052194204,
        "requestId": "3a6754ae-5488-429c-9ec6-1837a4c21727",
        "identity": {
            "cognitoIdentityPoolId": null,
            "cognitoIdentityId": null,
            "vpceId": "vpce-09fc3c0c5173d919b",
            "apiKey": "demo",
            "principalOrgId": null,
            "cognitoAuthenticationType": null,
            "userArn": null,
            "userAgent": "Amazon/EventBridge/ApiDestinations",
            "accountId": null,
            "caller": null,
            "sourceIp": "10.0.195.243",
            "accessKey": null,
            "vpcId": "vpc-0a1db1c1701e137ca",
            "cognitoAuthenticationProvider": null,
            "user": null
        },
        "domainName": "899aggxh3a.execute-api.us-east-1.amazonaws.com",
        "deploymentId": "74v610",
        "apiId": "899aggxh3a"
    },
    "body": "{\"foo\":\"bar\"}",
    "isBase64Encoded": false
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The new VPC Lattice and AWS Private Link integration allows developers to invoke Private APIs directly without needing a Lambda function. This reduces code, maintenance, and latency.

Top comments (0)