【问题标题】:Terraform Lambda in VPC (Peered with MongoDB Atlas) Connection NAT GatewayVPC 中的 Terraform Lambda(与 MongoDB Atlas 对等)连接 NAT 网关
【发布时间】:2020-12-07 19:03:23
【问题描述】:

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

  1. 是否需要为我的 VPC 设置 NAT 网关才能访问 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]
  }

}

这是我的 tfvars

admin_profile     = "default"
atlas_private_key = 
atlas_public_key  = 
atlas_org_id      = 
aws_account_id    = 

这是我的 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":"*"
      }
   ]
}

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

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": "sts:AssumeRole",
            "Principal": {
                "Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"]
            },
            "Effect": "Allow",
            "Sid": ""
        }
    ]
}

这是我的 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)
}

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

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

【问题讨论】:

    标签: aws-lambda amazon-vpc terraform-provider-aws mongodb-atlas aws-secrets-manager


    【解决方案1】:

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

    您需要创建一个 NAT 网关,或者为 Secrets Manager 配置一个VPC endpoint

    这里的最佳做法是什么?

    Create a VPC endpoint 用于 Secrets Manager。

    我是否需要为我的 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
    }
    

    【讨论】:

    • 即使我覆盖了 secretmanager 请求并手动硬编码凭据,我在 ping Atlas 时也会超时。错误是超时。
    • 抱歉,我不得不对你投反对票。在 Terraform 中没有对 Secrets Manager 的 VPC 端点的记录支持。
    • @XaxD 这似乎是不必要的。我将您链接到文档。我刚刚将 Terraform 示例添加到我的答案中。我不确定您为什么说没有对此的记录支持。 Terraform 支持所有 AWS VPC 终端节点类型,您只需在配置 vpc_endpoint 资源时指定 AWS 服务名称。
    • com.amazonaws.us-west-2.secretsmanager 不受支持
    • 你能分享你得到的确切错误信息吗?
    猜你喜欢
    • 2021-12-20
    • 1970-01-01
    • 1970-01-01
    • 2021-06-06
    • 1970-01-01
    • 2020-12-07
    • 2016-12-06
    • 2019-07-14
    • 2017-06-08
    相关资源
    最近更新 更多