VPC中的Terraform Lambda与MongoDB Atlas同行连接NAT网关

问题描述

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

  1. 我是否需要为VPC设置NAT网关才能访问Secrets 经理?还是可以以某种方式仅将秘密存储在VPC中?什么是 最佳实践?
  2. 我是否需要为Lambda设置NAT网关 访问我的Atlas托管的MongoDB集群,即使它们位于 同一台VPC,我已经将我的Lambda所在的安全组列入了白名单?
  3. 如何设置NAT网关以允许Lambda连接到我的 terraform中的Atlas集群?

理想情况下,我想尽可能地锁定与外部Internet的连接,但是,如果这不是一种选择,那么任何可行的实现都可以。

这是我的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 Cluster。理想情况下,将所有流量都保留在VPC中,但是如果需要公共Internet访问,就应该这样做。

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

解决方法

我是否需要为VPC设置NAT网关才能访问Secrets 经理?还是我可以以某种方式仅在VPC中托管秘密?

您要么需要创建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
}