Devin.KR

설정 변경 전 보관

100분 안팎

학습 목표

유닛과 환경 설정을 날짜별로 보관하고 diff와 체크섬으로 변경 전후를 비교합니다.

개념

변경 전에 복구 가능한 원본을 만듭니다

설정을 수정한 뒤 실패하면 기억에 의존해 원래 값을 다시 적기 쉽습니다. 그러나 공백·경로·파일 모드가 달라지면 같은 설정이 아닐 수 있습니다. 이번 목표는 변경 전 유닛과 환경 파일을 별도 폴더에 보관하고 차이·체크섬·복원 결과를 설명하는 것입니다. 사본을 만들었다는 말보다 어느 파일을 언제 어떤 권한으로 보관했고 복원된 바이트가 같은지를 증거로 남깁니다. 설정 보관은 실행 중 서비스가 정상이라는 검사와 별개입니다.

보관 대상은 bootcamp-systems.service와 service.env 두 파일입니다. 앞 모듈의 data/items.json은 이번 변경의 대상이 아니므로 복원 과정에서 새 샘플 값으로 덮어쓰지 않습니다. 실제 앱 데이터 백업은 이후 모듈에서 일관성과 복구 범위를 다룹니다. 유닛에 drop-in이 추가된 환경이라면 두 파일만으로 유효 구성을 재현할 수 없다는 점도 기록합니다. 이번 실습은 별도 drop-in 없는 전용 유닛으로 범위를 정합니다.

날짜와 충돌 방지를 함께 설계합니다

evidence/backups 아래에 시각과 실행 PID를 붙인 새 폴더를 만듭니다. 같은 초에 두 번 실행할 가능성이 있어 날짜 문자열 하나만으로 고유하다고 믿지 않습니다. 보관 함수는 목적 폴더가 이미 있으면 FileExistsError로 거부하고 옛 사본을 덮어쓰지 않습니다. 덮어쓴 백업은 사고 직전 상태를 잃을 수 있으므로 충돌을 숨기지 않습니다. 재시도하려면 새로운 이름을 고르고 원래 사본의 존재를 유지합니다.

보관 전에 두 원본이 모두 일반 파일인지 확인합니다. 첫 파일을 저장한 뒤 둘째가 없다는 사실을 알면 완성되지 않은 묶음을 정상 백업으로 오해할 수 있습니다. 심볼릭 링크도 이 실습에서는 거부해 다른 경로를 뜻하지 않도록 합니다. 실제 운영에서는 의도된 링크를 지원할 정책이 필요하지만 교육용 함수가 조용히 따라가도록 하지 않습니다. 원본 입력의 범위를 좁힐수록 검사와 설명이 명확해집니다.

바이트와 접근 권한을 보관합니다

로컬 실습 함수는 파일을 텍스트로 다시 쓰지 않고 copyfile로 바이트를 복사합니다. 줄바꿈이 CRLF인 원본을 LF로 바꾸면 눈에는 같은 줄이어도 체크섬은 달라집니다. 사본 폴더는 0700, 사본과 manifest는 0600으로 제한합니다. 원본 모드는 manifest에 별도로 기록해 복원 때 되돌립니다. 서비스 환경 파일이 나중에 비밀값을 포함해도 사본이 더 넓은 권한으로 노출되지 않게 합니다.

이 로컬 함수는 일반 사용자의 임시 파일만 다루며 root 소유권을 복원하지 않습니다. 실제 VM 미션은 원본의 소유자·그룹·모드를 stat 기록으로 남기고 기존 시스템 파일에 내용을 되돌리는 방식으로 그 속성을 유지합니다. 파일을 삭제한 뒤 새로 만드는 복원이라면 소유권까지 다시 설정해야 합니다. 내용 해시가 같아도 서비스 계정의 접근이 달라졌을 수 있으므로 메타데이터와 앱 권한을 따로 확인합니다.

체크섬은 동일성의 근거입니다

