수정 및 롤백
Hostveil은 절대 눈감고 변경하지 않습니다. 모든 수정은 얼마나 안전하게 자동화할 수 있는지에 따라 분류되고, 항상 정확한 변경 내용을 먼저 보여 주고, 원본을 체크포인트로 백업하며, 명령 한 줄로 되돌릴 수 있습니다.
발견 항목은 어떻게 분류되는가
각 발견 항목에는 얼마나 자동화가 적절한지를 결정하는 수정 종류가 태그로 붙습니다.
| 종류 | 의미 | 수정 가능? |
|---|---|---|
| Auto-fix | 명백히 올바른 하나의 변경. 적용되기 전에 여전히 확인할 수 있습니다. | 예 |
| Review | 여러 유효한 대안이 있으며, 어느 것을 적용할지 직접 선택합니다. | 예 |
| Manual | 안전한 자동화가 없음 — Hostveil이 대신 무엇을 해야 하는지 설명합니다. | 아니오 |
| Unavailable | 아직 수정 방법이 없는 알려진 문제 (예: 패치가 없는 CVE). | 아니오 |
Auto-fix와 Review 발견 항목만 Hostveil이 적용할 수 있습니다. fix --all은 Auto-fix 항목만 적용하고, 판단이 필요한 것은 사용자에게 남깁니다. Review 항목까지 받아들이겠다고 명령에서 밝히는 방법이 fix --all --review입니다.
Auto-fix가 되려면
--review를 붙이지 않는 한, Auto-fix는 fix --all이 혼자 적용하는 유일한 종류입니다. 그러니 기준은, 아무도 들여다보지 않은 채 적용해도 문제 삼을 데가 없으려면 무엇이 필요한가입니다. 세 가지가 동시에 성립해야 합니다.
되돌릴 수 있을 것. 롤백이 복원할 무언가가 남아야 합니다 — 파일 편집은 원래 바이트를, 권한 변경은 원래 권한 비트를 저장합니다. 명령을 실행하는 수정은 남길 것이 없어서 hostveil history가 실행됐다는 사실만 기록하고 되돌릴 수 없음으로 표시합니다. 그래서 명령을 실행하는 수정은 절대 Auto-fix가 되지 않으며, 이는 각 체커가 알아서 지키는 관례가 아니라 Hostveil이 수정 자체에 적용하는 규칙입니다. updates.disabled가 이 규칙이 존재하는 이유를 보여 줍니다. 조치는 하나뿐이고 — unattended-upgrades를 설치하고 서비스를 켜는 것 — 그게 ‘Auto-fix 모양’이 뜻하는 전부인데, 여전히 명령이므로 사용자에게는 Review로 도착합니다. fileperms.shadow와 fileperms.owner는 같은 기준이 양쪽으로 갈리는 자리입니다. /etc/shadow의 권한을 조이는 것은 체크포인트가 이전 권한을 기록하므로 Auto-fix이고, chown root:root는 체크포인트에 이전 소유자를 적을 자리가 없으므로 Manual입니다.
되돌릴 수 있을 뿐 아니라, 되돌릴 손이 남아 있을 것. 변경이 잘못됐을 때 그것을 되돌리려면 그 호스트에 접속할 수 있어야 합니다. 자기 접근을 끊어 버릴 수 있는 조치는 파일 편집 자체가 완벽하게 되돌아가더라도 이 조건에서 탈락합니다. sshd_config 한 줄을 바꾸는 것뿐인데도 여러 SSH 항목이 Review인 이유입니다. ssh.passwordauth는 PasswordAuthentication no를 쓰는데, 바이트 단위로 되돌릴 수 있지만 키 로그인이 아직 되지 않는 상태라면 아무 소용이 없습니다. 되돌리려면 방금 끊긴 그 세션이 필요하니까요. ssh.gatewayports, ssh.hostbasedauth, ssh.kbdinteractive도 지금 사용자가 타고 있을지 모르는 경로를 각각 닫습니다. firewall.inactive는 같은 반론을 끝까지 밀고 가서 Manual에 이릅니다. 기본 거부 정책은 설정하는 순간 규칙이 허용하지 않는 모든 연결에 적용되고, 명령 실행에는 되돌릴 체크포인트가 없습니다.
모호하지 않을 것. 옳은 값이 정확히 하나여야 하고, 그것을 적용해서 정상적인 설정이 깨지지 않아야 합니다. 커널 하드닝 항목은 전부 이 조건에서 걸려 전부 Review입니다. sysctl.d 드롭인을 쓰는 것은 기계적이고 되돌릴 수 있는 편집 하나이지만, rp_filter는 단일 회선 서버에서 1이고 VPN이나 다중 회선 호스트에서는 2이며, 이 호스트가 어느 쪽인지 아는 사람은 사용자뿐입니다. Hostveil은 값을 쓰기는 합니다 — 사용자가 먼저 보고 난 뒤에요. 통과하는 쪽은 fileperms.shadow처럼 생겼습니다. 이 수정은 권한 비트를 빼기만 하므로 — 0604는 0600이 되지 0640이 되지 않습니다 — 파일이 갖고 있지 않던 접근 권한을 새로 내줄 수 없습니다. agent.config-perms가 설정하는 값도 해당 런타임의 하드닝 문서가 그대로 싣고 있는 기준값입니다.
조건을 어겼는데 Review가 아니라 Manual이 되는 경우
세 조건 중 하나를 어기면 서로 독립적인 대안이 둘 이상 있을 때 Review가 됩니다. Review는 경고가 아니라 선택입니다 — Hostveil이 선택지를 보여 주고 사용자가 하나를 고릅니다. 기계적인 조치가 하나뿐이면 고를 것이 없고, 그 항목은 Manual이 됩니다.
커널 항목에는 진짜 짝이 있습니다. 재부팅해도 남는 드롭인을 쓰거나, 지금 바로 적용되고 재부팅하면 사라지는 sysctl -w를 쓰거나 — 그래서 Review입니다. compose.ds017에는 그 짝이 없습니다. :ro를 붙이는 것이 유일하게 기계적인 조치인데 그 마운트에 정당하게 쓰는 서비스를 그대로 멈춰 세우고, 항목이 함께 제시하는 대안인 더 좁은 하위 디렉터리 마운트는 서비스가 실제로 어느 경로를 건드리는지 알아야 합니다. 선택지가 하나이고 그걸 안전하게 만들 방법이 없으면 Manual입니다.
Manual 항목은 수정이 없는 이유를 한 문장으로 달고 다니며, 그 문장은 항목 바로 옆에 나옵니다 — hostveil explain에도, 터미널 상세 창에도, 대시보드에도. 점검 항목의 이유들은 Hostveil이 호스트에 적용하는 정책이 아니라 각 항목에 대해 누군가 내린 판단이고, 그 판단은 그것을 마주치는 자리에서 읽을 수 있어야 합니다.
종류는 어떻게 정해지는가
스캔 결과에 찍히는 종류를 정하는 곳이 둘이고, 둘 중 더 조심스러운 쪽이 이깁니다.
수정 레지스트리는 고칠 수단이 있는지를 정합니다. 체커가 수정을 의도했는데 등록된 것이 없으면 그 항목은 Manual로 표시됩니다. 눌러도 아무 데도 닿지 않는 수정 버튼이 생기지 않도록요. (체커가 Unavailable로 표시한 항목은 — 아무도 수정을 내놓지 않은 취약점을 모은 cve.unpatched-image 같은 것은 — 그대로 Unavailable로 남습니다.) 컨테이너에 Docker 소켓을 마운트한 compose.ds016이 가장 분명한 사례입니다. compose 체커는 수정을 요청하지만 레지스트리가 내놓기를 거절합니다. 유일하게 정직한 조치가 마운트를 지우는 것이고, 그러면 Portainer와 Traefik과 Watchtower가 멈추기 때문입니다.
체커는 그 수정에 사람 판단이 얼마나 필요한지를 정합니다. 레지스트리에 Auto-fix로 등록돼 있다는 것은 조치가 기계적인 하나라는 모양에 대한 진술이지, 그 항목을 사람이 보지 않고 처리해도 된다는 판단이 아닙니다. 그리고 이 진술은 체커를 낮추지 못합니다. 체커가 Review를 요구했으면 Review가 나옵니다. ssh.passwordauth에서 이 일이 벌어지는 것을 볼 수 있습니다. 등록된 수정은 지시어 한 줄을 쓰는 편집 하나로, 모양만 보면 Hostveil이 Auto-fix로 등록하는 그것입니다. 그런데 SSH 체커는 비밀번호 로그인을 끄는 일이 사용자 자신의 접근에 무엇을 할 수 있는지를 알기에 Review를 요구합니다. 화면에는 Review로 나오고, fix --all은 그냥 지나갑니다.
규칙은 반대 방향으로도 돕니다. 체커가 Manual이라고 말하면, 그 ID에 우연히 맞는 수정이 등록돼 있어도 뒤집지 못합니다. compose.ds018이 이를 보여 줍니다. 모든 인터페이스에 열린 데이터스토어는 Compose 서비스에서 나왔으면 편집할 파일이 있으므로 Auto-fix이고, docker run으로 띄운 컨테이너에서 나온 같은 규칙은 편집할 파일이 없으므로 Manual입니다. 둘 중 어느 쪽을 보고 있는지 아는 것은 체커뿐입니다. accounts.uid0과 모든 dockerd. 항목도 같은 방식으로 Manual입니다 — 수정이 없어서가 아니라, 그것을 찾은 체커가 그렇게 선언했기 때문입니다.
미리보기와 적용, TUI, 대시보드가 모두 같은 코드로 종류를 정합니다. 보고서에서 읽은 종류가 곧 fix --all이 무엇을 건드릴지 정하는 그 종류입니다.
네 단계로 정리하면
어떤 발견 항목이든 종류를 직접 따져 볼 수 있습니다. Hostveil은 다음 순서로 판단합니다.
- 체커가 이 항목에 사람의 판단이 얼마나 필요하다고 보는지 선언합니다.
- 이 ID에 조치가 등록돼 있고, 이 발견 항목에 대해 실제로 만들어지는가? 아니면 답은 Manual이고, 아래 어느 것도 그것을 뒤집지 못합니다.
- 등록된 조치에는 형태가 있습니다. Auto-fix는 동작이 정확히 하나, Review는 독립적인 대안이 둘 이상입니다. 형태로는 Auto-fix이지만 파일을 고치는 대신 명령을 실행하는 조치는 여기서 Review가 됩니다. 명령은 되돌릴 체크포인트를 남기지 않기 때문입니다.
- 1단계와 3단계 중 사람의 개입을 더 많이 요구하는 쪽이 표시됩니다. 순서는 Auto-fix < Review < Manual < Unavailable입니다.
3단계가 대안 하나짜리 Review가 있을 수 없는 이유입니다. 고를 것이 없고, 동작 하나에 경고를 붙인 것은 선택이 아닙니다. 위 절이 Manual로 돌리는 규칙이 바로 이것입니다.
Review 항목도 Hostveil이 완전히 고칠 수 있습니다 — 사용자가 보지 않는 사이에 하지 않을 뿐입니다. 적용하는 방법은 같은 명령에 원하는 대안을 하나 붙이는 것이고, 바로 다음 절에 있습니다.
수정 적용하기
단일 발견 항목을 ID로 미리보고 적용합니다.
hostveil fix ssh.rootlogin
적용은 항상 1) 정확한 diff나 명령을 보여 주고, 2) 원본 파일을 체크포인트로 백업하고, 3) 변경을 적용한 뒤, 4) 해당 발견 항목의 영역을 다시 돌려 정말 사라졌는지 확인합니다. --yes를 넘기지 않는 한 [y/N] 프롬프트에서 확인합니다.
마지막 단계가 있는 이유는 "Hostveil이 파일을 썼다"와 "발견 항목이 사라졌다"가 서로 다른 주장이고, 지금까지는 앞의 것만 확인됐기 때문입니다. 결과는 추측하지 않고 그대로 보고합니다.
| 결과 | 의미 |
|---|---|
사라짐 gone | 영역을 다시 점검했고 더 이상 보고되지 않습니다. |
아직 반영 전 pending | 파일은 올바르고 실행 중인 호스트는 그대로입니다. Compose 파일은 Docker가 지금 보고 있는 것이 아니라 예전에 한 번 전달받은 것이라, docker compose up -d로 다시 만들기 전까지 컨테이너는 옛 설정을 유지합니다. 재점검은 그 파일을 읽으므로 확인할 수 있는 것은 파일이 올바르다는 사실뿐입니다. 메시지가 반영에 필요한 명령을 알려줍니다. |
여전히 보고됨 still-present | 변경은 적용됐지만 항목이 남아 있습니다. 대개 아직 반영되지 않았기 때문입니다. sysctl 드롭인은 다음 부팅에 적용되고, sshd는 재시작 전까지 이미 읽어 둔 설정으로 동작합니다. 실패가 아니며, 롤백도 그대로 가능합니다. |
확인 불가 unavailable | 재점검이 실행되지 못했거나 범위를 다 못 봤습니다. Docker가 없거나 root가 아닌 경우 등입니다. “들여다보지 못했다”는 “여전히 문제다”도 아니고 “고쳐졌다”도 아닙니다. |
점검 안 함 not-run | 재점검을 시도하지 않았습니다. fix --all이 이 값을 보고하며, 이유는 아래에 있습니다. |
가운데 두 줄은 구분해 둘 값어치가 있습니다. 여전히 보고됨은 체커가 옛 상태를 볼 수 있다는 뜻이고, 아직 반영 전은 볼 수 없다는 뜻입니다. 체커가 읽는 것이 방금 조치가 쓴 그 파일이기 때문입니다. 뒤쪽이 잘못 말했을 때 더 위험하므로, 아무것도 바뀌지 않은 호스트를 두고 사라졌다고 하는 대신 따로 보고합니다.
재점검은 수정됨 표시 여부를 바꾸지 않습니다. 대신 바꿀 수 있는 것은 점수입니다. 아직 반영되지 않은 수정은 공격자가 볼 수 있는 것을 아무것도 바꾸지 않았으므로, 반영될 때까지 해당 항목은 계속 감점됩니다. 이는 재점검에 달려 있지 않습니다 — 재시작이 필요한 수정은 스스로 그렇게 선언하고, 그래서 fix --all도 아무것도 재점검하지 않은 채 같은 답에 도달하며, 적용한 것 중 몇 개가 아직 기다리고 있는지 보고합니다.
fix --all이 수정마다의 재점검을 건너뛰는 이유는 따로 있습니다. 스무 개를 고칠 때마다 재점검하면 체커를 스무 번 돌리게 되고, 일괄 적용은 어차피 재스캔으로 끝나기 때문입니다.
--json에 실리는 이름은 두 번째 열의 값입니다.
Review 대안 선택하기
Review 발견 항목은 독립적인 대안을 제공합니다. 0부터 시작하는 인덱스를 --action으로 넘깁니다.
hostveil fix ssh.passwordauth --action 00번이 권장안입니다. 모든 인터페이스가 대안을 권장 순서대로 나열하고, TUI와 대시보드는 첫 번째를 미리 선택해 두며, fix --all --review는 묻지 않고 그것을 적용합니다. 목록을 읽지 않았을 때 얻게 되는 것도 그것입니다.
여기서 권장은 무인으로 적용하기에 권장한다는 뜻이고, 사람이 먼저 고를 것과 늘 같지는 않습니다. 커널 하드닝 항목이 그 경우입니다. 드롭인에 영구히 쓰는 것과 sysctl -w로 실행 중인 커널에 적용하는 것은 진짜 선택이고 어느 쪽이 일반적으로 낫지 않습니다. 다만 일괄 적용에는 선택의 여지가 없습니다. 체크포인트를 남기고 재부팅을 견디는 쪽은 드롭인뿐이라, 그것이 0번이고 나머지가 뒤에 놓입니다.
서비스별로 구분하기
동일한 Compose 규칙이 둘 이상의 서비스에서 발생하면, --service로 범위를 좁힙니다.
hostveil fix compose.ds018 --service db안전한 모든 수정 적용하기
hostveil fix --all각 Auto-fix 발견 항목을 적용하며, 각각 자체 체크포인트를 가지므로 개별적으로 롤백할 수 있습니다.
이력 및 롤백
적용된 모든 수정은 체크포인트로 기록됩니다. 최신순으로 나열합니다.
hostveil history각 항목은 타임스탬프, 발견 항목 ID, 레이블, 정확한 롤백 명령을 보여 줍니다. 체크포인트 ID로 하나를 되돌립니다.
hostveil rollback <checkpoint-id>롤백은 백업된 파일을 바이트 단위 그대로 복원합니다. TUI, 웹 대시보드, CLI가 모두 하나의 공유 엔진을 거치기 때문에, 어느 곳에서 적용한 수정이든 history에 나열되고 어디서나 되돌릴 수 있습니다. (일부 작업은 본질적으로 되돌릴 수 없으며, history는 이를 되돌릴 수 없음으로 표시합니다.)
체크포인트는 얼마나 보관되나
Hostveil은 최근 200개를 보관하고, 한 번에 아무리 많이 생기더라도 1시간 안에 쓰인 것은 지우지 않습니다.
중요한 것은 두 번째 규칙입니다. 정리는 저장할 때마다 돌기 때문에, 개수 상한만으로는 큰 fix --all 한 번이 자기가 만든 앞쪽 체크포인트를 지워 버립니다. Compose 서비스가 백 개인 호스트라면 먼저 적용한 조치가 적용되는 그 순간 되돌릴 수 없게 됩니다. 1시간은 한 번의 실행과, 호스트가 이상해진 것을 알아차리고 rollback에 손을 뻗기까지의 시간을 함께 덮습니다.
스캔 스냅숏은 훨씬 빨리 정리됩니다. 하나 잃어도 잃는 것이 되돌릴 능력이 아니라 추세선이기 때문입니다.
롤백이 거부할 때
체크포인트에는 수정이 써 넣은 내용의 체크섬이 함께 기록됩니다. 그 뒤에 파일을 직접 편집해서 체크섬이 더 이상 맞지 않으면, 롤백은 복원하지 않고 멈춥니다.
hostveil: /etc/ssh/sshd_config changed after the fix was applied
Rolling back would overwrite it with the pre-fix backup and discard those edits.
Rollback keeps no backup of its own, so this cannot be undone.
Save a copy first, then: hostveil rollback cp-3f9a --force이것은 실패가 아니라 거부입니다 — 아무것도 건드리지 않았습니다. 롤백은 자기 자신의 체크포인트를 남기지 않기 때문에, 직접 작업한 편집 위에 복원해 버리면 되돌릴 방법이 없습니다. 복구 기능이 절대 만들어서는 안 되는 결과입니다. 현재 파일을 따로 복사해 둔 뒤, 그래도 백업을 되살리고 싶다면 --force를 넘기세요.
대시보드는 같은 질문을 플래그 대신 확인 대화상자로 묻습니다. 같은 파일을 두 번 수정한 것은 외부 편집으로 보지 않으므로, 하나의 Compose 파일에 fix --all을 적용해도 그 체크포인트들은 모두 되돌릴 수 있습니다.
복원할 것이 남지 않는 수정
명령을 실행하는 수정 — 새 이미지를 받거나 서비스를 재시작하는 종류 — 은 백업할 파일이 없어서 이력 기록만 남고 복원 지점은 생기지 않습니다. Hostveil은 적용 전 미리보기에서 이를 알려 주고, history는 해당 항목을 되돌릴 수 없음으로 표시합니다. 되돌리려면 직접 손으로 해야 합니다.