Amazon RDS에서 민감한 데이터 보호
다음 리소스가 생성됩니다.
아마존 RDS
terraform 파일 생성
infra/plan/rds.tf
resource "random_string" "db_suffix" {
length = 4
special = false
upper = false
}
resource "random_string" "root_username" {
length = 12
special = false
upper = true
}
resource "random_password" "root_password" {
length = 12
special = true
upper = true
}
resource "aws_db_instance" "postgresql" {
# Engine options
engine = "postgres"
engine_version = "12.5"
# Settings
name = "postgresql${var.env}"
identifier = "postgresql-${var.env}"
# Credentials Settings
username = "u${random_string.root_username.result}"
password = "p${random_password.root_password.result}"
# DB instance size
instance_class = "db.m5.large"
# Storage
storage_type = "gp2"
allocated_storage = 100
max_allocated_storage = 200
# Availability & durability
multi_az = true
# Connectivity
db_subnet_group_name = aws_db_subnet_group.sg.id
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.sg.id]
port = var.rds_port
# Database authentication
iam_database_authentication_enabled = true
# Additional configuration
parameter_group_name = "default.postgres12"
# Backup
backup_retention_period = 14
backup_window = "03:00-04:00"
final_snapshot_identifier = "postgresql-final-snapshot-${random_string.db_suffix.result}"
delete_automated_backups = true
skip_final_snapshot = false
# Encryption
storage_encrypted = true
# Maintenance
auto_minor_version_upgrade = true
maintenance_window = "Sat:00:00-Sat:02:00"
# Deletion protection
deletion_protection = false
tags = {
Environment = var.env
}
}
다음 출력 추가
output "rds-username" {
value = "u${random_string.root_username.result}"
}
output "rds-password" {
value = "p${random_password.root_password.result}"
}
output "private-rds-endpoint" {
value = aws_db_instance.postgresql.address
}
DB 서브넷 그룹
프라이빗 서브넷에 Amazon RDS 인스턴스를 배포합니다.
resource "aws_db_subnet_group" "sg" {
name = "postgresql-${var.env}"
subnet_ids = [aws_subnet.private["private-rds-1"].id, aws_subnet.private["private-rds-2"].id]
tags = {
Environment = var.env
Name = "postgresql-${var.env}"
}
}
VPC 보안 그룹
VPC 보안 그룹에서 다음을 허용합니다.
resource "aws_security_group" "sg" {
name = "postgresql-${var.env}"
description = "Allow inbound/outbound traffic"
vpc_id = aws_vpc.main.id
ingress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.private["private-rds-1"].cidr_block]
}
ingress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.private["private-rds-2"].cidr_block]
}
ingress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.public["public-rds-1"].cidr_block]
}
ingress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.public["public-rds-2"].cidr_block]
}
egress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = [aws_subnet.private["private-rds-1"].cidr_block]
}
egress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = [aws_subnet.private["private-rds-2"].cidr_block]
}
egress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.public["public-rds-1"].cidr_block]
}
egress {
from_port = var.rds_port
to_port = var.rds_port
protocol = "tcp"
cidr_blocks = [aws_subnet.public["public-rds-2"].cidr_block]
}
tags = {
Name = "postgresql-${var.env}"
Environment = var.env
}
}
(선택 사항) RDS 인스턴스 노출
로컬 시스템에서 또는 외부 CI/CD 도구를 통해 RDS 인스턴스 데이터베이스에 액세스하려는 경우 외부 네트워크 로드 밸런서를 생성하고 RDS 인스턴스의 사설 IP 주소를 대상으로 지정할 수 있습니다. 인스턴스가 실패하면 네트워크 인터페이스의 프라이빗 IP 주소가 변경될 수 있으므로 Lambda 함수를 배포하여 현재 프라이빗 IP 주소를 지속적으로 확인하고 이전 IP 주소를 등록 해제하고 새 프라이빗 IP 주소로 새 대상을 등록할 수 있습니다.
네트워크 로드 밸런서
RDS 프라이빗 IP 주소에 도달하려면 RDS 인스턴스와 외부 네트워크 로드 밸런서가 동일한 가용 영역에 있어야 합니다. 따라서 NLB는 기본 RDS 인스턴스와 동일한 서브넷에 배포됩니다.
대상 유형이 IP 주소인 대상 그룹을 생성합니다. NLB와 RDS 간의 연결을 모니터링하기 위해 Cloud Watch 경보가 추가되었습니다.
파일 생성infra/plan/nlb.tf
locals {
subnet_id = aws_subnet.public["public-rds-1"].availability_zone == aws_db_instance.postgresql.availability_zone ? aws_subnet.public["public-rds-1"].id : aws_subnet.public["public-rds-2"].id
}
resource "aws_lb" "rds" {
name = "nlb-expose-rds-${var.env}"
internal = false
load_balancer_type = "network"
subnets = [local.subnet_id]
enable_deletion_protection = false
tags = {
Environment = var.env
}
}
resource "aws_lb_listener" "rds" {
load_balancer_arn = aws_lb.rds.id
port = var.rds_port
protocol = "TCP"
default_action {
target_group_arn = aws_lb_target_group.rds.id
type = "forward"
}
}
resource "aws_lb_target_group" "rds" {
name = "expose-rds-${var.env}"
port = var.rds_port
protocol = "TCP"
target_type = "ip"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
protocol = "TCP"
}
tags = {
Environment = var.env
}
}
resource "aws_cloudwatch_metric_alarm" "rds-access" {
alarm_name = "rds-external-access-status"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "1"
metric_name = "UnHealthyHostCount"
namespace = "AWS/NetworkELB"
period = "60"
statistic = "Maximum"
threshold = 1
alarm_description = "Monitoring RDS External Access"
treat_missing_data = "breaching"
dimensions = {
TargetGroup = aws_lb_target_group.rds.arn_suffix
LoadBalancer = aws_lb.rds.arn_suffix
}
}
파일 완성infra/plan/output
output "public-rds-endpoint" {
value = "${element(split("/", aws_lb.rds.arn), 2)}-${element(split("/", aws_lb.rds.arn), 3)}.elb.${var.region}.amazonaws.com"
}
이제 대상을 등록해야 합니다. Lambda 함수를 사용하여 작업을 수행할 수 있습니다. Amazon CloudWatch 이벤트 규칙이 추가되어 15분마다 Lambda 함수를 호출합니다.
람다 함수
파일 생성infra/plan/lambda.tf
data "archive_file" "lambda_zip" {
type = "zip"
source_file = "${path.module}/lambda/populate-nlb-tg-with-rds-private-ip.py"
output_path = "lambda_function_payload.zip"
}
resource "aws_lambda_function" "rds" {
filename = "lambda_function_payload.zip"
function_name = "populate-nlb-tg-with-rds-private-ip"
role = aws_iam_role.iam_for_lambda.arn
handler = "populate-nlb-tg-with-rds-private-ip.handler"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
runtime = "python3.8"
timeout = 300
environment {
variables = {
RDS_PORT = var.rds_port
NLB_TG_ARN = aws_lb_target_group.rds.arn
RDS_SG_ID = aws_security_group.sg.id
RDS_ID = aws_db_instance.postgresql.id
}
}
tags = {
Environment = var.env
}
}
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "lambda_nlb" {
name = "nlb-tg-access"
role = aws_iam_role.iam_for_lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ec2:DescribeNetworkInterfaces",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:RegisterTargets",
"rds:DescribeDBInstances"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
resource "aws_iam_role_policy" "lambda_logging" {
name = "lambda_logging"
role = aws_iam_role.iam_for_lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
]
})
}
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/${aws_lambda_function.rds.function_name}"
retention_in_days = 1
}
resource "aws_cloudwatch_event_rule" "lambda" {
name = "populate-nlb-tg-with-rds-private-ip"
description = "Populate NLB tg with RDS private IP"
schedule_expression = "rate(15 minutes)"
}
resource "aws_cloudwatch_event_target" "lambda" {
rule = aws_cloudwatch_event_rule.lambda.name
target_id = "Lambda"
arn = aws_lambda_function.rds.arn
}
resource "aws_lambda_permission" "cloudwatch" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.rds.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.lambda.arn
}
람다 함수는 파이썬으로 작성되었습니다. 프로세스는 다음과 같습니다.
locals {
subnet_id = aws_subnet.public["public-rds-1"].availability_zone == aws_db_instance.postgresql.availability_zone ? aws_subnet.public["public-rds-1"].id : aws_subnet.public["public-rds-2"].id
}
resource "aws_lb" "rds" {
name = "nlb-expose-rds-${var.env}"
internal = false
load_balancer_type = "network"
subnets = [local.subnet_id]
enable_deletion_protection = false
tags = {
Environment = var.env
}
}
resource "aws_lb_listener" "rds" {
load_balancer_arn = aws_lb.rds.id
port = var.rds_port
protocol = "TCP"
default_action {
target_group_arn = aws_lb_target_group.rds.id
type = "forward"
}
}
resource "aws_lb_target_group" "rds" {
name = "expose-rds-${var.env}"
port = var.rds_port
protocol = "TCP"
target_type = "ip"
vpc_id = aws_vpc.main.id
health_check {
enabled = true
protocol = "TCP"
}
tags = {
Environment = var.env
}
}
resource "aws_cloudwatch_metric_alarm" "rds-access" {
alarm_name = "rds-external-access-status"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "1"
metric_name = "UnHealthyHostCount"
namespace = "AWS/NetworkELB"
period = "60"
statistic = "Maximum"
threshold = 1
alarm_description = "Monitoring RDS External Access"
treat_missing_data = "breaching"
dimensions = {
TargetGroup = aws_lb_target_group.rds.arn_suffix
LoadBalancer = aws_lb.rds.arn_suffix
}
}
output "public-rds-endpoint" {
value = "${element(split("/", aws_lb.rds.arn), 2)}-${element(split("/", aws_lb.rds.arn), 3)}.elb.${var.region}.amazonaws.com"
}
data "archive_file" "lambda_zip" {
type = "zip"
source_file = "${path.module}/lambda/populate-nlb-tg-with-rds-private-ip.py"
output_path = "lambda_function_payload.zip"
}
resource "aws_lambda_function" "rds" {
filename = "lambda_function_payload.zip"
function_name = "populate-nlb-tg-with-rds-private-ip"
role = aws_iam_role.iam_for_lambda.arn
handler = "populate-nlb-tg-with-rds-private-ip.handler"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
runtime = "python3.8"
timeout = 300
environment {
variables = {
RDS_PORT = var.rds_port
NLB_TG_ARN = aws_lb_target_group.rds.arn
RDS_SG_ID = aws_security_group.sg.id
RDS_ID = aws_db_instance.postgresql.id
}
}
tags = {
Environment = var.env
}
}
resource "aws_iam_role" "iam_for_lambda" {
name = "iam_for_lambda"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
resource "aws_iam_role_policy" "lambda_nlb" {
name = "nlb-tg-access"
role = aws_iam_role.iam_for_lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ec2:DescribeNetworkInterfaces",
"elasticloadbalancing:DeregisterTargets",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:RegisterTargets",
"rds:DescribeDBInstances"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
resource "aws_iam_role_policy" "lambda_logging" {
name = "lambda_logging"
role = aws_iam_role.iam_for_lambda.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
]
})
}
resource "aws_cloudwatch_log_group" "lambda" {
name = "/aws/lambda/${aws_lambda_function.rds.function_name}"
retention_in_days = 1
}
resource "aws_cloudwatch_event_rule" "lambda" {
name = "populate-nlb-tg-with-rds-private-ip"
description = "Populate NLB tg with RDS private IP"
schedule_expression = "rate(15 minutes)"
}
resource "aws_cloudwatch_event_target" "lambda" {
rule = aws_cloudwatch_event_rule.lambda.name
target_id = "Lambda"
arn = aws_lambda_function.rds.arn
}
resource "aws_lambda_permission" "cloudwatch" {
statement_id = "AllowExecutionFromCloudWatch"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.rds.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.lambda.arn
}
describe_target_health
함수를 사용하여 현재 등록된 IP를 가져옵니다. describe_db_instances
함수를 사용하여 현재 RDS 인스턴스 가용성 영역을 가져옵니다. describe_network_interfaces
기능을 사용하여 현재 RDS 사설 IP를 검색합니다. Registry Target
이 없으면 새 항목을 만듭니다. 현재 등록 대상이 오래된 경우 등록을 취소하고 새 RDS 개인 IP 주소로 새 등록 대상을 만듭니다. 파일 생성
infra/plan/lambda/populate-nlb-tg-with-rds-private-ip.py
import json
import os
import random
import sys
import boto3
import logging
from datetime import datetime
from botocore.exceptions import ClientError
logger = logging.getLogger()
logger.setLevel(logging.INFO)
'''
This function populates a Network Load Balancer's target group with RDS IP addresses
Configure these environment variables in your Lambda environment
1. NLB_TG_ARN - The ARN of the Network Load Balancer's target group
2. RDS_PORT
3. RDS_SG_ID - RDS VPC Security Group Id
4. RDS_ID - RDS Identifier
'''
NLB_TG_ARN = os.environ['NLB_TG_ARN']
RDS_PORT = int(os.environ['RDS_PORT'])
RDS_SG_ID = os.environ['RDS_SG_ID']
RDS_ID = os.environ['RDS_ID']
try:
elbv2client = boto3.client('elbv2')
except ClientError as e:
logger.error(e.response['Error']['Message'])
sys.exit(1)
try:
rdsclient = boto3.client('rds')
except ClientError as e:
logger.error(e.response['Error']['Message'])
sys.exit(1)
try:
ec2client = boto3.client('ec2')
except ClientError as e:
logger.error(e.response['Error']['Message'])
sys.exit(1)
def register_target(tg_arn, new_target_list):
logger.info(f"INFO: Register new_target_list:{new_target_list}")
try:
elbv2client.register_targets(
TargetGroupArn=tg_arn,
Targets=new_target_list
)
except ClientError as e:
logger.error(e.response['Error']['Message'])
def deregister_target(tg_arn, new_target_list):
try:
logger.info(f"INFO: Deregistering targets: {new_target_list}")
elbv2client.deregister_targets(
TargetGroupArn=tg_arn,
Targets=new_target_list
)
except ClientError as e:
logger.error(e.response['Error']['Message'])
def target_group_list(ip_list):
target_list = []
for ip in ip_list:
target = {
'Id': ip,
'Port': RDS_PORT,
}
target_list.append(target)
return target_list
def get_registered_ips(tg_arn):
registered_ip_list = []
try:
response = elbv2client.describe_target_health(
TargetGroupArn=tg_arn)
registered_ip_count = len(response['TargetHealthDescriptions'])
logger.info(f"INFO: Number of currently registered IP: {registered_ip_count}")
for target in response['TargetHealthDescriptions']:
registered_ip = target['Target']['Id']
registered_ip_list.append(registered_ip)
except ClientError as e:
logger.error(e.response['Error']['Message'])
return registered_ip_list
def get_rds_private_ips(rds_az):
resp = ec2client.describe_network_interfaces(Filters=[{
'Name': 'group-id',
'Values': [RDS_SG_ID]
}, {
'Name': 'availability-zone',
'Values': [rds_az]
}])
private_ip_address = []
for interface in resp['NetworkInterfaces']:
private_ip_address.append(interface['PrivateIpAddress'])
return private_ip_address
def get_rds_az():
logger.info(f"INFO: Get RDS current AZ: {RDS_ID}")
az = None
try:
response = rdsclient.describe_db_instances(
DBInstanceIdentifier=RDS_ID
)
if len(response['DBInstances']) > 0:
az = response['DBInstances'][0]['AvailabilityZone']
logger.info(f"INFO: RDS AZ is: {az}")
except ClientError as e:
logger.error(e.response['Error']['Message'])
return az
def handler(event, context):
registered_ip_list = get_registered_ips(NLB_TG_ARN)
current_rds_az = get_rds_az()
new_active_ip_set = get_rds_private_ips(current_rds_az)
registration_ip_list = []
# IPs that have not been registered
if len(registered_ip_list) == 0 or registered_ip_list != new_active_ip_set:
registration_ip_list = new_active_ip_set
if registration_ip_list:
registerTarget_list = target_group_list(registration_ip_list)
register_target(NLB_TG_ARN, registerTarget_list)
logger.info(f"INFO: Registering {registration_ip_list}")
else:
logger.info(f"INFO: No new target registered")
deregistration_ip_list = []
if registered_ip_list != new_active_ip_set:
for ip in registered_ip_list:
deregistration_ip_list.append(ip)
logger.info(f"INFO: Deregistering IP: {ip}")
deregisterTarget_list = target_group_list(deregistration_ip_list)
deregister_target(NLB_TG_ARN, deregisterTarget_list)
else:
logger.info(f"INFO: No old target deregistered")
파일 완성
infra/plan/variable.tf
:variable "rds_port" {
type = number
default = 5432
}
RDS 인스턴스를 배포해 보겠습니다.
cd infra/envs/dev
terraform apply ../../plan/
다음 부분으로 이동하기 전에 Amazon RDS 인스턴스에
metabase
데이터베이스를 생성해야 합니다.PGPASSWORD=$(terraform output rds-password) psql --host $(terraform output public-rds-endpoint) --port 5432 --user $(terraform output rds-username) --dbname postgres
CREATE USER metabase;
GRANT rds_iam TO metabase;
CREATE DATABASE metabase;
GRANT ALL ON DATABASE metabase TO metabase;
모든 리소스가 생성되고 올바르게 작동하는지 확인하겠습니다.
RDS 인스턴스
VPC 보안 그룹
람다
NLB 대상 그룹
결론
이제 RDS 인스턴스를 사용할 수 있습니다. 에서는 Amazon EKS에 배포된 컨테이너와 Amazon RDS 인스턴스에서 생성된 데이터베이스 간에 연결을 설정합니다.
Reference
이 문제에 관하여(Amazon RDS에서 민감한 데이터 보호), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://dev.to/stack-labs/securing-the-connectivity-between-amazon-eks-and-amazon-rds-part-4-58n2
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(Amazon RDS에서 민감한 데이터 보호), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/stack-labs/securing-the-connectivity-between-amazon-eks-and-amazon-rds-part-4-58n2텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)