Devin.KR

코드와 비밀 설정 분리

65분 안팎

학습 목표

실습용 비밀을 코드와 별도 설정으로 전달합니다.

개념

왜 설정을 코드에서 분리하나요

개인 자료 보관 앱의 로그인 상태를 보호하려고 키를 소스 파일에 적으면, 같은 파일을 읽을 수 있는 사람에게 키 사용 권한까지 줍니다. 저장소 복제본·코드 리뷰 화면·백업·배포 압축 파일로 사본이 늘어납니다. 화면에서 줄을 지워도 이미 복사된 값은 남을 수 있습니다. 이번 목표는 문자열을 숨기는 요령이 아니라, 코드 전달과 비밀 전달의 경로를 분리하고 잘못된 전달을 시작 시점에 발견하는 것입니다.

이번 모듈은 Node.js 표준 모듈과 Python 3만 사용하며 외부 패키지를 설치하지 않습니다. 로컬 과제는 starter ZIP의 루트에서 bash check.sh로 실행합니다. AssertionError는 기대값과 실제값이 다른 위치를 알려 줍니다. 테스트는 그대로 두고 TODO를 고친 뒤 solution에도 같은 명령을 실행합니다. 브라우저 과제는 표준 입력 JSON 하나와 출력 한 줄로 채점합니다. 모든 키와 계정은 합성 fixture이며 본인 소유 환경만 점검합니다.

무엇이 비밀이고 무엇이 설정인가요

DB 파일 위치·로그 수준은 배포 설정이며, 서명 키와 접근 토큰은 소유자가 제한된 비밀입니다. 설정을 외부에서 받는다고 모든 설정이 비밀이 되는 것은 아닙니다. 반대로 개발용이라고 실제 계정의 토큰을 교재에 적어도 되는 것은 아닙니다. 이 앱에서는 VAULT_KEY 하나를 받습니다. 32바이트 값을 소문자 16진수 64자로 표현하는 계약이며 실제 키 생성은 무작위 바이트로 합니다. 반복 문자로 만든 값은 검사 fixture로만 사용합니다.

길이와 문자 집합 검사는 전달 오류를 발견하지만 키의 무작위성이나 안전한 보관을 입증하지 않습니다. 64개의 같은 문자가 형식 검사를 통과한다고 좋은 키가 되는 것은 아닙니다. 키 생성 책임과 입력 형식 검증 책임을 구분합니다. 환경변수도 암호화 금고가 아닙니다. 프로세스가 읽을 수 있으며 실행 도구의 환경 덤프나 오류 수집에 섞일 수 있어, 접근 권한과 출력 통제는 여전히 필요합니다.

어떻게 시작 전 검사를 만드나요

config.cjs의 loadConfig는 전달받은 객체의 VAULT_KEY만 읽습니다. 유형이 문자열인지 확인하고 정규식으로 전체 형식을 검사한 뒤 키를 담은 설정 객체를 반환합니다. 빈 문자열·공백·누락·63자·65자·대문자 또는 16진수 밖 문자는 거절합니다. 잘못된 값을 자동으로 trim하거나 소문자로 고치지 않습니다. 예상한 전달 형식을 요구해야 설정 생성기의 오류를 조기에 찾을 수 있습니다.

오류는 VAULT_KEY라는 항목 이름과 기대 형식만 알려 줍니다. 입력값을 오류 문장에 붙이면 검증기가 비밀을 로그로 옮기는 통로가 됩니다. 예외의 message에 실제 입력이 없는지 테스트합니다. 파일 경로·호스트 이름처럼 공개 가능한 설정도 사용자 식별 정보를 포함할 수 있으므로, 설정 전체를 펼쳐 찍는 방식은 피합니다. 기동 관찰에는 키 설정 여부와 검증 성공 같은 최소한의 정보면 충분합니다.

미션의 server.cjs는 createServer 안에서 loadConfig를 먼저 호출합니다. 설정이 유효해야 createSecureApp과 HTTP 서버 객체를 만듭니다. 요청이 처음 들어온 뒤 검사하면 이미 준비 상태로 보이는 서비스가 뒤늦게 실패할 수 있습니다. 잘못된 설정을 기본 공개 키로 대체하는 것도 위험합니다. 이번 계약은 설정이 없는 경우 기동을 거절하며, 정상 환경의 설정 문제를 인증 우회로 바꾸지 않습니다.

const {loadConfig}=require('./config.cjs');
const config=loadConfig(process.env);
// config.key는 필요한 구성 요소에만 전달합니다.
// console.log(config)는 사용하지 않습니다.

검사 행렬을 먼저 정합니다

정상값 하나만 시험하면 누락 시 기본값으로 돌아가는 결함을 놓칩니다. config-test.cjs는 누락·빈 문자열·공백·잘못된 길이·잘못된 문자·숫자와 정상값을 나눕니다. 실패 사례마다 예외가 있어야 하고, 정상값은 변형 없이 유지되어야 합니다. Object.freeze는 설정 속성의 우발적 재할당을 줄이는 장치이며 비밀을 읽지 못하게 하거나 메모리에서 지우는 장치는 아닙니다. 검사 목적을 효과보다 크게 설명하지 않습니다.

