VPC 中的 Terraform Lambda(与 MongoDB Atlas 对等)连接 NAT 网关

Xax*_*axD 3 amazon-vpc aws-lambda mongodb-atlas terraform-provider-aws aws-secrets-manager

我已经构建了一个 Terraform 配置,用于部署 MongoDB atlas 云集群,并使用我的 AWS 账户设置 VPC 对等点。terraform 配置将凭证存储在 AWS Secrets Manager 中。不幸的是,我的 Lambda 无法访问 Secrets Manager API 端点或 Atlas 上托管的 MongoDB 集群。我读到我需要在我的 VPC 上设置 NAT 网关才能访问公共互联网。我不是网络专业人士,我尝试添加一堆不同的配置但无济于事。请帮忙:

  1. 我是否需要为我的 VPC 设置 NAT 网关才能访问 Secrets Manager?或者我可以以某种方式将 Secret 托管在 VPC 中吗?这里的最佳实践是什么?
  2. 我是否需要为 Lambda 设置 NAT 网关才能访问 Atlas 托管的 MongoDB 集群,即使它们位于同一 VPC 上并且我已将 Lambda 所在的安全组列入白名单?
  3. 如何设置 NAT 网关以允许我的 Lambda 连接到 Terraform 中的 Atlas 集群?

理想情况下,我想尽可能地锁定与外部互联网的连接,但如果这不是一个选择,我可以接受任何可行的实现。

这是我的 Terraform 配置

variable "admin_profile" {
  type = string
  default = "superadmin"
}


variable "region" {
  type    = string
  default = "us-west-2"
}

provider "aws" {
  profile = var.admin_profile
  region  = "us-west-2"
  alias   = "admin"
}


// create mongo db organization + cluster on atlas

provider "mongodbatlas" {
  public_key  = var.atlas_public_key
  private_key = var.atlas_private_key
}

//superadmin creds
variable aws_account_id {
  type = string
}

variable atlas_private_key {
  type = string
}

variable atlas_public_key {
  type = string
}

variable atlas_region {
  type    = string
  default = "US_WEST_2"
}

variable atlas_org_id {
  type    = string
  default = "" #EXCLUDE THIS
}


// generated creds for db
variable atlas_db_user {
  default = "mongo_user"
}

resource "random_password" "password" {
  length  = 16
  special = false
  #override_special = "_%-"
}

locals {
  atlas_db_password = random_password.password.result
}

variable atlas_db_vpc_cidr {
  default = "192.168.224.0/21"
}

// resources
resource "mongodbatlas_project" "cluster-partner-project" {
  name   = "live"
  org_id = var.atlas_org_id
}

resource "mongodbatlas_cluster" "cluster-partner" {
  project_id                   = mongodbatlas_project.cluster-partner-project.id
  name                         = "cluster-partner"
  num_shards                   = 1
  replication_factor           = 3
  provider_backup_enabled      = true
  cluster_type                 = "REPLICASET"
  auto_scaling_disk_gb_enabled = true
  mongo_db_major_version       = "4.2"

  //Provider Settings "block"
  provider_name               = "AWS"
  disk_size_gb                = 40
  provider_disk_iops          = 120
  provider_volume_type        = "STANDARD"
  provider_encrypt_ebs_volume = true
  provider_instance_size_name = "M10"
  provider_region_name        = var.atlas_region
}

resource "mongodbatlas_database_user" "cluster-partner-user" {
  username           = var.atlas_db_user
  password           = local.atlas_db_password
  auth_database_name = "admin"
  project_id         = mongodbatlas_project.cluster-partner-project.id
  roles {
    role_name     = "readAnyDatabase"
    database_name = "admin"
  }

  roles {
    role_name     = "readWrite"
    database_name = "app_db"
  }
}

resource "mongodbatlas_network_container" "cluster-partner-network" {
  atlas_cidr_block = var.atlas_db_vpc_cidr
  project_id       = mongodbatlas_project.cluster-partner-project.id
  provider_name    = "AWS"
  region_name      = var.atlas_region
}

resource "mongodbatlas_network_peering" "cluster-partner-network-peering" {
  accepter_region_name   = var.region
  project_id             = mongodbatlas_project.cluster-partner-project.id
  container_id           = mongodbatlas_network_container.cluster-partner-network.container_id
  provider_name          = "AWS"
  route_table_cidr_block = aws_vpc.primary.cidr_block
  vpc_id                 = aws_vpc.primary.id
  aws_account_id         = var.aws_account_id
}

resource "mongodbatlas_project_ip_whitelist" "default-db-access" {
  project_id         = mongodbatlas_project.cluster-partner-project.id
  aws_security_group = aws_security_group.primary_default.id
  comment            = "Access for App to MongoDB"
  depends_on         = [mongodbatlas_network_peering.cluster-partner-network-peering]
}