SHA-256은 파일 바이트로부터 고정 길이의 해시를 계산합니다. 원본에서 사본으로 복사한 뒤 동일 해시인지 비교해 내용이 같은지 확인합니다. manifest에는 파일 이름별 sha256과 mode를 남깁니다. 해시는 변경 전 값과 비교할 기준이 있어야 의미가 있습니다. 장애가 난 파일로 새 manifest를 만들어 정상이라 선언하면 원래 설정의 보관 증거가 되지 않습니다. 빈 파일도 해시가 있으므로 누락과 빈 내용을 구분합니다.

체크섬 파일과 사본을 함께 누구나 수정할 수 있다면 해시 일치만으로 신뢰성을 보장하지 못합니다. 권한을 제한하고 검증 기준이 바뀌지 않았는지 확인합니다. 이번 과제는 변조를 발견하는 기능 테스트이지 공격자가 보관소를 통제한 상황의 인증 설계가 아닙니다. 기록에는 내용 일치 확인이라는 범위를 쓰고 완전한 보안이라는 표현을 사용하지 않습니다. 공개 제출물에 환경 파일 전체 내용을 붙이지 않습니다.

diff를 성공과 실패로만 나누지 않습니다

diff -u는 앞 파일을 이전, 뒤 파일을 변경 후로 두고 비교합니다. 마이너스 줄은 이전에서 빠지는 내용, 플러스 줄은 새로 들어가는 내용입니다. RUN_SECONDS만 바꿨다고 예상했다면 DATA_FILE까지 바뀐 줄은 검토해야 합니다. diff는 같으면 종료 코드 0, 차이가 있으면 1, 비교 오류면 더 큰 값을 반환합니다. 차이 있다는 결과는 명령 실행 자체의 오류가 아니므로 이를 구분해 기록합니다.

set -e가 있는 셸에서 diff를 단독 실행하면 정상적인 차이도 스크립트를 끝내게 할 수 있습니다. if 블록에서 종료 코드를 받고 1인지 더 큰 오류인지 나눕니다. diff를 실행할 수 없거나 파일이 없는데도 단순히 변경됨이라고 쓰면 근거가 틀립니다. 파일의 전체 체크섬은 내용 동일성을, diff는 사람이 읽을 변경 위치를 보여 줍니다. 두 도구를 같은 목적의 대체품으로만 보지 않습니다.

복원은 전체 검증 후 시작합니다

restore는 사본 묶음의 이름과 모든 체크섬을 확인한 뒤 대상에 씁니다. 첫 파일부터 복사하고 뒤의 사본이 변조됐음을 발견하면 반쪽 설정이 됩니다. 테스트는 두 번째 사본을 바꿔도 첫 번째 대상이 유지되는지 확인합니다. 대상이 심볼릭 링크면 이 함수는 거부합니다. 필요한 경우를 임의로 허용하기보다 복원 경로가 실습 폴더의 일반 파일이라는 계약을 지킵니다.

모든 사본을 검증한 뒤에도 쓰는 도중 I/O 실패가 나면 두 파일을 한 번에 원자적으로 복구하는 보장은 없습니다. 동시 편집도 이 함수 범위 밖입니다. 실습에서는 변경 작업자를 한 명으로 제한하고 실패가 나면 대상의 바이트와 권한을 다시 확인합니다. 실제 구성 배포는 임시 파일·교체 절차·서비스 중단 범위까지 추가로 설계해야 합니다. 기능 테스트가 처리하지 않는 조건을 인계에 남깁니다.

내용 복구 뒤 서비스 검사를 잇습니다

로컬 테스트는 임시 폴더에서 바이트·모드 복원만 확인하며 systemd를 실행하지 않습니다. VM에서는 유닛을 되돌리면 daemon-reload로 정의를 다시 읽고 기존 유한 서비스가 자연 종료한 뒤 다음 기동을 수행합니다. 환경 값은 새 기동에서 읽힙니다. 파일 복사만 끝났는데 옛 프로세스의 응답을 보고 새 설정이 적용됐다고 쓰지 않습니다. MainPID와 기동 시각을 묶어 어떤 실행을 확인했는지 남깁니다.

