Terraform Lambda di Gateway NAT Koneksi VPC (Peered with MongoDB Atlas)
Saya telah membuat konfigurasi Terraform yang menerapkan cluster cloud atlas MongoDB, dan menyiapkan peer VPC dengan akun AWS saya. Konfigurasi terraform menyimpan kredensial di AWS Secrets Manager. Sayangnya, Lambda saya tidak dapat mengakses titik akhir API Manajer Rahasia atau klaster MongoDB yang dihosting di Atlas. Saya telah membaca bahwa saya perlu menyiapkan gateway NAT di VPC saya untuk mengakses internet publik. Saya bukan ahli jaringan, dan saya telah mencoba menambahkan banyak konfigurasi berbeda tetapi tidak berhasil. Tolong bantu:
- Apakah saya perlu menyiapkan gateway NAT untuk VPC saya untuk mengakses Pengelola Rahasia? Atau bisakah saya menghosting Rahasia di VPC? Apa praktik terbaik di sini?
- Apakah saya perlu menyiapkan gateway NAT untuk Lambda saya untuk mengakses klaster MongoDB yang dihosting Atlas saya, meskipun mereka berada di VPC yang sama dan saya telah memasukkan grup keamanan ke daftar putih Lambda saya?
- Bagaimana cara menyiapkan Gateway NAT agar Lambda saya dapat terhubung ke Kluster Atlas saya di Terraform?
Idealnya saya ingin mengunci koneksi ke internet luar sebanyak mungkin, tetapi jika itu bukan pilihan, saya baik-baik saja dengan implementasi yang hanya berfungsi.
Ini adalah konfigurasi Terraform saya
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]
}
}
Ini tfvars saya
admin_profile = "default"
atlas_private_key =
atlas_public_key =
atlas_org_id =
aws_account_id =
Ini adalah kebijakan Lambda saya (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":"*"
}
]
}
Ini adalah kebijakan Lambda saya (lambda-mengasumsikan-policy.json)
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": ["lambda.amazonaws.com", "ec2.amazonaws.com"]
},
"Effect": "Allow",
"Sid": ""
}
]
}
Dan ini kode (GoLang) untuk Lambda saya
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)
}
Jika ada yang dapat merekomendasikan implementasi atau penyesuaian pada konfigurasi VPC saya atau kode Lambda saya yang akan memungkinkan akses ke Secrets Manager dan Mongo Cluster saya. Idealnya, menyimpan semua lalu lintas di VPC, tetapi jika akses internet publik diperlukan, biarlah.
Edit Kesalahan yang saya dapatkan adalah Timeout. Perhatikan, meskipun saya melakukan hardcode kredensial (dan melewati langkah Secret Manager), saya masih kehabisan waktu ketika mencoba untuk terhubung ke instance Mongo yang dihosting Atlas.
Jawaban
Apakah saya perlu menyiapkan gateway NAT untuk VPC saya untuk mengakses Pengelola Rahasia? Atau bisakah saya menghosting Rahasia di VPC?
Anda juga perlu membuat gateway NAT, atau mengonfigurasi titik akhir VPC untuk Secrets Manager.
Apa praktik terbaik di sini?
Buat titik akhir VPC untuk Pengelola Rahasia.
Apakah saya perlu menyiapkan gateway NAT untuk Lambda saya untuk mengakses klaster MongoDB yang dihosting Atlas saya, meskipun mereka berada di VPC yang sama dan saya telah memasukkan grup keamanan ke daftar putih Lambda saya?
Tidak, inti dari peering VPC adalah Anda dapat terhubung langsung dalam VPC Anda, tanpa keluar melalui Internet. Perhatikan bahwa keduanya tidak "berada dalam VPC yang sama" tetapi berada dalam dua VPC terpisah dengan koneksi peering.
Saya tidak melihat masalah apa pun di Terraform Anda, menurut saya fungsi Lambda harus dapat terhubung ke cluster Mongo Atlas. Mungkin membantu jika Anda menambahkan pesan kesalahan yang sebenarnya Anda lihat ketika tidak dapat terhubung ke pertanyaan asli Anda.
Kode Terraform akan terlihat seperti ini:
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
}