// create a vpc in AWS
resource "aws_vpc" "primary" {
  provider             = aws.admin
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
}
// Internet Gateway
resource "aws_internet_gateway" "primary" {
  provider = aws.admin
  vpc_id   = aws_vpc.primary.id
}
// route table
resource "aws_route" "primary-internet_access" {
  provider               = aws.admin
  route_table_id         = aws_vpc.primary.main_route_table_id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.primary.id
}

resource "aws_route" "peeraccess" {
  provider                  = aws.admin
  route_table_id            = aws_vpc.primary.main_route_table_id
  destination_cidr_block    = var.atlas_db_vpc_cidr
  vpc_peering_connection_id = mongodbatlas_network_peering.cluster-partner-network-peering.connection_id
  depends_on                = [aws_vpc_peering_connection_accepter.peer]
}

//subnets

//public
resource "aws_subnet" "primary-az1" {
  provider                = aws.admin
  tags = {
    Name = "public primary subnet"
  }
  vpc_id                  = aws_vpc.primary.id
  cidr_block              = "10.0.1.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "${var.region}a"
}

//private
resource "aws_subnet" "primary-az2" {
  provider                = aws.admin
  tags = {
    Name = "private subnet 0"
  }
  vpc_id                  = aws_vpc.primary.id
  cidr_block              = "10.0.2.0/24"
  map_public_ip_on_launch = false
  availability_zone       = "${var.region}b"
}

// security groups for mongo vpc connect

resource "aws_security_group" "primary_default" {
  provider    = aws.admin
  name_prefix = "defaultvpc-"
  description = "Default security group for all instances in VPC ${aws_vpc.primary.id}"
  vpc_id      = aws_vpc.primary.id
  ingress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = [
      aws_vpc.primary.cidr_block,
      var.atlas_db_vpc_cidr
    ]
    # cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

// vpc peering auto accept

resource "aws_vpc_peering_connection_accepter" "peer" {
  provider                  = aws.admin
  vpc_peering_connection_id = mongodbatlas_network_peering.cluster-partner-network-peering.connection_id
  auto_accept               = true
}

// save mongo account details to secret manager


resource "aws_secretsmanager_secret" "partner_iam_mongo_access" {
  provider = aws.admin
  name     = "mongo-access"
}

locals {
  mongo_credentials = {
    connection_strings = mongodbatlas_cluster.cluster-partner.connection_strings
    password           = local.atlas_db_password
  }
}

resource "aws_secretsmanager_secret_version" "partner_iam_mongo_access" {
  provider      = aws.admin
  secret_id     = aws_secretsmanager_secret.partner_iam_mongo_access.id
  secret_string = jsonencode(local.mongo_credentials)
}


// create lambdas for each of the key steps in the app

// have to add the vpc

resource "aws_iam_role_policy" "lambda_policy" {
  provider = aws.admin
  name     = "lambda_policy"
  role     = aws_iam_role.lambda_role.id
  policy   = file("./lambda-policy.json")
}

data "aws_iam_policy" "aws_lambda_vpc_access_execution_role" {
  provider = aws.admin
  arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

resource "aws_iam_role" "lambda_role" {
  provider           = aws.admin
  name               = "lambda-vpc-role-managed"
  assume_role_policy = file("./lambda-assume-policy.json")
}

data "archive_file" "test-connection" {
  type        = "zip"
  source_file = "./test-connection"
  output_path = "./test-connection_deploy.zip"
}

resource "aws_lambda_function" "test-connection" {
  provider         = aws.admin
  filename         = "./test-connection_deploy.zip"
  function_name    = "test-connection"
  role             = aws_iam_role.lambda_role.arn
  handler          = "test-connection"
  runtime          = "go1.x"
  timeout          = 15
  source_code_hash = data.archive_file.test-connection.output_base64sha256
  vpc_config {
    subnet_ids         = [aws_subnet.primary-az1.id] // public subnet
    security_group_ids = [aws_security_group.primary_default.id]
  }

}
Run Code Online (Sandbox Code Playgroud)

这是我的 tfvar

admin_profile     = "default"
atlas_private_key = 
atlas_public_key  = 
atlas_org_id      = 
aws_account_id    = 
Run Code Online (Sandbox Code Playgroud)

这是我的 Lambda 策略 (lambda-policy.json)

{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents",
            "ec2:DescribeNetworkInterfaces",
            "ec2:CreateNetworkInterface",
            "ec2:DeleteNetworkInterface",
            "ec2:DescribeInstances",
            "ec2:AttachNetworkInterface",
            "secretsmanager:DescribeSecret",
            "secretsmanager:GetSecretValue",
            "secretsmanager:ListSecretVersionIds",
            "secretsmanager:ListSecrets"
         ],
         "Resource":"*"
      }
   ]
}
Run Code Online (Sandbox Code Playgroud)