정상 판정은 보관 해시·되돌린 설정 해시의 일치, 서비스 실행 주체, 기동 중 active, /items의 원래 데이터로 구성합니다. /health만으로는 잘못된 데이터 경로가 드러나지 않을 수 있습니다. 미션 검증은 설정 두 개의 원래 내용과 이전 app.conf·items.json의 불변도 확인합니다. 실패 원인의 기록을 지우지 않고 수정과 복구 시각을 추가해 다른 담당자가 같은 절차를 따라갈 수 있게 합니다.

실습과 인계의 완료 기준

starter의 backup.py에서 snapshot과 restore를 완성합니다. check.sh는 왕복 복원·폴더 충돌·사본 변조·원본 누락과 링크·빈 파일을 검사합니다. 실제 경로를 건드리지 않는 테스트여서 여러 번 실행해도 VM 설정을 바꾸지 않습니다. 실패 메시지에 snapshot NotImplementedError가 보이면 함수의 TODO가 남았다는 뜻입니다. 해시 오류 테스트가 통과하지 않으면 검증보다 쓰기가 먼저 일어나는지 호출 순서를 읽습니다.

최종 기록에는 변경 목적, 보관 위치, 원본 메타데이터, diff의 예상 범위, 복구 명령, 기능 확인, 남은 한계를 적습니다. 사본을 만들기 전 이미 바뀐 파일을 원본으로 표시하지 않습니다. 보관 날짜만 남기고 파일 이름과 검사 방법을 빠뜨리면 다음 담당자가 복구할 수 없습니다. 작은 로컬 회귀를 통과한 다음 VM 미션에서 실제 기동과 복구를 연결하며 diff 옵션의 자세한 설명은 더 읽기 장으로 보냅니다.

따라하기

내용 변화와 해시

환경 값 한 줄의 변경을 실제 바이트로 비교합니다. VM 보관 실행을 모사한 출력이 아닙니다.

import hashlib
a=b'RUN_SECONDS=20\n';b=b'RUN_SECONDS=5\n'
print('same_before='+str(hashlib.sha256(a).hexdigest()==hashlib.sha256(a).hexdigest()))
print('same_after='+str(hashlib.sha256(a).hexdigest()==hashlib.sha256(b).hexdigest()))

실행 결과

same_before=True
same_after=False

diff의 방향 읽기

고정 파일 이름을 사용해 실제 unified diff를 출력합니다. 이전 줄 삭제와 변경 후 줄 추가를 구분합니다.

import difflib
a=['RUN_SECONDS=20\n'];b=['RUN_SECONDS=5\n']
print(''.join(difflib.unified_diff(a,b,fromfile='before/service.env',tofile='after/service.env')),end='')

실행 결과

--- before/service.env
+++ after/service.env
@@ -1 +1 @@
-RUN_SECONDS=20
+RUN_SECONDS=5

로컬 보관 검사

backup.py 두 함수를 구현한 다음 실행합니다. 이 명령은 임시 디렉터리에서만 5개 경계 검사를 수행합니다.

bash check.sh

실행 결과

PASS backup: 5 cases, bytes, modes, collision, tamper, missing, empty

VM 보관본 검토

미션 실행으로 생성한 실제 보관 폴더 이름을 확인해 변수에 지정합니다. 두 파일의 원본 소유권·모드와 해시를 대조하고 실제 환경 본문은 공개 제출하지 않습니다.

find evidence/backups -name manifest.json -print
# 위 목록에서 이번 실행 경로를 골라 지정합니다.
backup_dir=evidence/backups/실제-폴더이름
cat "$backup_dir/manifest.json"
cat "$backup_dir/metadata.txt"
cat evidence/service.md

확인 문제

실습

backup.py의 snapshot과 restore를 구현합니다. 두 파일의 사전 검증·날짜 충돌 거부·사본 제한 권한·SHA-256 및 원본 모드 기록·전체 검증 후 복원을 지킵니다. 임시 폴더 검사이므로 systemd와 관리자 권한은 필요 없습니다.

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

실행 명령

bash check.sh

기대 결과

PASS backup: 5 cases, bytes, modes, collision, tamper, missing, empty

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

더 읽기

면접 질문

  • 프로세스와 systemd 서비스 상태의 차이를 설명합니다.