견적관리 결재상신 — 외부 커넥션 'Amaranth - 결재' auth_config 자동 복호화
Build and Push Images / build-and-push (push) Has been cancelled
Build and Push Images / build-and-push (push) Has been cancelled
증상: - 견적관리에서 결재상신 클릭 시 SSO URL 발급 실패. external_rest_api_connections 테이블의 accessToken/hashKey 가 AES-256-GCM 으로 암호화되어 저장되는데 amaranthApprovalClient.loadApprovalConnection() 가 평문 그대로 사용함. 수정: - ExternalRestApiConnectionService.encryptSensitiveData/decryptSensitiveData 와 동일한 알고리즘(aes-256-gcm, scrypt(DB_PASSWORD_SECRET,'salt',32)) 으로 복호화 하는 tryDecrypt 헬퍼를 amaranthApprovalClient 내부에 추가. - loadApprovalConnection 에서 accessToken/hashKey 만 자동 복호화 (callerName/groupSeq 는 평문 저장이므로 그대로). - 'iv:authTag:cipher' 3-part 형식이 아니면 평문으로 간주(마이그레이션 호환). 검증: - 복호화 후 accessToken='MN5KzKBWRAa92BPxDlRLl3GcsxeZXc' / hashKey='2251910...' 로 wace_plm AmaranthApprovalApiClient.java 의 하드코딩 값과 일치 확인.
This commit is contained in:
@@ -22,6 +22,33 @@ import { logger } from "../utils/logger";
|
||||
|
||||
const CONNECTION_NAME = "Amaranth - 결재";
|
||||
|
||||
// external_rest_api_connections.auth_config 의 accessToken/hashKey 는
|
||||
// ExternalRestApiConnectionService 에서 AES-256-GCM 으로 암호화되어 저장됨
|
||||
// (`iv:authTag:ciphertext` 3-part hex). 같은 키/알고리즘으로 복호화한다.
|
||||
const ENC_ALGORITHM = "aes-256-gcm";
|
||||
const ENC_KEY = crypto.scryptSync(
|
||||
process.env.DB_PASSWORD_SECRET || "default-secret-key-change-in-production",
|
||||
"salt",
|
||||
32,
|
||||
);
|
||||
|
||||
function tryDecrypt(value: string): string {
|
||||
if (!value || typeof value !== "string") return value;
|
||||
const parts = value.split(":");
|
||||
if (parts.length !== 3) return value; // 평문 그대로 (마이그레이션 호환)
|
||||
try {
|
||||
const iv = Buffer.from(parts[0], "hex");
|
||||
const authTag = Buffer.from(parts[1], "hex");
|
||||
const decipher = crypto.createDecipheriv(ENC_ALGORITHM, ENC_KEY, iv) as crypto.DecipherGCM;
|
||||
decipher.setAuthTag(authTag);
|
||||
let out = decipher.update(parts[2], "hex", "utf8");
|
||||
out += decipher.final("utf8");
|
||||
return out;
|
||||
} catch {
|
||||
return value; // 복호화 실패 시 원본 (평문 가정)
|
||||
}
|
||||
}
|
||||
|
||||
interface AmaranthAuth {
|
||||
baseUrl: string;
|
||||
callerName: string;
|
||||
@@ -31,7 +58,7 @@ interface AmaranthAuth {
|
||||
aesKey: string;
|
||||
}
|
||||
|
||||
/** DB 의 외부 커넥션 'Amaranth - 결재' 에서 인증 정보를 로드 */
|
||||
/** DB 의 외부 커넥션 'Amaranth - 결재' 에서 인증 정보를 로드 (민감 필드 자동 복호화) */
|
||||
export async function loadApprovalConnection(): Promise<AmaranthAuth> {
|
||||
const row = await queryOne<any>(
|
||||
`SELECT base_url, auth_config FROM external_rest_api_connections
|
||||
@@ -48,10 +75,10 @@ export async function loadApprovalConnection(): Promise<AmaranthAuth> {
|
||||
}
|
||||
return {
|
||||
baseUrl: String(row.base_url || "").replace(/\/+$/, ""),
|
||||
callerName: cfg.callerName,
|
||||
accessToken: cfg.accessToken,
|
||||
hashKey: cfg.hashKey,
|
||||
groupSeq: cfg.groupSeq,
|
||||
callerName: cfg.callerName, // 평문 (암호화 대상 아님)
|
||||
accessToken: tryDecrypt(cfg.accessToken),
|
||||
hashKey: tryDecrypt(cfg.hashKey),
|
||||
groupSeq: cfg.groupSeq, // 평문
|
||||
aesKey: cfg.aesKey || "8441e27489d402cd",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user