starter에서 Missing expected exception이 보이면 유효하지 않은 입력을 받았는데 예외가 발생하지 않았다는 뜻입니다. 정상 설정 검사까지 실패하면 정규식의 시작·끝 경계나 입력 길이를 확인합니다. 오류 메시지에 값을 출력해서 디버깅하지 말고, 유형·길이·검사 항목 이름을 테스트에서 확인합니다. 민감 문자열을 공유할 필요가 없는 실패 재현을 만드는 것도 교육 과정의 일부입니다.

전달 예시와 사본 관리

config.example은 변수 이름과 작성 규칙만 설명하고 키 원문을 넣지 않습니다. 수동 실행은 README의 무작위 더미 키 생성 명령을 사용하며, 자동 시험에서는 고정값을 시험 코드에만 둡니다. 명령에 실제 비밀 문자열을 직접 적으면 셸 기록에 남을 수 있습니다. 이 실습은 실제 서비스의 비밀 저장소를 구성하는 과제가 아니므로, 운영에서는 승인된 전달 도구와 접근 정책을 별도로 설계해야 합니다.

저장소 제외 규칙은 앞으로 파일이 추가되는 일을 줄이지만 과거 기록이나 이미 만들어진 ZIP을 정리하지 않습니다. 키가 노출되었다면 코드 삭제와 키 폐기는 다른 일입니다. 여기서는 비밀을 분리하고 누락을 거절하는 경계까지 완성합니다. 교체 이후 기존 접근을 폐기하는 검증은 키 교체 레슨에서 이어갑니다. 실패한 설정으로 실제 요청을 보내는 우회 실험은 필요하지 않습니다.

누적 앱에서 확인할 지점

m06의 자료 저장·소유자 검사·입력 제한을 바꾸지 않고 새로운 설정을 인증 구성에 전달합니다. 기존 Map 모델은 과거 회귀 검사로 남고, 현재 앱의 경로는 server.cjs에서 secure-app.cjs와 SQLite 저장소로 이어집니다. 설정 검사 단위 시험과 실제 생성 함수의 누락 거절을 각각 확인해야 함수만 완성하고 호출을 잊는 실수를 잡을 수 있습니다. 보고서에는 검사 위치·거절 조건·정상 대조·잔여 보관 위험을 적습니다.

더 읽기의 환경 설정 장은 문자열 설정과 숫자 변환·파일 전달을 넓게 설명합니다. 이번 레슨의 제출물은 그 장의 서버가 아니라 개인 자료 보관 앱의 키 전달 경계입니다. 완성 후에는 코드에서 키 원문을 제거했는지, 예시 파일에도 복제하지 않았는지, 설정 누락이 서버 객체 생성 전에 중단되는지 자신의 말로 설명할 수 있어야 합니다.

관련 원칙과 도구의 범위는 OWASP 비밀 관리에서 확인할 수 있습니다. 이 레슨의 판정은 제공된 교재 코드와 고정 fixture 범위에 한정합니다.

따라하기

누락·빈 값·정상값을 비교합니다

키 원문 대신 설정 허용 여부만 출력합니다. node로 실행 가능한 독립 예제입니다.

function read(env){
 const key=env.VAULT_KEY;
 if(typeof key!=='string' || !/^[0-9a-f]{64}$/.test(key))throw new Error('VAULT_KEY: expected 64 lowercase hex characters');
 return Object.freeze({key});
}
for(const key of [undefined,'','a'.repeat(64)]){try{read({VAULT_KEY:key});console.log('ACCEPT');}catch(e){console.log('REJECT');}}

실행 결과

REJECT
REJECT
ACCEPT

오류에 입력값이 없는지 확인합니다

잘못된 문자열을 오류에 덧붙이지 않습니다.

function read(env){
 const key=env.VAULT_KEY;
 if(typeof key!=='string' || !/^[0-9a-f]{64}$/.test(key))throw new Error('VAULT_KEY: expected 64 lowercase hex characters');
 return Object.freeze({key});
}
const bad='synthetic-secret-invalid';try{read({VAULT_KEY:bad});}catch(e){console.log(e.message);console.log(e.message.includes(bad));}

실행 결과

VAULT_KEY: expected 64 lowercase hex characters
false

정상 설정의 전달 형식을 확인합니다

반복 문자는 단위 시험용입니다. 길이와 객체 동결 상태만 확인합니다.

function read(env){
 const key=env.VAULT_KEY;
 if(typeof key!=='string' || !/^[0-9a-f]{64}$/.test(key))throw new Error('VAULT_KEY: expected 64 lowercase hex characters');
 return Object.freeze({key});
}
const c=read({VAULT_KEY:'a'.repeat(64)});console.log(c.key.length);console.log(Object.isFrozen(c));

실행 결과

64
true

starter의 설정 검증을 완성합니다

security-secret-config ZIP에서 config.cjs의 TODO를 고친 뒤 bash check.sh를 실행합니다. 누락 입력에서 Missing expected exception이 나오면 거절 분기를 확인합니다. solution의 마지막 PASS 행과 종료 코드 0을 대조합니다.

확인 문제

실습

config.cjs의 loadConfig를 완성합니다. 누락·빈 문자열·공백·잘못된 길이·문자·유형을 거절하고 정상 소문자 hex 64자는 받아들입니다. 오류에 입력값을 넣지 않고 Object.freeze 설정을 반환합니다. 테스트를 변경하지 않습니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

PASS: missing, empty, malformed configuration rejected; valid key accepted

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 비밀값을 코드와 로그에서 보호하는 방법을 설명합니다.