PLT-SEC-002 전송 중 암호화 구현 가이드
🎯 개요
PLT-SEC-002 요구사항
"모든 네트워크 통신은 TLS를 사용한다"
- ✅ 외부 API 통신: TLS 1.2 이상 필수 (이미 구현됨)
- ⚠️ 내부 서비스 간 통신: TLS 또는 mTLS (구현 필요)
- ✅ 데이터베이스 연결: SSL/TLS 필수 (이미 구현됨)
구현 전략
기존 Cloud Run 인프라를 활용한 단계적 mTLS 구현
현재 DTx 플랫폼의 의료 데이터 보안 요구사항을 충족하기 위해 내부 서비스 간 mutual TLS(mTLS) 암호화를 구현합니다.
🏗️ 현재 통신 구조 분석
식별된 서비스 간 통신
| 통신 유형 | 소스 서비스 | 대상 서비스 | 현재 암호화 | 필요 조치 |
|---|---|---|---|---|
| 동기 API 호출 | dta-wide-mcp | dta-wide-api | HTTPS | mTLS 강화 |
| 동기 API 호출 | dta-wide-agent-qa | dta-wide-api | HTTPS | mTLS 강화 |
| 비동기 메시징 | 모든 서비스 | Pub/Sub | TLS | 이미 안전 |
| 데이터베이스 | 모든 서비스 | Cloud SQL | SSL/TLS | 인증서 검증 강화 |
| 외부 API | 모든 서비스 | OpenAI 등 | HTTPS | 이미 안전 |
현재 구현 상태
✅ 이미 안전한 부분
// 외부 API 통신 - HTTPS 사용
const response = await this.httpService.post('https://api.openai.com/v1/chat/completions', {
// OpenAI API 호출 - 이미 HTTPS
});
// Pub/Sub 통신 - Google managed TLS
await this.pubSubService.publish('events', eventData);
⚠️ mTLS 구현 필요한 부분
// 내부 서비스 간 통신 - mTLS 필요
// apps/dta-wide-mcp/src/mcp-server/services/api-client.service.ts
this.apiBaseUrl = `${appConfig?.['dtaWideApiHost'] || 'http://localhost:3002'}/v1`;
// ^^^^ mTLS로 개선 필요
// apps/dta-wide-agent-qa/src/mcp-server/services/api-client.service.ts
this.apiBaseUrl = `${appConfig?.['apiHost'] || 'http://localhost:3002'}/v1`;
// ^^^^ mTLS로 개선 필요
🔧 mTLS 구현 솔루션
1. Google Cloud Run Service Connect 활용 (권장)
Google Cloud의 관리형 서비스 메시를 활용하여 자동 mTLS 구현
장점
- ✅ Google 완전 관리: 인증서 자동 발급, 갱신, 배포
- ✅ Zero Trust 네트워크: 서비스 ID 기반 인증
- ✅ 투명한 구현: 애플리케이션 코드 변경 최소화
- ✅ Cloud Monitoring 통합: mTLS 연결 상태 모니터링
구현 단계
1단계: Service Connect 활성화
# cloudrun-deploy/dta-wide-api/cloudrun/prod/terragrunt.hcl
inputs = {
cloud_run_service = [
{
name = "dta-wide-api"
template = {
# Service Connect 설정
annotations = {
# mTLS 활성화
"run.googleapis.com/network-interfaces" = jsonencode([
{
network = "projects/${local.project}/global/networks/default"
subnetwork = "projects/${local.project}/regions/${local.location}/subnetworks/default"
}
])
# PLT-SEC-002 전송 중 암호화 어노테이션
"dta.weltcorp.com/tls-policy" = "mtls-required"
"dta.weltcorp.com/security-level" = "high"
}
# Service Connect Identity
service_account = "dta-wide-api@${local.project}.iam.gserviceaccount.com"
}
# mTLS 정책 설정
ingress = "INGRESS_TRAFFIC_INTERNAL_AND_CLOUD_LOAD_BALANCING"
}
]
}
2단계: 서비스 간 인증 설정
# infrastructure/terraform/modules/mtls-service-connect/main.tf
# Service Connect 네트워크 엔드포인트 그룹
resource "google_compute_network_endpoint_group" "dta_wide_api_neg" {
name = "dta-wide-api-neg"
network = "default"
subnetwork = "default"
default_port = 8080
zone = "europe-west3-a"
cloud_run {
service = "dta-wide-api"
}
}
# Service Connect 서비스 연결
resource "google_compute_backend_service" "dta_wide_api_backend" {
name = "dta-wide-api-backend"
protocol = "HTTPS"
load_balancing_scheme = "INTERNAL_SELF_MANAGED"
backend {
group = google_compute_network_endpoint_group.dta_wide_api_neg.id
}
# mTLS 설정
security_policy = google_compute_security_policy.mtls_policy.id
}
# mTLS 보안 정책
resource "google_compute_security_policy" "mtls_policy" {
name = "mtls-required-policy"
rule {
action = "allow"
priority = "1000"
match {
expr {
expression = "has(request.auth.principal) && request.auth.principal.startswith('serviceAccount:')"
}
}
description = "Allow authenticated service accounts only"
}
rule {
action = "deny(403)"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Deny all other traffic"
}
}
2. 애플리케이션 레벨 mTLS 구현
NestJS 애플리케이션에서 직접 mTLS 인증서 처리
HttpService mTLS 설정
// apps/dta-wide-mcp/src/config/mtls-config.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs';
import * as https from 'https';
@Injectable()
export class MtlsConfigService {
constructor(private readonly configService: ConfigService) {}
createMtlsAgent(): https.Agent {
const environment = this.configService.get<string>('NODE_ENV');
// Production에서만 mTLS 적용
if (environment === 'production') {
const clientCert = fs.readFileSync('/app/certs/client.crt');
const clientKey = fs.readFileSync('/app/certs/client.key');
const caCert = fs.readFileSync('/app/certs/ca.crt');
return new https.Agent({
cert: clientCert,
key: clientKey,
ca: caCert,
rejectUnauthorized: true, // 인증서 검증 강제
});
}
// Development에서는 기본 HTTPS
return new https.Agent({ rejectUnauthorized: false });
}
}
// apps/dta-wide-mcp/src/mcp-server/services/api-client.service.ts (수정)
@Injectable()
export class ApiClientService {
private readonly httpsAgent: https.Agent;
constructor(
private readonly logger: LoggerService,
private readonly httpService: HttpService,
private readonly configService: ConfigService,
private readonly tokenManager: TokenManagerService,
private readonly mtlsConfig: MtlsConfigService // 추가
) {
this.logger.setContext('ApiClientService');
// mTLS Agent 설정
this.httpsAgent = this.mtlsConfig.createMtlsAgent();
const appConfig = this.configService.getNamespace<Record<string, any>>('app');
this.apiBaseUrl = `${appConfig?.['dtaWideApiHost'] || 'https://localhost:3002'}/v1`;
// ^^^^^ HTTPS로 변경
}
private async makeRequest<T>(
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
endpoint: string,
data?: any,
userId?: string
): Promise<AxiosResponse<T>> {
const config: AxiosRequestConfig = {
method,
url: `${this.apiBaseUrl}${endpoint}`,
data,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'dta-wide-mcp/1.0',
// PLT-SEC-002 전송 중 암호화 헤더
'X-TLS-Policy': 'mtls-required',
'X-Security-Level': 'high'
},
httpsAgent: this.httpsAgent, // mTLS Agent 사용
timeout: 30000,
};
// JWT 토큰 추가 (기존 로직)
if (userId) {
const tokens = this.tokenManager.getTokens(userId);
if (tokens?.accessToken) {
config.headers!['Authorization'] = `Bearer ${tokens.accessToken}`;
}
}
try {
const response = await firstValueFrom(this.httpService.request<T>(config));
this.logger.debug(`API request successful: ${method} ${endpoint}`, {
statusCode: response.status,
tlsVersion: response.request?.socket?.getProtocol?.(), // TLS 버전 로깅
});
return response;
} catch (error) {
this.logger.error(`API request failed: ${method} ${endpoint}`, {
error: error.message,
tlsError: error.code === 'CERT_AUTHORITY_INVALID' ? 'mTLS certificate validation failed' : null,
});
throw error;
}
}
}
3. 인증서 관리 자동화
Google Certificate Manager를 활용한 인증서 라이프사이클 관리
# infrastructure/terraform/modules/certificate-management/main.tf
# Certificate Authority 생성
resource "google_privateca_ca_pool" "dta_wide_ca_pool" {
name = "dta-wide-mtls-ca-pool"
location = var.region
tier = "ENTERPRISE"
publishing_options {
publish_ca_cert = true
publish_crl = true
}
labels = {
environment = var.environment
purpose = "mtls-internal-communication"
}
}
resource "google_privateca_certificate_authority" "dta_wide_ca" {
location = var.region
pool = google_privateca_ca_pool.dta_wide_ca_pool.name
certificate_authority_id = "dta-wide-root-ca"
config {
subject_config {
subject {
organization = "Welt Corporation"
organizational_unit = "DTA Wide Platform"
locality = "Seoul"
province = "Seoul"
country_code = "KR"
common_name = "DTA Wide Root CA"
}
}
x509_config {
ca_options {
is_ca = true
}
key_usage {
base_key_usage {
cert_sign = true
crl_sign = true
}
}
}
}
type = "SELF_SIGNED"
key_spec {
algorithm = "RSA_PKCS1_4096_SHA256"
}
# 10년 유효
lifetime = "87600h"
}
# 서비스별 인증서 템플릿
resource "google_privateca_certificate_template" "service_template" {
location = var.region
name = "dta-wide-service-template"
predefined_values {
key_usage {
base_key_usage {
key_encipherment = true
digital_signature = true
}
extended_key_usage {
server_auth = true
client_auth = true
}
}
}
identity_constraints {
cel_expression {
expression = "subject_alt_names.all(san, san.type == DNS || san.type == EMAIL || san.type == URI)"
title = "Allow DNS, Email, and URI SANs"
}
allow_subject_passthrough = false
allow_subject_alt_names_passthrough = true
}
}
# Cloud Run 서비스용 인증서 자동 생성
resource "google_privateca_certificate" "dta_wide_api_cert" {
for_each = toset(["dta-wide-api", "dta-wide-mcp", "dta-wide-agent-qa"])
location = var.region
pool = google_privateca_ca_pool.dta_wide_ca_pool.name
name = "${each.key}-mtls-cert"
config {
subject_config {
subject {
organization = "Welt Corporation"
organizational_unit = "DTA Wide Platform"
common_name = "${each.key}.internal.dta-wide.com"
}
subject_alt_name {
dns_names = [
"${each.key}.internal.dta-wide.com",
"${each.key}-${var.environment}.run.app"
]
}
}
x509_config {
key_usage {
base_key_usage {
key_encipherment = true
digital_signature = true
}
extended_key_usage {
server_auth = true
client_auth = true
}
}
}
public_key {
format = "PEM"
key = base64decode(google_kms_crypto_key_version.service_key[each.key].public_key[0].pem)
}
}
# 1년 유효, 자동 갱신
lifetime = "8760h"
labels = {
service = each.key
environment = var.environment
purpose = "mtls-communication"
}
}
# KMS를 통한 개인키 관리
resource "google_kms_key_ring" "cert_key_ring" {
name = "dta-wide-mtls-keys"
location = "global"
}
resource "google_kms_crypto_key" "service_keys" {
for_each = toset(["dta-wide-api", "dta-wide-mcp", "dta-wide-agent-qa"])
name = "${each.key}-mtls-key"
key_ring = google_kms_key_ring.cert_key_ring.id
purpose = "ASYMMETRIC_SIGN"
version_template {
algorithm = "RSA_SIGN_PKCS1_4096_SHA256"
}
rotation_period = "2592000s" # 30일마다 키 로테이션
}
resource "google_kms_crypto_key_version" "service_key" {
for_each = toset(["dta-wide-api", "dta-wide-mcp", "dta-wide-agent-qa"])
crypto_key = google_kms_crypto_key.service_keys[each.key].id
}
🔍 Cloud SQL SSL/TLS 강화
현재 상태 개선
# cloudrun-deploy/dta-wide-api/cloudrun/prod/terragrunt.hcl (수정)
inputs = {
cloud_run_service = [
{
template = {
containers = [
{
env = [
# ... 기존 환경변수 ...
# PLT-SEC-002 데이터베이스 SSL 강화
{
name = "DATABASE_SSL_MODE"
value = "require" # SSL 연결 강제
},
{
name = "DATABASE_SSL_CERT_VERIFICATION"
value = "full" # 인증서 완전 검증
},
{
name = "DATABASE_SSL_ROOT_CERT"
value = "/app/certs/server-ca.pem"
}
]
}
]
# SSL 인증서 볼륨 마운트
volumes = [
# ... 기존 볼륨 ...
{
name = "database-ssl-certs"
secret = [
{
secret = "database-ssl-certificates"
default_mode = 400 # 읽기 전용
items = [
{
version = "1"
path = "server-ca.pem"
},
{
version = "1"
path = "client-cert.pem"
},
{
version = "1"
path = "client-key.pem"
}
]
}
]
}
]
}
}
]
}
Prisma SSL 설정 강화
// libs/core/database/src/prisma.service.ts (수정)
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor(private readonly configService: ConfigService) {
const databaseUrl = configService.get<string>('DATABASE_URL');
const sslMode = configService.get<string>('DATABASE_SSL_MODE', 'prefer');
const sslCertVerification = configService.get<string>('DATABASE_SSL_CERT_VERIFICATION', 'basic');
// PLT-SEC-002 강화된 SSL 연결 설정
const sslConfig = sslMode === 'require' ? {
ssl: {
rejectUnauthorized: sslCertVerification === 'full',
ca: sslCertVerification === 'full'
? fs.readFileSync('/app/certs/server-ca.pem', 'utf8')
: undefined,
cert: sslCertVerification === 'full'
? fs.readFileSync('/app/certs/client-cert.pem', 'utf8')
: undefined,
key: sslCertVerification === 'full'
? fs.readFileSync('/app/certs/client-key.pem', 'utf8')
: undefined,
}
} : {};
super({
datasources: {
db: {
url: `${databaseUrl}?sslmode=${sslMode}${sslConfig.ssl ? '&ssl=true' : ''}`
}
}
});
}
async onModuleInit(): Promise<void> {
await this.$connect();
// SSL 연결 상태 검증
const result = await this.$queryRaw`SELECT
version() as db_version,
CASE WHEN ssl = 't' THEN 'SSL Enabled' ELSE 'SSL Disabled' END as ssl_status
FROM pg_stat_ssl WHERE pid = pg_backend_pid()`;
console.log('Database SSL Status:', result);
}
}
📊 모니터링 및 검증
1. mTLS 연결 상태 모니터링
// apps/dta-wide-api/src/health/security-health.controller.ts
@Controller('health/security')
export class SecurityHealthController {
@Get('tls')
async getTlsStatus(): Promise<TlsHealthStatus> {
const checks = await Promise.allSettled([
this.checkDatabaseSsl(),
this.checkServiceMtls(),
this.checkCertificateStatus()
]);
return {
status: checks.every(c => c.status === 'fulfilled') ? 'secure' : 'insecure',
timestamp: new Date().toISOString(),
checks: {
database_ssl: this.parseCheck(checks[0]),
service_mtls: this.parseCheck(checks[1]),
certificates: this.parseCheck(checks[2])
},
compliance: {
plt_sec_002: checks.every(c => c.status === 'fulfilled'),
tls_version: '1.3',
mtls_enabled: true
}
};
}
private async checkDatabaseSsl(): Promise<SslCheckResult> {
try {
const result = await this.prisma.$queryRaw`
SELECT ssl, cipher, bits, version
FROM pg_stat_ssl
WHERE pid = pg_backend_pid()
`;
return {
status: 'ok',
details: result[0] || {},
secure: result[0]?.ssl === true
};
} catch (error) {
return {
status: 'error',
details: { error: error.message },
secure: false
};
}
}
private async checkServiceMtls(): Promise<MtlsCheckResult> {
try {
// 다른 서비스에 mTLS 테스트 요청
const response = await this.httpService.get('https://dta-wide-mcp.internal/health', {
httpsAgent: this.mtlsAgent,
timeout: 5000
}).toPromise();
return {
status: 'ok',
details: {
connected: true,
tls_version: response.request?.socket?.getProtocol?.(),
peer_certificate: response.request?.socket?.getPeerCertificate?.()?.subject
},
secure: true
};
} catch (error) {
return {
status: 'error',
details: { error: error.message },
secure: false
};
}
}
}
2. Cloud Monitoring 통합
# infrastructure/terraform/modules/mtls-monitoring/main.tf
# mTLS 상태 모니터링 대시보드
resource "google_monitoring_dashboard" "mtls_security_dashboard" {
dashboard_json = jsonencode({
displayName = "PLT-SEC-002 TLS/mTLS Security Dashboard"
mosaicLayout = {
tiles = [
{
width = 6
height = 4
widget = {
title = "mTLS Connection Success Rate"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"custom.googleapis.com/mtls/connection_success_rate\""
}
}
}]
}
}
},
{
width = 6
height = 4
widget = {
title = "SSL/TLS Certificate Expiry"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"gce_instance\" AND metric.type=\"custom.googleapis.com/ssl/certificate_days_to_expiry\""
}
}
}]
}
}
}
]
}
})
}
# mTLS 실패 알림
resource "google_monitoring_alert_policy" "mtls_failure_alert" {
display_name = "mTLS Connection Failures - PLT-SEC-002"
combiner = "OR"
enabled = true
conditions {
display_name = "mTLS connection failure rate > 5%"
condition_threshold {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"custom.googleapis.com/mtls/connection_failure_rate\""
duration = "300s"
comparison = "COMPARISON_GREATER_THAN"
threshold_value = 5.0
aggregations {
alignment_period = "300s"
per_series_aligner = "ALIGN_RATE"
}
}
}
notification_channels = var.security_notification_channels
alert_strategy {
notification_rate_limit {
period = "300s"
}
}
documentation {
content = <<-EOT
PLT-SEC-002 요구사항 위반: mTLS 연결 실패율이 5%를 초과했습니다.
다음 단계를 수행하세요:
1. /health/security/tls 엔드포인트로 TLS 상태 확인
2. Certificate Manager에서 인증서 상태 확인
3. 서비스 간 네트워크 연결 상태 확인
4. 필요시 인증서 갱신 또는 서비스 재시작
보안 문제이므로 즉시 대응이 필요합니다.
EOT
mime_type = "text/markdown"
}
}
🚀 배포 가이드
1단계: 환경별 점진적 배포
# 1. Certificate Authority 및 인증서 생성
cd infrastructure/terragrunt/prod/certificate-management
terragrunt apply
# 2. mTLS 인프라 배포
cd infrastructure/terragrunt/prod/mtls-service-connect
terragrunt apply
# 3. 애플리케이션 설정 업데이트
cd cloudrun-deploy/dta-wide-api/cloudrun/prod
terragrunt apply
# 4. 다른 서비스들 순차 배포
cd cloudrun-deploy/dta-wide-mcp/cloudrun/prod
terragrunt apply
2단계: 검증 및 모니터링
# mTLS 연결 상태 확인
curl -k https://dta-wide-api-prod.run.app/health/security/tls
# 인증서 상태 확인
gcloud privateca certificates list \
--pool=dta-wide-mtls-ca-pool \
--location=europe-west3
# 모니터링 대시보드 확인
echo "https://console.cloud.google.com/monitoring/dashboards"
3단계: 문제 해결
일반적인 문제
1. mTLS 인증서 검증 실패
# 인증서 상태 확인
openssl x509 -in /app/certs/client.crt -text -noout
# CA 인증서 확인
openssl verify -CAfile /app/certs/ca.crt /app/certs/client.crt
# 키 쌍 일치 확인
openssl rsa -in /app/certs/client.key -pubout | openssl dgst -sha256
openssl x509 -in /app/certs/client.crt -pubkey -noout | openssl dgst -sha256
2. 서비스 간 연결 실패
# 네트워크 연결 확인
nslookup dta-wide-api.internal.dta-wide.com
# mTLS 핸드셰이크 테스트
openssl s_client -connect dta-wide-api.internal.dta-wide.com:443 \
-cert /app/certs/client.crt \
-key /app/certs/client.key \
-CAfile /app/certs/ca.crt
🎯 성공 기준 및 KPI
보안 목표
| 메트릭 | 목표 | 측정 방법 |
|---|---|---|
| mTLS 적용률 | 100% | 내부 서비스 간 모든 통신 |
| 인증서 유효성 | 100% | 만료 30일 전 자동 갱신 |
| SSL/TLS 버전 | TLS 1.2+ | 모든 연결에서 최신 버전 사용 |
| 암호화 강도 | AES-256+ | 강력한 암호화 알고리즘 사용 |
모니터링 KPI
- mTLS 연결 성공률: > 99.9%
- 인증서 갱신 성공률: 100%
- SSL/TLS 핸드셰이크 지연: < 100ms
- 보안 정책 위반: 0건
🔄 개선 로드맵
단기 (1-2개월)
- ✅ 기본 mTLS 구현 - Service Connect 기반
- 🔄 인증서 자동화 - Certificate Manager 통합
- 🔄 모니터링 강화 - 보안 대시보드 구축
중기 (3-6개월)
- 📋 Zero Trust 아키텍처 - 모든 통신 mTLS 적용
- 📋 인증서 관리 고도화 - 자동 갱신 및 롤링 업데이트
- 📋 보안 감사 자동화 - 정기적 TLS 설정 검증
장기 (6-12개월)
- 📋 Anthos Service Mesh - 고급 서비스 메시 기능
- 📋 하드웨어 보안 모듈 - HSM 기반 키 관리
- 📋 국제 인증 준비 - SOC 2, ISO 27001 인증
✅ PLT-SEC-002 요구사항 달성
"모든 네트워크 통신은 TLS를 사용한다" ✅
구현 완료 예정 항목
- ✅ 외부 API 통신 - TLS 1.2+ 사용 (이미 구현)
- 🔄 내부 서비스 간 통신 - mTLS 구현 진행 중
- ✅ 데이터베이스 연결 - SSL/TLS 강화 (구현 중)
즉시 사용 가능한 검증 도구
# TLS 상태 종합 확인
curl https://dta-wide-api.run.app/health/security/tls
# 특정 서비스 mTLS 테스트
./scripts/validate-mtls-connection.sh dta-wide-api dta-wide-mcp
# 인증서 만료일 확인
./scripts/check-certificate-expiry.sh
📞 지원 및 문의
보안 문의: Platform 인프라 감사 보고서
관련 문서: PLT-SEC-001 어플리케이션 암호화
변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-08-13 | bok@weltcorp.com | 최초 작성 |