如何将 aws api 网关连接到 vpc 内的私有 lambda 函数

how to connect an aws api gateway to a private lambda function inside a vpc

我正在尝试将 aws api 网关连接到驻留在 VPC 中的 lambda 函数,然后检索秘密管理器以使用带有 boto3 的 python 代码访问数据库。数据库和 vpc 端点是在私有子网中创建的。

lambda 函数

def test_secret():

    secret = "mysecret"
    region = "MY-REGION" :)
    
    session = boto3.session.Session()
    client = session.client(
        service_name="secretsmanager",
        region_name=region
    )
    secret_value_response = client.get_secret_value(SecretId=secret)

    try:
        result =  json.loads(secret_value_response["SecretString"])

    except Exception as e:
        result = "Error found: {}".format(e)
    return result


def handler(event, context):

    get_secrets = test_secret() # THE CODE FAIL HERE IN CLOUDWATCH

    try:
        some_string = event["queryStringParameters"]["some_string"]

        response = {}

        response["statusCode"] = 200
        response["body"] = some_string + " " + get_secrets["name"]

        print("secrets: ", some_string + " " + get_secrets["name"])

    except Exception as e:
        response = "Error: {}".format(e)

    return response

地形

安全组

resource "aws_security_group" "db" {
  name   = "db"
  vpc_id = aws_vpc.default.id

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

λ

resource "aws_lambda_function" "lambda_test" {
  function_name       = "lambda-test"
  ...

  # Attach Lambda to VPC
  vpc_config {
    subnet_ids = [aws_subnet.private_subnet.id]
    security_group_ids = [aws_security_group.db.id]
  }
}

resource "aws_iam_policy" "lambda_test" {
  name        = "lambda-test"

  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs",
                "ec2:DescribeNetworkInterfaces",
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:AttachNetworkInterface",
                "ec2:AssignPrivateIpAddresses",
                "ec2:UnassignPrivateIpAddresses",
                "autoscaling:CompleteLifecycleAction",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
              "arn:aws:lambda:::${aws_lambda_function.lambda_test.arn}",
              "arn:aws:lambda:::${aws_lambda_function.lambda_test.arn}/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": [
              "arn:aws:lambda:::${data.aws_secretsmanager_secret.my_secret.arn}",
              "arn:aws:lambda:::${data.aws_secretsmanager_secret.my_secret.arn}/*"
            ]
        }
    ]
}
EOF
}

resource "aws_iam_role" "lambda_test_role" {
  name = "lambda-test-role"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Id": "",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "secretsmanager.amazonaws.com"
        ]
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "lambda_test" {
  policy_arn  = aws_iam_policy.lambda_test.arn
  role        = aws_iam_role.lambda_test_role.name
}

resource "aws_iam_role_policy_attachment" "lambda_test_vpc_access" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
  role        = aws_iam_role.lambda_test_role.name
}

vpc 端点

resource "aws_vpc_endpoint" "vpc_endpoint" {
  vpc_id                      = aws_vpc.default.id
  service_name                = "com.amazonaws.${var.AWS_REGION}.secretsmanager"
  vpc_endpoint_type           = "Interface"

  security_group_ids          = [aws_security_group.db.id]
  private_dns_enabled         = true

  policy                      = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
          "Effect": "Allow",
          "Action": "*",
          "Principal": "*",
          "Resource": "*"
        }
    ]
}
EOF
}

无需尝试访问 secretsmanager,lambda 本身工作正常,我能够访问 url 端点,提供参数,然后在 cloudwatch 日志中查看结果,但一旦我尝试在 lambda 函数端点调用 secretsmanager,页面 return {"message": "Internal server error"},当我查看日志时它说 {"errorMessage": "Could not connect to the endpoint URL: \"https://secretsmanager.REGIONHIDDEN.amazonaws.com/\"", "errorType": "EndpointConnectionError"

我上面有什么地方做错了吗?

如果您可以从 API 网关调用 Lambda 函数,那么您的问题标题“如何将 aws api 网关连接到 vpc 中的私有 lambda 函数”已经完成并且可以正常工作.

看来您的实际问题只是从 VPC 中的 Lambda 函数 运行 内部访问 Secrets Manager。

您将“db”安全组分配给 Lambda 函数也很奇怪。这个安全组的 inbound/outbound 规则是什么?

完全不清楚您创建 VPC 端点的原因。我们应该如何看待 service_name = "foo"?什么是服务“foo”?此 VPC 终端节点与 Lambda 函数有何关联?如果这应该是 Secrets Manager 的 VPC 端点,则服务名称 should be "com.amazonaws.YOUR-REGION.secretsmanager".

如果您需要更多帮助,您需要编辑您的问题以提供以下内容:任何相关安全组的入站 出站规则,以及正在尝试的 Lambda 函数代码调用 SecretsManager。


更新: 在评论和更新问题中进行澄清后,我认为问题是您缺少 VPC 端点的任何子网分配。此外,由于您要添加具有完全访问权限的 VPC 策略,因此您可以完全忽略它,因为默认策略是完全访问权限。我建议将 VPC 端点更改为以下内容:

resource "aws_vpc_endpoint" "vpc_endpoint" {
  vpc_id                      = aws_vpc.default.id
  service_name                = "com.amazonaws.${var.AWS_REGION}.secretsmanager"
  vpc_endpoint_type           = "Interface"

  subnet_ids                  = [aws_subnet.private_subnet.id]
  security_group_ids          = [aws_security_group.db.id]
  private_dns_enabled         = true
}

更新 2: Lambda 函数的 IAM 策略的这一部分是错误的:

       {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": [
              "arn:aws:lambda:::${data.aws_secretsmanager_secret.my_secret.arn}",
              "arn:aws:lambda:::${data.aws_secretsmanager_secret.my_secret.arn}/*"
            ]
        }

这使 Lambda 可以访问秘密,其中的 ARN 是 Lambda 函数,这不是有效的秘密 ARN。应该是这样的:

   {
        "Effect": "Allow",
        "Action": "secretsmanager:GetSecretValue",
        "Resource": "${data.aws_secretsmanager_secret.my_secret.arn}"
    }

你的这部分政策也搞砸了:

            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutLogEvents",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs",
                "ec2:DescribeNetworkInterfaces",
                "ec2:CreateNetworkInterface",
                "ec2:DeleteNetworkInterface",
                "ec2:AttachNetworkInterface",
                "ec2:AssignPrivateIpAddresses",
                "ec2:UnassignPrivateIpAddresses",
                "autoscaling:CompleteLifecycleAction",
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
              "arn:aws:lambda:::${aws_lambda_function.lambda_test.arn}",
              "arn:aws:lambda:::${aws_lambda_function.lambda_test.arn}/*"
            ]

您正在将此策略分配给 Lambda 函数。您在策略中列出的资源是 Lambda 函数应该有权访问的资源。您没有将 Lambda 函数本身列为资源。我不确定如何修复这部分政策,它需要分成多个部分,或者只是用 "*".

替换资源列表

此外,当您在 Terraform 中引用资源的 .arn 值时,您将获得完整的 ARN,因此您不应在其前面加上任何前缀。