From 5db83e485d4eb4b43d7d98966ab91f446c47af5f Mon Sep 17 00:00:00 2001 From: Rinjae Date: Mon, 22 Jun 2026 15:18:07 +0900 Subject: [PATCH] =?UTF-8?q?-=20damo-manager=20CLI=20=EC=B6=94=EA=B0=80:=20?= =?UTF-8?q?DamoCli.java=20/=20Main-Class=20=EC=84=A0=EC=96=B8=20-=20Jenkin?= =?UTF-8?q?sfile=20=EB=8F=84=EC=9E=85:=20=EC=9E=90=EB=8F=99=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C/=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=20=EC=9E=91=EC=84=B1=20-=20bypass=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EB=8F=84=EC=9E=85:=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=ED=94=84=EB=A1=9C=ED=8D=BC=ED=8B=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=95=94=EB=B3=B5=ED=98=B8=ED=99=94=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 88 ++++++++ README.md | 136 ++++++++++-- build.gradle | 17 +- .../java/com/eactive/ext/djb/DamoCli.java | 199 ++++++++++++++++++ .../java/com/eactive/ext/djb/DamoManager.java | 124 +++++++++-- 5 files changed, 526 insertions(+), 38 deletions(-) create mode 100644 Jenkinsfile create mode 100644 src/main/java/com/eactive/ext/djb/DamoCli.java diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..e8878a5 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,88 @@ +pipeline { + agent { label 'djb-vm' } + + triggers { + pollSCM('H/10 * * * *') + } + + options { + timestamps() + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '20', artifactNumToKeepStr: '20')) + } + + environment { + JAVA_HOME = '/apps/opts/jdk8' + GRADLE_HOME = '/apps/opts/gradle-8.7' + GRADLE_USER_HOME = '/apps/opts/gradle-home' + PATH = "/apps/opts/jdk8/bin:/apps/opts/gradle-8.7/bin:/apps/opts/bin:${env.PATH}" + GIT_SSH_COMMAND = 'ssh -o StrictHostKeyChecking=accept-new' + } + + stages { + stage('Checkout') { + steps { + checkout scm + } + } + + stage('Verify toolchain') { + steps { + sh ''' + set -eu + java -version + gradle --version + ''' + } + } + + stage('Build JAR') { + steps { + sh 'gradle clean build --no-daemon' + } + } + + stage('Smoke test') { + steps { + sh ''' + set -eu + echo "---- default (bypass) mode ----" + java -cp build/libs/damo-manager.jar com.eactive.ext.djb.DamoManager + echo "---- fake mode (-Ddamo-manager.enabled=true) ----" + java -Ddamo-manager.enabled=true -cp build/libs/damo-manager.jar com.eactive.ext.djb.DamoManager + echo "---- require-real must fail without scpdb ----" + if java -Ddamo-manager.require-real=true -cp build/libs/damo-manager.jar com.eactive.ext.djb.DamoManager 2>/dev/null; then + echo "ERROR: require-real should have failed without scpdb"; exit 1 + else + echo "OK: require-real failed fast as expected" + fi + echo "---- CLI param mode ----" + java -jar build/libs/damo-manager.jar info + ''' + } + } + + stage('Package & checksum') { + steps { + sh ''' + set -eu + rm -rf build/dist + mkdir -p build/dist + cp build/libs/damo-manager.jar build/dist/ + cp README.md build/dist/ + cd build/dist + sha1sum damo-manager.jar README.md > SHA1SUMS + sha256sum damo-manager.jar README.md > SHA256SUMS + ls -la + echo "---- SHA256SUMS ----" + cat SHA256SUMS + ''' + } + post { + success { + archiveArtifacts artifacts: 'build/dist/damo-manager.jar,build/dist/README.md,build/dist/SHA1SUMS,build/dist/SHA256SUMS', fingerprint: true + } + } + } + } +} diff --git a/README.md b/README.md index 04f7d2a..07963de 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,90 @@ Penta scpdb 라이브러리 래퍼. **단일 jar (`damo-manager.jar`)** 로 운영/로컬 양쪽 환경에서 동작한다. -- **운영**: 클래스패스에 `com.penta.scpdb.ScpDbAgent` 가 존재 → 실제 `ScpEncB64` / `ScpDecB64` / `ScpHashB64` 호출 -- **로컬 개발**: scpdb 라이브러리 없음 → 자동으로 **fake 모드** 진입, 시작 시 `System.err` 에 강한 경고 배너 출력. 암복호화는 단순 Base64 round-trip, 해시는 JRE `MessageDigest` 로 대체 +## 동작 모드 + +클래스 로딩 시점에 시스템 프로퍼티로 한 번만 결정된다. **기본은 bypass(무변환)** 이며, 실제 암복호화는 명시적으로 켜야 한다. + +| 옵션 | 결정되는 모드 | encrypt/decrypt | hash | +|---|---|---|---| +| (없음, 기본) | **bypass** | **원문 그대로 (무변환)** | **원문 그대로 (무변환)** | +| `-Ddamo-manager.enabled=true` + scpdb 존재 | **real** | `ScpEncB64`/`ScpDecB64` | `ScpHashB64` | +| `-Ddamo-manager.enabled=true` + scpdb 부재 | **fake** | Base64 round-trip | JRE `MessageDigest` | +| `-Ddamo-manager.require-real=true` | **real** (강제) | `ScpEncB64`/`ScpDecB64` | `ScpHashB64` | + +- **bypass (기본값)**: 암복호화/해시를 **전혀 수행하지 않고 입력을 그대로 반환**. fake 와 달리 Base64 인코딩조차 하지 않는 완전 무변환. 시작 시 경고 배너 출력. 평문 그대로 저장/조회하거나 암복호화 계층을 통째로 끌 때 사용 +- **real (`-Ddamo-manager.enabled=true` + scpdb)**: 실제 `ScpEncB64`/`ScpDecB64`/`ScpHashB64` 호출 +- **fake (`-Ddamo-manager.enabled=true`, scpdb 없음)**: scpdb 부재 시 자동 진입. 시작 시 경고 배너. 암복호화는 단순 Base64, 해시는 JRE `MessageDigest` +- **require-real (`-Ddamo-manager.require-real=true`)**: **운영 보호 플래그.** real 모드로만 동작해야 하며, scpdb 가 없어 fake/bypass 로 떨어질 상황이면 **조용히 폴백하지 않고 클래스 로딩 시점에 즉시 실패**(`IllegalStateException` → `ExceptionInInitializerError`)한다. `enabled` 를 함의하므로 운영 서버에서는 이 플래그 하나만 켜면 "real 아니면 기동 거부"가 된다 + +> bypass / fake 모두 **운영 절대 금지**. 운영 서버에는 **`-Ddamo-manager.require-real=true`** 를 주어 fake/bypass 동작을 원천 차단할 것. ## 빌드 ```bash cd damo-manager -gradle clean build # 또는 ./gradlew clean build (wrapper 추가 시) +gradle clean build # wrapper 미사용. gradle 단독 실행 ls build/libs/ # damo-manager.jar ``` JDK 8 toolchain 을 요구한다 (`build.gradle` 의 `JavaLanguageVersion.of(8)`). `JAVA_HOME` 이 JDK 8 이거나 Gradle 이 시스템에서 JDK 8 을 auto-detect 할 수 있어야 한다. -Fake 모드 스모크 검증: +산출 jar 는 라이브러리이자 **실행 가능한 CLI** 다 (manifest `Main-Class: com.eactive.ext.djb.DamoCli`). CLI 사용법은 아래 [CLI](#cli-java--jar) 참고. + +스모크 검증: ```bash -gradle runFakeSmoke +gradle runBypassSmoke # bypass 모드 (기본값, 옵션 없음) +gradle runFakeSmoke # fake 모드 (-Ddamo-manager.enabled=true, scpdb 부재) ``` +### Jenkins 빌드 + +저장소 루트에 `Jenkinsfile` 이 있다. `djb-vm` 에이전트에서 `pollSCM` 으로 트리거되며 다음을 수행한다. + +1. **Build JAR** — `gradle clean build --no-daemon` +2. **Smoke test** — fake / bypass 모드 main + CLI `info` 실행 +3. **Package & checksum** — `build/dist/` 에 `damo-manager.jar` + `README.md` 복사 후 `SHA1SUMS` / `SHA256SUMS` 생성 +4. **archiveArtifacts** — `damo-manager.jar`, `README.md`, `SHA1SUMS`, `SHA256SUMS` 보관 (fingerprint) + +빌드 환경(JDK8/Gradle 경로)은 다른 djb-eapim Jenkinsfile 과 동일한 `/apps/opts/*` 경로를 사용한다. + +## CLI (java -jar) + +`damo-manager.jar` 를 `java -jar` 로 직접 실행하면 encrypt/decrypt/hash 를 즉석에서 테스트할 수 있다. **대화식(REPL)** 과 **파라미터식** 둘 다 지원하며, 파라미터식은 text 인자를 생략하면 **stdin → stdout** 파이프로 동작한다. + +```bash +# 대화식(REPL): 명령을 줄 단위로 입력 +java -jar damo-manager.jar +damo> enc hello +aGVsbG8= +damo> info +mode=FAKE revision=20260604 returnCode=0 +damo> quit + +# 파라미터식: 1회 실행 +java -jar damo-manager.jar enc "hello, 다모" +java -jar damo-manager.jar dec aGVsbG8sIOuLpOuqqA== +java -jar damo-manager.jar sha256 password + +# stdin/stdout 파이프 (text 인자 생략 시 stdin 읽음, 후행 개행 1개 제거) +echo -n 'hello, 다모' | java -jar damo-manager.jar enc +echo -n 'hello, 다모' | java -jar damo-manager.jar enc | java -jar damo-manager.jar dec + +# 기본은 bypass: 모든 동작이 원문 그대로 통과 +java -jar damo-manager.jar enc "hello, 다모" # -> hello, 다모 + +# real/fake 활성화 +java -Ddamo-manager.enabled=true -jar damo-manager.jar enc "hello" # scpdb 있으면 real, 없으면 fake(Base64) + +# 운영: real 강제 (scpdb 없으면 기동 실패) +java -Ddamo-manager.require-real=true -jar damo-manager.jar info +``` + +명령: `enc|encrypt`, `dec|decrypt`, `sha256|hash256`, `sha512|hash512`, `info|mode`, `help|?`, (대화식) `quit|exit`. 알 수 없는 명령은 stderr 안내 후 exit code 2. + +> 라이브러리로 쓸 때는 manifest 의 `Main-Class` 가 영향을 주지 않는다 (CLI 는 부가 기능). + ## 소스 코드 커스터마이즈 `src/main/java/com/eactive/ext/djb/DamoManager.java` 상단 상수 영역을 수정한다. @@ -89,6 +154,7 @@ $CATALINA_HOME/lib/ ``` `libs_scpdb-*.jar` 와 함께 네이티브 .so/.dll 및 `/prod/damo/ba_scp/scpdb_agent.ini` 도 준비되어 있어야 한다. +**운영 JVM 옵션에 반드시 `-Ddamo-manager.require-real=true` 를 준다** — scpdb 가 빠져 fake/bypass 로 잘못 동작하면 기동 자체가 실패하여 사고를 막는다. (setenv.sh 의 `CATALINA_OPTS`/`JAVA_OPTS` 등) **로컬 개발**: @@ -97,20 +163,31 @@ $CATALINA_HOME/lib/ └── damo-manager.jar ← 단독. scpdb 라이브러리 없음 ``` -WAS 기동 시 콘솔(`catalina.out` 또는 stderr)에 다음과 같은 배너가 출력되면 정상 동작이다. +- 옵션 없이 기동 → **bypass**(무변환). 화면/DB 에 원문이 그대로 흐른다. +- `-Ddamo-manager.enabled=true` 기동 → scpdb 가 없으므로 **fake**(Base64/JRE 해시). + +WAS 기동 시 콘솔(`catalina.out` 또는 stderr)에 다음 배너 중 하나가 출력된다. ``` +# 기본 (옵션 없음) +============================================================ + *** DAMO-MANAGER BYPASS MODE (default) *** + 암복호화/해시를 전혀 수행하지 않고 원문을 그대로 사용합니다. + ... + 실제 암복호화(real/fake) 가 필요하면: -Ddamo-manager.enabled=true +============================================================ + +# -Ddamo-manager.enabled=true 인데 scpdb 가 없을 때 ============================================================ *** DAMO-MANAGER FAKE MODE *** com.penta.scpdb.ScpDbAgent NOT FOUND on classpath. - Fallback: - encrypt/decrypt -> Base64 (NO ENCRYPTION) - hash -> JRE MessageDigest (SHA-256 / SHA-512) - *** DO NOT use this build for production! *** + ... ============================================================ ``` -> Fake 모드는 **암호화가 전혀 없는 단순 Base64 통과** 이므로 운영 절대 금지. +real 모드(운영 정상)에서는 배너가 출력되지 않는다. + +> bypass / fake 모두 암호화가 없으므로 **운영 절대 금지**. 운영은 `-Ddamo-manager.require-real=true` 로 real 을 강제한다. ## 주요 메서드 @@ -128,18 +205,19 @@ String plain = damo2.decrypt(cipher); // -> "hello" String h256 = damo2.hash(DamoManager.SHA256, "password"); // SHA-256 base64 String h512 = damo2.hash(DamoManager.SHA512, "password"); // SHA-512 base64 -boolean fake = damo2.isFakeMode(); // 디버깅 / 분기용 -int rc = damo2.getReturnCode(); // 0: 성공, -1: 실패 +boolean fake = damo2.isFakeMode(); // 디버깅 / 분기용 (bypass 면 false) +boolean bypass = damo2.isBypassMode(); // 기본(옵션 미설정) 이면 true +int rc = damo2.getReturnCode(); // 0: 성공, -1: 실패 ``` `new DamoManager()` 와 `DamoManager.getInstance()` 를 함께 지원하므로, 기존 호출 코드를 변경하지 않고도 singleton 접근 방식으로 점진 전환할 수 있다. -| 메서드 | 운영 동작 | Fake 동작 | -|---|---|---| -| `String encrypt(String plain)` | `ScpEncB64(INI_FILE, "KEY1", plain, "UTF-8")` | `Base64.encode(plain.getBytes("UTF-8"))` | -| `String decrypt(String b64)` | `ScpDecB64(INI_FILE, "KEY1", b64, "UTF-8")` | `new String(Base64.decode(b64), "UTF-8")` | -| `String hash(int type, String src)` | `ScpHashB64(type, src, "UTF-8")` | `MessageDigest(SHA-256/512).digest(src) → Base64` | +| 메서드 | 운영(real) | Fake | Bypass | +|---|---|---|---| +| `String encrypt(String plain)` | `ScpEncB64(INI_FILE, "KEY1", plain, "UTF-8")` | `Base64.encode(plain.getBytes("UTF-8"))` | `plain` (원문) | +| `String decrypt(String b64)` | `ScpDecB64(INI_FILE, "KEY1", b64, "UTF-8")` | `new String(Base64.decode(b64), "UTF-8")` | 입력 그대로 | +| `String hash(int type, String src)` | `ScpHashB64(type, src, "UTF-8")` | `MessageDigest(SHA-256/512).digest(src) → Base64` | `src` (원문) | 해시 `type` 상수: @@ -155,22 +233,27 @@ int rc = damo2.getReturnCode(); // 0: 성공, -1: - `checkHangul(char)` - `getInstance()` (singleton 인스턴스 접근) - `getReturnCode()`, `getRevision()` +- `isFakeMode()`, `isBypassMode()` (현재 모드 판별) ## 검증 스모크 main 이 포함되어 있다. ```bash +# 기본(bypass) java -cp build/libs/damo-manager.jar com.eactive.ext.djb.DamoManager +# fake (real/fake 활성화, scpdb 부재) +java -Ddamo-manager.enabled=true -cp build/libs/damo-manager.jar com.eactive.ext.djb.DamoManager ``` 또는: ```bash -gradle runFakeSmoke +gradle runBypassSmoke # 기본 bypass +gradle runFakeSmoke # -Ddamo-manager.enabled=true (fake) ``` -예상 출력: +예상 출력 (fake 모드, `-Ddamo-manager.enabled=true`): ``` ============================================================ @@ -178,11 +261,18 @@ gradle runFakeSmoke ... ============================================================ -revision = 20160503 -fakeMode = true -encrypt = aGVsbG8sIL3M3sCw +revision = 20260604 +bypassMode = false +fakeMode = true +encrypt = aGVsbG8sIOuLpOuqqA== decrypt = hello, 다모 roundtrip = true sha256 b64 = ... (len=44) sha512 b64 = ... (len=88) ``` + +기본(bypass) 모드(`gradle runBypassSmoke`, 옵션 없음)에서는 `encrypt`/`decrypt`/`hash` 가 모두 입력(`hello, 다모`)을 그대로 반환하고 `bypassMode = true`, `fakeMode = false` 로 출력된다. `-Ddamo-manager.require-real=true` 인데 scpdb 가 없으면 `ExceptionInInitializerError` 로 기동에 실패한다(운영 보호). + +### 웹 테스트 페이지 (eapim-admin) + +eapim-admin 에는 브라우저에서 encrypt/decrypt/hash 를 확인하는 진단 페이지 `WebContent/damo.jsp` 가 있다 (`/monitoring/damo.jsp`, 로그인 필요). damo-manager.jar 를 WAS lib 에 배포하면 현재 모드(REAL/FAKE/BYPASS)와 동작을 즉시 확인할 수 있고, jar 미배포 시에도 안내만 표시하고 깨지지 않는다. diff --git a/build.gradle b/build.gradle index 881e657..50e9e6f 100644 --- a/build.gradle +++ b/build.gradle @@ -35,15 +35,26 @@ jar { attributes( 'Implementation-Title' : 'damo-manager', 'Implementation-Version': project.version, - 'Built-By' : 'damo-manager gradle build' + 'Built-By' : 'damo-manager gradle build', + 'Main-Class' : 'com.eactive.ext.djb.DamoCli' ) } } -// Fake 모드 스모크 검증용. 실행: ./gradlew runFakeSmoke +// Fake 모드 스모크 검증용 (-Ddamo-manager.enabled=true + scpdb 부재). 실행: gradle runFakeSmoke tasks.register('runFakeSmoke', JavaExec) { group = 'verification' - description = 'Run DamoManager main() with no scpdb on classpath (fake mode smoke test)' + description = 'Run DamoManager main() with -Ddamo-manager.enabled=true and no scpdb (fake mode smoke test)' + classpath = sourceSets.main.runtimeClasspath + mainClass = 'com.eactive.ext.djb.DamoManager' + systemProperty 'damo-manager.enabled', 'true' + dependsOn 'classes' +} + +// Bypass 모드 스모크 검증용 (기본값, 옵션 없음). 실행: gradle runBypassSmoke +tasks.register('runBypassSmoke', JavaExec) { + group = 'verification' + description = 'Run DamoManager main() with no option (default bypass mode smoke test)' classpath = sourceSets.main.runtimeClasspath mainClass = 'com.eactive.ext.djb.DamoManager' dependsOn 'classes' diff --git a/src/main/java/com/eactive/ext/djb/DamoCli.java b/src/main/java/com/eactive/ext/djb/DamoCli.java new file mode 100644 index 0000000..9460b1d --- /dev/null +++ b/src/main/java/com/eactive/ext/djb/DamoCli.java @@ -0,0 +1,199 @@ +package com.eactive.ext.djb; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.nio.charset.Charset; + +/** + * damo-manager.jar 의 CLI 진입점 (manifest Main-Class). + * encrypt/decrypt/hash 동작을 운영/로컬에서 빠르게 확인하는 테스트 도구. + * + * 실행 방식: + * java -jar damo-manager.jar # 대화식(REPL) + * java -jar damo-manager.jar <cmd> [text] # 파라미터식 1회 실행 + * echo "<text>" | java -jar damo-manager.jar <cmd> # stdin 입력 → stdout 출력 + * + * 명령: + * enc|encrypt <text> 암호화 (real=ScpEncB64, fake=Base64, bypass=원문) + * dec|decrypt <text> 복호화 + * sha256|hash256 <text> SHA-256 해시 (bypass=원문) + * sha512|hash512 <text> SHA-512 해시 (bypass=원문) + * info|mode 현재 모드/리비전/리턴코드 + * help|? 도움말 + * quit|exit (대화식) 종료 + * + * 기본은 bypass(무변환). 실제 암복호화(real/fake) 를 쓰려면 -Ddamo-manager.enabled=true + * + * stdin/stdout: 파라미터식에서 text 인자를 생략하면 stdin 전체를 읽어 입력으로 사용하고 + * 결과를 stdout 으로 출력하므로 파이프 연동이 가능하다 (후행 개행 1개는 제거). + */ +public final class DamoCli { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private DamoCli() { + } + + public static void main(String[] args) throws IOException { + PrintStream out = new PrintStream(System.out, true, "UTF-8"); + PrintStream err = new PrintStream(System.err, true, "UTF-8"); + DamoManager damo = new DamoManager(); + + if (args.length == 0) { + repl(damo, out, err); + return; + } + + String cmd = args[0]; + String lower = cmd.toLowerCase(); + + if (lower.equals("info") || lower.equals("mode")) { + out.println(modeLine(damo)); + return; + } + if (lower.equals("help") || lower.equals("?") || lower.equals("-h") || lower.equals("--help")) { + out.println(usage()); + return; + } + + String text; + if (args.length >= 2) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < args.length; i++) { + if (i > 1) { + sb.append(' '); + } + sb.append(args[i]); + } + text = sb.toString(); + } else { + if (System.console() != null) { + err.println("(stdin 입력 대기중... 입력 후 Ctrl-D)"); + } + text = readAllStdin(); + } + + String result = run(damo, cmd, text, err); + if (result == null) { + System.exit(2); + } + out.println(result); + } + + private static void repl(DamoManager damo, PrintStream out, PrintStream err) throws IOException { + boolean tty = System.console() != null; + if (tty) { + out.println("damo-manager CLI (" + modeLine(damo) + ")"); + out.println("명령: enc | dec | sha256 | sha512 | info | help | quit"); + } + BufferedReader in = new BufferedReader(new InputStreamReader(System.in, UTF8)); + String line; + while (true) { + if (tty) { + out.print("damo> "); + out.flush(); + } + line = in.readLine(); + if (line == null) { + break; // EOF + } + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + int sp = line.indexOf(' '); + String cmd = sp < 0 ? line : line.substring(0, sp); + String text = sp < 0 ? "" : line.substring(sp + 1); + String lower = cmd.toLowerCase(); + + if (lower.equals("quit") || lower.equals("exit") || lower.equals("q")) { + break; + } + if (lower.equals("info") || lower.equals("mode")) { + out.println(modeLine(damo)); + continue; + } + if (lower.equals("help") || lower.equals("?")) { + out.println(usage()); + continue; + } + + String result = run(damo, cmd, text, err); + if (result != null) { + out.println(result); + } + } + } + + /** 명령 실행. 알 수 없는 명령이면 stderr 에 안내 후 null 반환. */ + private static String run(DamoManager damo, String cmd, String text, PrintStream err) { + String lower = cmd.toLowerCase(); + if (lower.equals("enc") || lower.equals("encrypt")) { + return damo.encrypt(text); + } + if (lower.equals("dec") || lower.equals("decrypt")) { + return damo.decrypt(text); + } + if (lower.equals("sha256") || lower.equals("hash256")) { + return damo.hash(DamoManager.SHA256, text); + } + if (lower.equals("sha512") || lower.equals("hash512")) { + return damo.hash(DamoManager.SHA512, text); + } + err.println("unknown command: " + cmd); + err.println(usage()); + return null; + } + + private static String modeLine(DamoManager damo) { + String mode = damo.isBypassMode() ? "BYPASS" : (damo.isFakeMode() ? "FAKE" : "REAL"); + return "mode=" + mode + + " revision=" + damo.getRevision() + + " returnCode=" + damo.getReturnCode(); + } + + private static String usage() { + return String.join("\n", + "Usage:", + " java -jar damo-manager.jar # 대화식(REPL)", + " java -jar damo-manager.jar [text] # 파라미터식 1회 실행", + " echo \"\" | java -jar damo-manager.jar # stdin 입력", + "", + "Commands:", + " enc|encrypt 암호화 (real=ScpEncB64, fake=Base64, bypass=원문)", + " dec|decrypt 복호화", + " sha256|hash256 SHA-256 해시 (bypass=원문)", + " sha512|hash512 SHA-512 해시 (bypass=원문)", + " info|mode 현재 모드/리비전/리턴코드", + " help|? 도움말", + " quit|exit 대화식 종료", + "", + "모드 옵션 (기본은 bypass 무변환):", + " -Ddamo-manager.enabled=true real/fake 동작 활성화 (scpdb 있으면 real, 없으면 fake)", + " -Ddamo-manager.require-real=true 운영: real 강제. real 불가 시 기동 실패(fake/bypass 폴백 금지)"); + } + + private static String readAllStdin() throws IOException { + InputStream in = System.in; + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte[] buf = new byte[4096]; + int n; + while ((n = in.read(buf)) != -1) { + bos.write(buf, 0, n); + } + String s = new String(bos.toByteArray(), UTF8); + // echo 등으로 들어온 후행 개행 1개 제거 + if (s.endsWith("\r\n")) { + return s.substring(0, s.length() - 2); + } + if (s.endsWith("\n")) { + return s.substring(0, s.length() - 1); + } + return s; + } +} diff --git a/src/main/java/com/eactive/ext/djb/DamoManager.java b/src/main/java/com/eactive/ext/djb/DamoManager.java index b968b33..4f2426f 100644 --- a/src/main/java/com/eactive/ext/djb/DamoManager.java +++ b/src/main/java/com/eactive/ext/djb/DamoManager.java @@ -9,10 +9,29 @@ import java.util.Base64; /** * 운영(penta scpdb 라이브러리 존재) / 로컬(scpdb 부재) 양쪽에서 동작하는 단일 클래스. * - * 클래스패스에 {@code com.penta.scpdb.ScpDbAgent} 가 존재하면 실 호출, 아니면 fake 모드로 자동 fallback. - * fake 모드 진입 시 클래스 로딩 시점에 System.err 로 강한 경고 배너 출력. + * 동작 모드는 다음 규칙으로 한 번만 결정된다 (클래스 로딩 시점). + * - 기본값(시스템 프로퍼티 미설정) : bypass. 암복호화/해시를 전혀 하지 않고 원문 그대로. + * - {@code -Ddamo-manager.enabled=true} 설정 시 : 실제 동작(real/fake). + * · real : 클래스패스에 {@code com.penta.scpdb.ScpDbAgent} 가 존재. + * · fake : scpdb 부재 시 자동 fallback. + * - {@code -Ddamo-manager.require-real=true} (운영 보호) : real 모드로만 동작. + * enabled 를 함의하며, real 이 불가능하면 fake/bypass 로 폴백하지 않고 기동 시점에 즉시 실패. + * bypass / fake 진입 시 클래스 로딩 시점에 System.err 로 강한 경고 배너 출력. * - * fake 모드 동작: + * 즉 아무 옵션 없이 기동하면 안전하게 무변환(bypass)으로 동작하고, + * 실제 암복호화를 쓰려면 {@code -Ddamo-manager.enabled=true} 를 명시해야 한다. + * 운영 서버에서 fake/bypass 로 잘못 동작하는 것을 막으려면 {@code -Ddamo-manager.require-real=true} 를 준다. + * + * bypass 모드 동작 (기본값, 암복호화/해시 전면 통과): + * - encrypt(s) : s -- 인코딩조차 하지 않고 원문 그대로 + * - decrypt(s) : s -- 디코딩조차 하지 않고 원문 그대로 + * - hash(t, s) : s -- 해시하지 않고 원문 그대로 + * bypass 는 fake 와 다르다. fake 는 Base64/해시 변환을 하지만, bypass 는 아무 변환도 하지 않는다. + * + * real 모드 동작 (-Ddamo-manager.enabled=true + scpdb 존재): + * - encrypt/decrypt/hash : 실제 ScpEncB64 / ScpDecB64 / ScpHashB64 호출 + * + * fake 모드 동작 (-Ddamo-manager.enabled=true + scpdb 부재, 로컬 개발용): * - encrypt(s) : Base64.encode(s.getBytes(UTF-8)) -- 암호화 없음 * - decrypt(s) : new String(Base64.decode(s), UTF-8) * - hash(t, s) : SHA-256(71) / SHA-512(73) JRE MessageDigest -> Base64 @@ -29,19 +48,82 @@ public class DamoManager { private static final int SCP_OK = 0; private static final int SCP_FAIL = -1; + /** + * 실제 동작(real/fake) 활성화 플래그. 이 시스템 프로퍼티가 {@code true} 일 때만 암복호화/해시를 수행한다. + * 미설정(기본) 이면 bypass(무변환). + * 예: {@code -Ddamo-manager.enabled=true} + */ + public static final String ENABLE_PROPERTY = "damo-manager.enabled"; + + /** + * 운영 보호 플래그. {@code true} 이면 real 모드(penta scpdb 실 암복호화)로만 동작해야 하며, + * scpdb 가 없어 fake/bypass 로 떨어질 상황이면 조용히 폴백하지 않고 클래스 로딩 시점에 즉시 실패한다. + * 이 플래그는 {@link #ENABLE_PROPERTY} 를 함의하므로, 운영 서버에서는 이 플래그 하나만 켜면 + * "real 아니면 기동 거부" 가 된다. + * 예: {@code -Ddamo-manager.require-real=true} + */ + public static final String REQUIRE_REAL_PROPERTY = "damo-manager.require-real"; + + /** 기본값(ENABLE_PROPERTY 미설정) 이면 bypass: 암복호화/해시를 전혀 하지 않고 원문을 그대로 사용. */ + private static final boolean BYPASS_MODE; private static final boolean REAL_MODE; static { + boolean requireReal = "true".equalsIgnoreCase(System.getProperty(REQUIRE_REAL_PROPERTY)); + // require-real 은 enable 을 함의한다 (운영에서 real 강제). + boolean enabled = requireReal + || "true".equalsIgnoreCase(System.getProperty(ENABLE_PROPERTY)); + + boolean bypass; boolean real; - try { - Class.forName("com.penta.scpdb.ScpDbAgent", false, - DamoManager.class.getClassLoader()); - real = true; - } catch (Throwable t) { + if (!enabled) { + // 기본: 아무 옵션 없으면 무변환(bypass) + bypass = true; real = false; + } else { + bypass = false; + try { + Class.forName("com.penta.scpdb.ScpDbAgent", false, + DamoManager.class.getClassLoader()); + real = true; + } catch (Throwable t) { + real = false; + } + } + + if (requireReal && !real) { + // 운영 보호: real 이 아니면(=fake/bypass) 조용히 동작하지 않고 기동 자체를 실패시킨다. + throw new IllegalStateException( + "[damo-manager] " + REQUIRE_REAL_PROPERTY + "=true 이지만 real 모드를 사용할 수 없습니다. " + + "com.penta.scpdb.ScpDbAgent 가 클래스패스에 없어 fake/bypass 로 동작하게 되므로 기동을 중단합니다. " + + "운영에서는 scpdb 라이브러리/네이티브/ini(" + INI_FILE + ") 를 갖춰 real 모드로만 동작해야 합니다."); + } + + BYPASS_MODE = bypass; + REAL_MODE = real; + + if (bypass) { + printBypassBanner(); + } else if (!real) { printFakeBanner(); } - REAL_MODE = real; + } + + private static void printBypassBanner() { + String line = "============================================================"; + System.err.println(); + System.err.println(line); + System.err.println(" *** DAMO-MANAGER BYPASS MODE (default) ***"); + System.err.println(" 암복호화/해시를 전혀 수행하지 않고 원문을 그대로 사용합니다."); + System.err.println(" Pass-through (NO transform at all):"); + System.err.println(" encrypt(s) -> s (NO ENCODING)"); + System.err.println(" decrypt(s) -> s (NO DECODING)"); + System.err.println(" hash(t,s) -> s (NO HASHING)"); + System.err.println(" 실제 암복호화(real/fake) 가 필요하면: -D" + ENABLE_PROPERTY + "=true"); + System.err.println(" *** DO NOT use this build for production! ***"); + System.err.println(line); + System.err.println(); + System.err.flush(); } private static void printFakeBanner() { @@ -98,7 +180,12 @@ public class DamoManager { } public boolean isFakeMode() { - return !REAL_MODE; + return !REAL_MODE && !BYPASS_MODE; + } + + /** 기본(ENABLE_PROPERTY 미설정) 으로 기동되어 암복호화/해시를 전혀 하지 않는 통과 모드인지 여부. */ + public boolean isBypassMode() { + return BYPASS_MODE; } // ----------------------------------------------------------------- @@ -114,6 +201,10 @@ public class DamoManager { this.nReturn = SCP_FAIL; return ""; } + if (BYPASS_MODE) { + this.nReturn = SCP_OK; + return plain; // 원문 그대로. 인코딩조차 하지 않음. + } if (REAL_MODE) { try { String out = this.bridge.encB64(plain); @@ -143,6 +234,10 @@ public class DamoManager { this.nReturn = SCP_FAIL; return ""; } + if (BYPASS_MODE) { + this.nReturn = SCP_OK; + return base64Cipher; // 원문 그대로. 디코딩조차 하지 않음. + } if (REAL_MODE) { try { String out = this.bridge.decB64(base64Cipher); @@ -177,6 +272,10 @@ public class DamoManager { this.nReturn = SCP_FAIL; return ""; } + if (BYPASS_MODE) { + this.nReturn = SCP_OK; + return src; // 원문 그대로. 해시하지 않음. + } if (REAL_MODE) { try { String out = this.bridge.hashB64(type, src); @@ -356,8 +455,9 @@ public class DamoManager { // ----------------------------------------------------------------- public static void main(String[] args) { DamoManager m = new DamoManager(); - System.out.println("revision = " + m.getRevision()); - System.out.println("fakeMode = " + m.isFakeMode()); + System.out.println("revision = " + m.getRevision()); + System.out.println("bypassMode = " + m.isBypassMode()); + System.out.println("fakeMode = " + m.isFakeMode()); String plain = "hello, 다모"; String enc = m.encrypt(plain);