这是我的 Lambda 策略 (lambda-assume-policy.json)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Principal": {
                "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"]
            },
            "Effect": "Allow",
            "Sid": ""
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

这是我的 Lambda 的 (GoLang) 代码

package main

import (
    "context"
    "fmt"
    "errors"
    "time"
    "encoding/json"
    "github.com/aws/aws-lambda-go/lambda"
    "github.com/sparrc/go-ping"
    "github.com/aws/aws-sdk-go/service/secretsmanager"  
    "go.mongodb.org/mongo-driver/mongo"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/session"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)


type MongoCreds struct {
    ConnectionStrings []map[string]interface{} `json:"connection_strings"`
    Password          string   `json:"password"`
}

var MainRegion = "us-west-2"

func HandleRequest(ctx context.Context, updatedValues interface{}) (string, error) {
    fmt.Println("we are pinging")
    pinger, err := ping.NewPinger("www.google.com")
    if err != nil {
        panic(err)
    }

    pinger.Count = 3
    pinger.Run() // blocks until finished
    stats := pinger.Statistics() // get send/receive/rtt stats
    fmt.Println(stats)
    fmt.Println("connecting to mongo")
    err = ConnectToMongoClient()
    if err != nil {
        fmt.Println("failure to connect to mongo")
    }
    return "", err
}

func ConnectToMongoClient() error {
    sess := session.Must(session.NewSession(&aws.Config{
        Region: aws.String(MainRegion),
    }))

    svc := secretsmanager.New(sess)
    input := &secretsmanager.GetSecretValueInput{
        SecretId: aws.String("mongo-access"),
    }
    fmt.Println("getting credentials")

    secret, err := svc.GetSecretValue(input)
    if err != nil {
        return err
    }
    var mongo_creds MongoCreds
    secretJson := []byte(*secret.SecretString)
    err = json.Unmarshal(secretJson, &mongo_creds)
    fmt.Println("credentials fetched")
    fmt.Println(mongo_creds)
    if err != nil {
        return err
    }
    var mongoURI string 
    for _, connection := range(mongo_creds.ConnectionStrings) {
        if val, ok := connection["standard_srv"]; ok {
            mongoURI = val.(string)
        }
    }
    if mongoURI == "" {
        return errors.New("Unable to parse a connecting string from secret")
    }
    clientOptions := options.Client().ApplyURI(mongoURI).SetAuth(options.Credential{Username: "mongo_user", Password: mongo_creds.Password})
    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    client, err := mongo.Connect(ctx, clientOptions)
    fmt.Println("connecting")
    if err != nil {
        fmt.Println(err.Error())
        return err
    }

    ctx, _ = context.WithTimeout(context.Background(), 5*time.Second)
    if err = client.Ping(ctx, readpref.Primary()); err != nil {
        return err
    }
    return err
}

func main() {
    lambda.Start(HandleRequest)
}

Run Code Online (Sandbox Code Playgroud)

如果有人可以建议对我的 VPC 配置或 Lambda 代码进行实施或调整,以允许访问 Secrets Manager 和我的 Mongo 集群。理想情况下,将所有流量保留在 VPC 中,但如果需要公共互联网访问,那就这样吧。

编辑我收到的错误是超时。请注意,即使我对凭据进行硬编码(并跳过 Secret Manager 步骤),在尝试连接到 Atlas 托管的 Mongo 实例时仍然会超时。

Mar*_*k B 5

我是否需要为我的 VPC 设置 NAT 网关才能访问 Secrets Manager?或者我可以以某种方式将 Secret 托管在 VPC 中吗?

您需要创建 NAT 网关,或为 Secrets Manager 配置VPC 终端节点

这里的最佳实践是什么?

为 Secrets Manager创建 VPC 终端节点

我是否需要为 Lambda 设置 NAT 网关才能访问 Atlas 托管的 MongoDB 集群,即使它们位于同一 VPC 上并且我已将 Lambda 所在的安全组列入白名单?

不,VPC 对等互连的全部意义在于您可以直接在 VPC 内进行连接,而无需通过 Internet 出去。请注意,它们并非“位于同一 VPC 中”,而是位于具有对等连接的两个单独的 VPC 中。

我在您的 Terraform 中没有看到任何问题,在我看来,Lambda 函数应该能够连接到 Mongo Atlas 集群。如果您添加无法连接到原始问题时看到的实际错误消息,可能会有所帮助。

Terraform 代码如下所示:

resource "aws_vpc_endpoint" "secretsmanager" {
  vpc_id            = aws_vpc.main.id
  service_name      = "com.amazonaws.us-west-2.secretsmanager"
  vpc_endpoint_type = "Interface"

  security_group_ids = [
    aws_security_group.sg1.id,
  ]

  private_dns_enabled = true
}
Run Code Online (Sandbox Code Playgroud)