1P by GN⁺ 4시간전 | ★ favorite | 댓글 1개
  • NixOS에서 비밀값을 Nix 설정, 비공개 Git 저장소, git-crypt에 평문으로 두면 Nix store가 누구나 읽을 수 있어 머신 접근자가 비밀값을 읽을 수 있음
  • sops-nix.sops.yaml 규칙과 sops 편집 흐름으로 비밀값 파일을 암호화하고, 활성화 시 호스트 SSH 키로 복호화해 /run/secrets/<name>tmpfs에 평문을 둠
  • agenixsecrets.nix에서 비밀값별 수신자 공개키를 지정하고 .age 파일을 /run/agenix/<name>tmpfs에 복호화하며, 새 호스트나 키 교체 때 재키잉이 필요함
  • 파일시스템에 비밀값을 직접 두는 방식은 단일 서버나 노트북에는 가능하지만, builtins.readFile "/var/lib/myservice/token"처럼 평가 시점에 읽으면 값이 Nix store에 들어가므로 피해야 함
  • 처음 시작하고 독립적인 토큰 몇 개라면 agenix가 절차가 적고, 메일 서버처럼 관련 비밀값이 많은 호스트라면 여러 값을 한 파일에 묶는 sops-nix가 더 잘 맞음

NixOS에서 비밀값을 다룰 때의 기본 위험

  • NixOS에서 비밀값 관리는 sops-nix, agenix/ragenix, 파일시스템 활용, 비공개 Git 저장소, git-crypt, Nix 설정에 직접 작성하는 방식으로 나뉨
  • 비공개 Git 저장소, git-crypt, Nix 설정 직접 작성 방식은 머신을 공유하거나 설정을 공개할 계획이 있으면 쓰면 안 됨
  • 공개된 설정에서 비밀값이 최소 두 번 유출된 적이 있으며, 예시는 1, 2에 남아 있음

