본문으로 건너뛰기

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-mcpdta-wide-apiHTTPSmTLS 강화
동기 API 호출dta-wide-agent-qadta-wide-apiHTTPSmTLS 강화
비동기 메시징모든 서비스Pub/SubTLS이미 안전
데이터베이스모든 서비스Cloud SQLSSL/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개월)

  1. 기본 mTLS 구현 - Service Connect 기반
  2. 🔄 인증서 자동화 - Certificate Manager 통합
  3. 🔄 모니터링 강화 - 보안 대시보드 구축

중기 (3-6개월)

  1. 📋 Zero Trust 아키텍처 - 모든 통신 mTLS 적용
  2. 📋 인증서 관리 고도화 - 자동 갱신 및 롤링 업데이트
  3. 📋 보안 감사 자동화 - 정기적 TLS 설정 검증

장기 (6-12개월)

  1. 📋 Anthos Service Mesh - 고급 서비스 메시 기능
  2. 📋 하드웨어 보안 모듈 - HSM 기반 키 관리
  3. 📋 국제 인증 준비 - SOC 2, ISO 27001 인증

✅ PLT-SEC-002 요구사항 달성

"모든 네트워크 통신은 TLS를 사용한다"

구현 완료 예정 항목

  1. 외부 API 통신 - TLS 1.2+ 사용 (이미 구현)
  2. 🔄 내부 서비스 간 통신 - mTLS 구현 진행 중
  3. 데이터베이스 연결 - 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.02025-08-13bok@weltcorp.com최초 작성