Skill 137 · AWS Lambda Web Functions
Subchapter 137.7
references/infrastructure-as-code.mdMarkdown12 KBView on GitHub
The AWS CLI (aws lambda-web) is the quickest path — see deployment.md and the CLI section of . Use CloudFormation when you want infrastructure-as-code (IaC): repeatable deployments, rollback support, and resources managed as a stack.
SKILL.mdLoad this file only when the user asks about CloudFormation or infrastructure-as-code for Lambda Web Functions.
Check the current documentation before creating any CDK or SAM code: the AWS SAM resource reference (opens in a new tab) and the AWS CDK API reference (opens in a new tab). The CloudFormation resources below are what both compile down to.
Three resources: the function, an immutable revision (code + config), and an HTTPS endpoint weighted to that revision.
Resources:
WebFunction: # just the name
Type: AWS::Lambda::WebFunction
Properties:
FunctionName: {name}
WebFunctionRevision: # immutable code + config
Type: AWS::Lambda::WebFunctionRevision
DependsOn: WebFunction
Properties:
FunctionName: {name}
BuildConfig:
CodeConfig:
S3Object:
Bucket: {bucket}
Key: "{name}/function.zip"
# VersionId: {version} # optional; use it to pick up a new upload under the same key
RuntimeConfig:
Runtime: {runtime}
ServiceConfig:
ExecutionRoleArn: !GetAtt WebFunctionRole.Arn
TimeoutSeconds: 30
WebFunctionEndpoint: # HTTPS endpoint, weighted to a revision
Type: AWS::Lambda::WebFunctionEndpoint
DependsOn: [WebFunction, WebFunctionRevision]
Properties:
FunctionName: {name}
EndpointName: default
EndpointType: HomeRegion
AuthType: IamAuth # SigV4 callers only; ApplicationManaged = PUBLIC, app owns auth
RevisionWeights:
- RevisionId: !GetAtt WebFunctionRevision.RevisionId
Weight: 100
Outputs:
EndpointDomainName:
Value: !GetAtt WebFunctionEndpoint.DomainName{runtime} is the Node.js runtime named in SKILL.md. For an existing function, read the current
value from buildConfig.runtimeConfig.runtime with aws lambda-web get-web-function-revision.
You must create the execution role yourself (trust lambda.amazonaws.com, attach AWSLambdaBasicExecutionRole for CloudWatch Logs) — see iam-and-security.md (opens in a new tab).
To reach private VPC resources, add an AWS::Lambda::NetworkConnector and reference it from the revision’s ServiceConfig.EgressNetworkConnectorArn. One stack can own the whole path — the connector does not have to exist first, and you never write a version number by hand. For what a connector does and how to size the subnets, security groups and operator role, see networking.md (opens in a new tab).
Connector:
Type: AWS::Lambda::NetworkConnector
Properties:
Name: {connector-name}
OperatorRole: !GetAtt ConnectorOperatorRole.Arn
Configuration:
VpcEgressConfiguration:
SubnetIds: [{subnet-a}, {subnet-b}, {subnet-c}]
SecurityGroupIds: [{connector-sg}]
NetworkProtocol: IPv4 # DualStack for IPv6 egress
AssociatedComputeResourceTypes:
- WebFunction
WebFunctionRevision:
Type: AWS::Lambda::WebFunctionRevision
DependsOn: WebFunction
Properties:
FunctionName: {name}
BuildConfig:
CodeConfig:
S3Object:
Bucket: {bucket}
Key: "{name}/function.zip"
RuntimeConfig:
Runtime: {runtime}
ServiceConfig:
ExecutionRoleArn: !GetAtt WebFunctionRole.Arn
TimeoutSeconds: 30
EgressNetworkConnectorArn: !GetAtt Connector.LatestVersionArnUse LatestVersionArn, not Arn. The revision needs a version-qualified ARN
(...:network-connector:nc-...:1). !GetAtt Connector.Arn is the unqualified ARN and is rejected
by resource-model validation before anything is created.
Creating the connector takes a few minutes, so the stack sits on that resource before the revision
starts. Give the endpoint EndpointType: HomeRegion. A connector is Regional, so the API rejects a
MultiRegion or PerRegion endpoint that would serve a revision referencing one, and the stack
fails on the endpoint resource.
Updating the connector’s configuration publishes a new version, so LatestVersionArn changes, and
the next update-stack replaces the revision with one naming the new version and shifts the endpoint
to it. The CLI path does not do this: aws lambda-web deploy carries the previous revision’s
connector ARN forward verbatim, and that ARN is version-qualified, so a redeploy keeps the old
version.
To let only a CloudFront distribution invoke an IamAuth endpoint, attach the function’s
resource-based policy with AWS::Lambda::ResourcePolicy. The property is ResourceArn, set to
the web-function ARN; FunctionResourceArn is not a property and CloudFormation rejects it. The
distribution’s origin uses an AWS::CloudFront::OriginAccessControl with
OriginAccessControlOriginType: lambda and SigningBehavior: always-amz-auth. Why each piece is
needed: domains-and-routing.md (opens in a new tab).
OriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: {name}-oac
OriginAccessControlOriginType: lambda
SigningBehavior: always-amz-auth
SigningProtocol: sigv4
# On the distribution's origin: OriginAccessControlId: !GetAtt OriginAccessControl.Id
CloudFrontInvokePolicy:
Type: AWS::Lambda::ResourcePolicy
Properties:
ResourceArn: !GetAtt WebFunction.FunctionArn # the web-function/... ARN
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal: { Service: cloudfront.amazonaws.com }
Action: lambda:InvokeWebFunctionEndpoint
Resource: !GetAtt WebFunctionEndpoint.EndpointArn
Condition:
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
aws:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}"aws:SourceArn must be the distribution’s full ARN, so build it with !Sub as above.
!Ref Distribution returns only the distribution ID and AWS::CloudFront::Distribution has no
Arn attribute, so either form gives a condition that never matches and CloudFront gets 403.
In CDK the generated L1 is CfnResourcePolicy (aws-cdk-lib/aws-lambda), whose property is
resourceArn; pass fn.attrFunctionArn from CfnWebFunction. A literal web-function ARN string
makes cdk synth warn that ResourceArn does not match the pattern
(CloudFormation-Validate::F3017 and F3031), because CDK’s built-in validation carries an older
schema; cfn-lint can report the same. The template is valid and the stack deploys; do not switch
to a custom resource to set the policy.
There is no L2 construct for Web Functions. If your aws-cdk-lib has no generated L1
(CfnWebFunction), declare the three resources with the escape hatch, CfnResource, which takes the
same property names as the YAML above:
const fn = new cdk.CfnResource(this, 'WebFunction', {
type: 'AWS::Lambda::WebFunction',
properties: { FunctionName: name },
});
const rev = new cdk.CfnResource(this, 'WebFunctionRevision', {
type: 'AWS::Lambda::WebFunctionRevision',
properties: {
FunctionName: name, // the bare name, never getAtt('FunctionArn')
BuildConfig: {
CodeConfig: { S3Object: { Bucket: bucket, Key: key } },
RuntimeConfig: { Runtime: runtime },
},
ServiceConfig: {
ExecutionRoleArn: role.roleArn,
EgressNetworkConnectorArn: connector.getAtt('LatestVersionArn').toString(),
},
},
});
rev.addDependency(fn);The connector is the exception: aws-cdk-lib does generate CfnNetworkConnector
(aws-cdk-lib/aws-lambda), so use it directly rather than a second CfnResource.
Two CDK-specific points:
DependsOn;
in CDK it is addDependency. Without it the revision is created in parallel with the function and
fails with ResourceNotFoundException, The function ... does not exist.cdk synth and cdk deploy can print warnings that do not mean the template is wrong.
Unknown resource type 'AWS::Lambda::WebFunction' (CloudFormation Validate) appears when the
installed aws-cdk-lib does not know the type; the deploy still succeeds. On the connector,
'LatestVersionArn' is not one of ['Arn', 'State'] appears because both the generated construct
and the type’s published attribute list carry only those two names, while getAtt on
LatestVersionArn still resolves and the deploy succeeds. Neither warning is a reason to fall
back to the unqualified Arn, which a revision rejects.Upload the zip, including node_modules, before deploying — nothing packages the code for you.
zip -r function.zip index.js package.json node_modules/
aws s3 cp function.zip s3://{bucket}/{name}/function.zip --region {region}AWS::Lambda::WebFunctionRevision).{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": ["s3:GetObject", "s3:GetObjectVersion"],
"Resource": "arn:aws:s3:::{bucket}/*",
"Condition": {
"StringEquals": {"aws:SourceAccount": "{account-id}"},
"ArnLike": {"aws:SourceArn": "arn:aws:lambda:{region}:{account-id}:web-function/*"}
}
}, {
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": ["arn:aws:s3:::{bucket}", "arn:aws:s3:::{bucket}/*"],
"Condition": {"Bool": {"aws:SecureTransport": "false"}}
}]
}aws:SourceArn must end in /*: the source ARN is under the revision, not the function itself, so the
bare function ARN matches nothing and the read is refused.
DependsOn. Without it Cfn could try to create revision or endpoint before the function exists.function-v2.zip) or set S3Object.VersionId.RevisionWeights is always required on the endpoint, even for a single revision.FunctionName must be the bare function name on every resource — never the function ARN.
An ARN is accepted at create time and the resources come up, then the endpoint’s DomainName
attribute cannot be read: Property '/FunctionName' has value '{arn}' in the resource model but '{name}' in the ARN. That fails the stack, and the rollback fails the same way on the revision,
leaving ROLLBACK_FAILED. Recovery takes two calls, because retention is rejected until a delete
has already failed: delete-stack to reach DELETE_FAILED, then
delete-stack --retain-resources WebFunctionRevision. !GetAtt WebFunctionRevision.RevisionId on
the endpoint is fine — the restriction is on FunctionName.LatestVersion. It is an integer, and an Output value must be a
string: !GetAtt fails the stack with “Every Value member must be a string”, and
!Sub '${Connector.LatestVersion}' fails with “does not resolve to a string”. Output
LatestVersionArn instead, which is the value a revision needs anyway.# Get the endpoint domain from stack outputs, then curl it
ENDPOINT=$(aws cloudformation describe-stacks \
--stack-name {stack-name} --region {region} \
--query "Stacks[0].Outputs[?OutputKey=='EndpointDomainName'].OutputValue" --output text)
curl https://$ENDPOINT/aws cloudformation delete-stack --stack-name {stack-name} --region {region}