sops-nix

  • sops-nix는 처음 접할 때 설정이 어렵게 느껴질 수 있지만, 문서가 크게 개선됐고 sops가 SSH 키로 비밀값 암복호화를 기본 지원하게 된 점이 큰 개선임
  • 다만 sops-nix는 이 SSH 키 지원에서 아직 뒤처져 있으며 sops-nix#779, sops-nix#922에서 관련 작업이 진행 중
  • 사용 흐름은 .sops.yaml에 암복호화 규칙을 만들고, sops 명령으로 비밀값 파일을 편집하는 방식
    • 예시는 secrets/*.yaml 경로에 대해 age 수신자로 SSH 공개키를 지정하는 형태
    • sops secrets/shush.yaml을 실행하면 선택한 편집기가 열리고, hello: sops 같은 YAML 값을 작성할 수 있음
    • 편집기를 종료하면 값은 ENC[AES256_GCM,...] 형태로 암호화되고, 같은 명령으로 다시 편집 가능함
  • NixOS 설정에서는 sops-nix 모듈이 대부분의 작업을 처리함
    • defaultSopsFile = ./secrets/shush.yaml;로 기본 비밀값 파일을 지정함
    • age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];로 호스트 SSH 키를 지정함
    • secrets."hello"owner, group, mode를 지정해 파일 권한을 설정함
  • 활성화 시점에 sops-nix가 호스트의 SSH 키로 파일을 복호화하고 평문을 /run/secrets/<name>에 둠
    • 이 경로는 tmpfs이므로 비밀값이 디스크에 닿지 않음
    • 값을 필요로 하는 서비스는 해당 경로를 읽으면 됨
  • 템플릿 기능은 공유 설정이나 다른 사용자가 참조하는 설정에서 유용함
    • 서비스 설정 파일이 평문과 일부 비밀값을 함께 요구할 때 전체 파일을 암호화하지 않아도 됨
    • 예를 들어 SMTP_USER=isabel은 평문으로 두고, SMTP_PASSWORD=${config.sops.placeholder."mailserver/smtp_password"}처럼 비밀값 자리표시자를 넣을 수 있음

agenix

  • agenixsops-nix와 달리 secrets.nix에서 비밀값과 접근 가능한 키를 설정하므로 Nix에 더 가까운 사용감을 줌
  • secrets.nix에는 사용자와 호스트의 SSH 공개키를 바인딩하고, 각 .age 파일에 어떤 공개키들이 접근 가능한지 지정함
    • 예를 들어 "secret1.age".publicKeys = [ isabel host1 ];, "secret2.age".publicKeys = [ isabel host2 ];처럼 비밀값마다 수신자 목록을 다르게 둘 수 있음
  • 비밀값 파일은 agenix CLI로 만들어야 함
    • agenix -e secret1.age 명령으로 secret1.age를 생성하거나 나중에 다시 편집할 수 있음
  • 호스트 설정에 연결하는 방식은 sops-nix와 비슷하지만, 각 비밀값이 별도 파일이므로 표면적이 더 작음
    • age.secrets.secret1.file = ./secrets/secret1.age;로 파일을 지정함
    • owner, group, mode로 소유자와 권한을 지정함
  • 부팅 시 호스트의 SSH 키로 각 .age 파일을 복호화해 /run/agenix/<name>에 둠
    • 이 경로도 tmpfs 위에 있음
  • 가장 자주 걸리는 부분은 재키잉(rekeying)
    • 새 호스트를 추가하거나 키를 교체하면, secrets.nix에서 publicKeys 목록이 바뀐 모든 비밀값을 다시 암호화해야 함
    • agenix --rekey가 이를 처리하지만, 기존 암호문을 읽기 위해 현재 수신자 중 하나의 개인키가 필요함
    • 실제로는 새로 올리려는 호스트가 아니라 가장 신뢰하는 머신에서 재키잉이 이뤄짐

파일시스템만 사용하는 방식

  • 파일시스템에 비밀값을 직접 두는 방식은 설정이 더 이상 머신을 완전히 설명하지 못한다는 비용이 있음
  • 재설치 시 모든 파일을 올바른 위치와 소유권으로 다시 넣어야 함
    • 새벽 2시에 서버를 복구하는 상황처럼 복구 작업에서는 큰 재앙이 될 수 있음
  • 피해야 할 패턴은 builtins.readFile "/var/lib/myservice/token" 같은 형태
    • 이 방식은 평가 시점에 파일을 읽고 내용을 Nix store에 포함함
    • Nix store는 누구나 읽을 수 있으므로, 처음 경고한 실패 방식과 정확히 같아짐
  • 대신 서비스에는 값 자체가 아니라 경로를 넘겨야 함
  • 단일 서버나 노트북에서는 괜찮을 수 있지만, 플릿이거나 설정만으로 처음부터 재구축하고 싶은 환경이라면 sops-nixagenix가 더 적합함
  • 머신마다 진짜 저장소에 넣으면 안 되는 값이 한두 개 있을 때는 괜찮지만, 나중에 다시 넣어야 할 책임이 미래의 자신에게 생김

sops-nix와 agenix 비교

  • sops-nix를 선택하는 주요 이유는 가능한 많은 데이터를 하나의 파일에 묶을 수 있다는 점
    • 메일 서버처럼 관련 비밀값이 많은 경우, agenix처럼 5개 안팎의 파일로 나누지 않고 한 파일에 더 많은 비밀값을 둘 수 있음
    • 강력한 도구로 계속 쓰기에 적합하며, 메일 서버처럼 비밀값이 매우 많은 호스트에서 먼저 선택할 만함
  • agenix단순성에서 강점이 있음
    • 배워야 할 YAML 스키마가 없음
    • 동기화해야 할 .sops.yaml이 없음
    • secrets.nix가 그냥 Nix이므로 호스트와 사용자에 쓰던 let ... in 바인딩을 키에도 그대로 쓸 수 있음
    • 사고 모델은 “비밀값 하나, 파일 하나, 수신자 목록 하나”이며 접근 제어 방식과 잘 맞음
    • 움직이는 부품이 가장 적은 편이면서도 호스트별 키 접근 제어를 제공하므로 NixOS 머신의 비밀값 관리를 처음 묻는 사람에게 추천하기 좋은 선택지임
  • 두 도구 모두 문제를 해결하며, 현재 차이는 대부분 사용성에 가까움
    • 처음 시작하고 여러 서비스가 관련 비밀값 묶음을 요구한다면 sops-nix가 더 잘 확장됨
    • 처음 시작하고 대부분 독립적인 토큰 몇 개만 있다면 agenix가 더 적은 절차로 목적지에 도달함
    • 첫 비밀값 도구를 고른다면 agenix로 흐름에 익숙해지고, “비밀값 하나당 파일 하나” 방식의 불편함을 실제로 느낄 때 sops-nix로 넘어가는 편이 좋음
  • 양자 내성 관련 내용은 정정됨
    • agesops는 양자 내성 보안 암호화 키를 지원함
    • age#578이 닫히고 v1.3.0이 릴리스됨
    • age 키를 만들 때 -pq를 포함하면 되며, 예시는 age-keygen -pq -o key.txt
Lobste.rs 의견들
  • sops-nix와 agenix가 복호화된 비밀값을 디스크에 저장하지 않는다는 설명은 봤지만, 실제로 어떤 이점이 있는지 궁금함
    암호화된 비밀값과 그 키가 둘 다 디스크에 있는 것 아닌가? 아니면 둘 중 하나가 TPM 같은 곳에 저장되는 건가?
    Nix를 막 쓰기 시작했고, 작은 셀프호스팅 배포에서는 단순해서 scp로 파일시스템에 넣는 방식을 쓰고 있음
    조금 찾아보니 SSH 개인키를 TPM으로 보호할 수 있는 듯하고, VM에서도 가능한지 궁금했는데 vTPM 지원이 있을 수 있다니 스스로 답을 찾은 셈임
    • 내 서버에서는 sops-nix를 쓰는데, 내 위협 모델에는 충분하고 잘 동작한다고 봄. 이제 SSH 개인키를 TPM에 저장하는 방식이 궁금해짐
      서버 쪽에는 NixOps 접근도 있음. Colmena가 하는 방식을 보면 됨: https://colmena.cli.rs/0.4/features/keys.html
      핵심은 비밀값을 가진 신뢰된 머신이 원격 서버로 밀어 넣는 구조임. 지금 scp로 하는 것과 비슷하지만, 그 과정을 더 Nix답게 구동함
    • 좋아하는 표현을 꺼낼 때가 됐네. “상황에 따라 다름!” 여기서 비밀값 관리는 두 문제를 동시에 다룸: 디스크 위의 비밀 파일을 보호하는 것, 그리고 시스템을 빌드하는 데 쓰는 설정 안의 비밀값을 보호하는 것. 결국 그 값들은 어딘가에는 있어야 하기 때문임
      최근 며칠 밤을 들여 내 시스템 flake에 agenix 계열을 다시 세팅했기 때문에, agenix에 대해서만 말할 수 있음. 관심 있는 사람에게는 agenix-rekey를 골랐는데, 비밀값이 들어간 .yml 파일을 둘 필요가 없고 이미 해둔 것처럼 비밀값을 전부 Nix 안에서 설정할 수 있기 때문임
      암호화된 비밀값은 Nix store에 있고, Nix store의 다른 것들처럼 전역 읽기 가능함. 그 비밀값을 푸는 SSH 개인키는 보통 실제 SSH 서버의 개인키이고, 선택적으로 그렇지 않게 할 수도 있음. 비밀값은 활성화 시점, 대략 부팅 시점에 복호화되어 /run/agenix/<user>에 놓임
      secrix라는 도구는 더 나아가 비밀값을 systemd 서비스에 묶고, 그 서비스를 비밀값이 필요한 서비스에 다시 묶음. 그래서 해당 서비스가 실행 중일 때만 비밀값이 복호화됨. 다만 실제로는 NixOS 사용자가 서비스를 자주 켰다 끄는 일이 드물어서 대부분 계속 실행 중이라는 뜻이 되기 쉽다. 사용자 생성 같은 시스템 활성화용 비밀값에는 맞을 수 있음
      agenix-rekey는 재키잉을 덜 귀찮게 만들고, secrets.nix 대신 flake 출력으로 대체해 줌. 키의 한쪽 절반으로 YubiKey 분할 ID를 쓴다. 재키잉은 그 키와 다른 절반으로 인증해서 비밀값을 복호화한 뒤, 서버의 SSH 공개키로 다시 키를 거는 과정임. 공개키는 시스템 설치 시점에 생성되며, 나는 nixos-anywhere--copy-host-keys를 써서 설치 클로저에서 생성된 키를 가져옴. 암호화된 비밀값은 저장소 안에 두지만, 신뢰된 빌더만 접근 가능한 별도 서브모듈에 둠
      참고로 vTPM은 대개 하드웨어 기반이 아니고, 많은 제공자는 TPM을 제공하더라도 대부분 TPM v2가 아니라 TPM v1.2만 제공함. 내 제공자인 BinaryLane도 그렇다. 보안 계층이 하나 더 생기는 건 맞지만, 실제 TPM 같은 마법의 HSM은 아니며 제공자나 호스트 노드 침해로부터 보호해 주지는 못함. 실제 HSM 기반 vTPM을 허용하려면 제공자 입장에서는 비용이 매우 클 것 같고, AWS는 AWS 가격으로 제공하는 듯함
    • 나는 agenix + agenix-rekey + age-plugin-1p를 씀
      마스터 키는 1Password 안에 있어서, 노트북 디스크에 어떤 자격 증명도 두지 않고도 어느 서버의 비밀번호든 편집, 조회, 재키잉할 수 있음
      실행 중 키 접근이 필요한 서버에는 접근권을 줄 수 있음. agenix-rekey에 해당 서버가 어떤 키를 볼 수 있는지 알려주고 agenix rekey를 실행하면, 그 서버가 복호화할 수 있는 암호화된 키 버전이 Nix store에 기록됨. 해당 서버의 SSH 개인키는 그 서버에만 있고 절대 밖으로 나가지 않음. agenix-rekey는 재키잉에 공개키만 필요함
      따라서 비밀값이 털리는 경우는 내 1Password 계정이 해킹되거나, 그 비밀값을 쓰는 서버가 해킹되는 경우임
    • Agenix에서는 기본적으로 암호화된 비밀값을 /etc/ssh/ssh_host_ed25519_key로 복호화한 뒤, /run/agenix.d에 마운트된 ramfs에 넣음
      그래서 맞음. 암호화된 내용, 암호화 키, 복호화된 내용이 모두 파일시스템에서 접근 가능함
    • 이렇게 하면 전체 NixOS 설정을 공개로 공유할 수도 있음. 그렇게 하는 사람은 많지 않지만, Nix의 약속 중 절반은 다른 사람도 내 시스템 문제를 같이 디버깅할 수 있다는 데 있다고 느낌
  • agenix와 agenix-rekey를 써보려고 생각만 하고 있었음. 많은 사람이 말하는 고통 지점을 꽤 줄여줄 것 같은데 아직 해보진 못함
    https://github.com/oddlama/agenix-rekey
  • agenix로 자격 증명을 관리하다가 그냥 파일시스템에 두는 방식으로 옮겼음. 더 단순하고, 재설치도 내게는 드문 일이기 때문임
    게다가 오래 유지되는 자격 증명도 점점 줄고 있음. 장기 자격 증명 복사에서 벗어나 하드웨어 기반 자격 증명으로 가고 있고, 그것을 직접 쓰거나 짧게 유지되는 자격 증명으로 교환하는 방향으로 옮겨가는 중임