问题描述
我已经构建了一个terraform配置,该配置可部署MongoDB Atlas云集群,并使用我的AWS账户设置VPC对等端。 terraform配置将凭证存储在AWS Secrets Manager中。不幸的是,我的Lambda无法访问Secrets Manager API端点或Atlas上托管的MongoDB集群。我已经读到我需要在VPC上设置NAT网关才能访问公共互联网。我不是网络专业人士,我尝试添加许多不同的配置都无济于事。请帮助:
- 我是否需要为VPC设置NAT网关才能访问Secrets 经理?还是可以以某种方式仅将秘密存储在VPC中?什么是 最佳实践?
- 我是否需要为Lambda设置NAT网关 访问我的Atlas托管的MongoDB集群,即使它们位于 同一台VPC,我已经将我的Lambda所在的安全组列入了白名单?
- 如何设置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
}