대상: 임시 환경 — GPU 클라우드 파드, CI 러너, 일회용 VM — 에서 SSH로 프로덕션에 닿는 모든 사람. 공격 절차는 없습니다 — 오직 누출 시 피해가 작아지도록 키를 최소 권한으로 만드는 것뿐입니다. 키 분리의 기본은 베이스라인 체크리스트를 보세요.
본 사이트의 견해: 임시 환경을 신뢰할 수 없는 것으로 취급하라
일회용 연산 자원은 흔히 보안 강화를 건너뜁니다 — 침해될 수 있는 곳입니다. 거기에 프로덕션 루트 키를 두면, 그 환경의 침해가 곧 프로덕션의 침해가 됩니다. 본 사이트는 호스트마다 별도 키를 쓰고, 각각을 용도로 범위 짓고, 미사용 즉시 키를 제거합니다(→ 피해를 줄이는 키 분리). 키를 "편리한 수단"이 아니라 "새면 모든 서버에 접근할 수 있게 되는 가장 중요한 자산"으로 다루는 것이 결국 가장 안전한 방법입니다.
✗ 임시 환경의 루트 키
환경 침해 → 그 키로 프로덕션의 루트 → 파일, 시크릿, 다른 서비스까지 닿음 — 전부.
○ 명령 제한 비루트 키
같은 키가 새도, 지정된 그 한 명령만 실행 가능. 셸 없음, 포워딩 없음 — 피해가 한 작업에 갇힘.
임시 환경의 루트 키가 위험한 이유
임시 환경은 보안 강화를 건너뛰는 경향이 있어("곧 지울 거니까") 손쉬운 표적이자 프로덕션 공격의 편리한 중계 지점이 됩니다. 거기의 프로덕션 루트 키는 피해를 최대로 키웁니다.
연산을 위해 파드를 띄우고, 거기서 프로덕션 작업을 하려고 루트의 authorized_keys에 키를 등록한다고 해봅시다. 편리하지만 — 그 파드가 침해되면 공격자가 그 키로 루트로서 프로덕션에 들어옵니다. 공격이 RCE로 시작되든 키 누출로 시작되든, 결과는 같습니다: 탈취된 키로 프로덕션이 넘어감(관련: RCE로 키가 탈취되어 부정 청구됨).
최소 권한 키로 가는 세 단계
"편리하니까 전권을 준다"에서 "할 수 있는 일을 최소로 제한한다"로 바꾸세요.
임시 환경의 루트 키를 제거한다
~/.ssh/authorized_keys에서 삭제하세요. 유휴 상태라면 제거해도 영향이 없습니다. 먼저 쓰지 않는 키를 남겨두지 마세요.다시 필요해도 루트로 돌아가지 않는다
키를 단일 작업으로 제한한다
command="..."와 restrict를 추가하세요. 로그인에 성공해도 지정된 그 한 명령만 실행할 수 있으며, 포트 포워딩과 셸이 꺼집니다. 누출이 그 한 작업에 갇힙니다.# Register a key restricted to ONE operation in a non-root user's authorized_keys.
# Logging in with it can run only the given command; forwarding and PTY are off.
command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@cirestrict는 포워딩, 에이전트 포워딩, PTY, X11 등을 한 번에 비활성화하고; command="..."는 실행 가능한 동작을 하나로 고정합니다. 기본 수는 단일 용도로 범위 지은 키 — 배포 풀만, 특정 스크립트 실행만 — 을 용도별로 만드는 것입니다.
흔한 설정(위험)
- 파드/CI에서 프로덕션 루트에 키 등록
- 끝난 키를 그대로 둠
- 하나의 키를 여러 호스트에 재사용
- 전체 셸을 주는 키를 흩뿌림
최소 권한 설정
- 프로덕션엔 비루트 사용자로만 닿음
- 키는 한 작업으로 제한(명령 제한)
- 호스트별·용도별로 키 분리
- 미사용 즉시 authorized_keys에서 키 제거
재사용 키를 가장 중요한 자산으로 취급하라
키를 "편리한 수단"으로 여기면 재사용하고 삭제를 잊습니다. 대신 그것을 새면 모든 서버에 접근할 수 있게 되는 가장 중요한 자산으로 취급하세요. 호스트별·용도별로 분리하고, 프로덕션 키를 임시 환경에서 빼두고, 사용 후 항상 제거하세요. 그것만으로 한 번의 누출이 전부로 번지는 것을 막습니다.
키 줄에 걸 수 있는 주요 제한
OpenSSH에서는 authorized_keys의 키 줄 앞에 쓰는 옵션으로 그 키가 할 수 있는 일을 좁힙니다. 자주 쓰는 것은 다음과 같습니다.
| 옵션 | 제한하는 것 | 쓰는 경우 |
|---|---|---|
command="..." | 그 키로 로그인하면 지정한 명령만 실행되고, 클라이언트가 보낸 명령은 무시된다 | 배포 풀이나 백업처럼 정해진 한 가지 일만 하는 키 |
restrict | 포트·에이전트·X11 포워딩, PTY 할당, ~/.ssh/rc 실행을 한 번에 끈다(OpenSSH 7.2 이상) | 거의 모든 자동화용 키. 필요한 것만 pty 등으로 다시 켠다 |
from="..." | 키에 더해, 클라이언트의 호스트 이름이나 IP 주소가 목록과 일치해야 한다 | 출발지 IP가 고정된 CI나 작업 서버 |
expiry-time="..." | 지정한 날짜·시각 이후에는 키가 받아들여지지 않는다(OpenSSH 7.7 이상) | 일시적인 작업에만 쓰는 키 |
PermitRootLogin(sshd_config) | root 직접 로그인 허용 여부. forced-commands-only는 command=가 붙은 키에만 root를 허용한다 | 정말로 root 직접 접근이 필요한 경우가 아니면 no |
OpenSSH 릴리스 노트는 restrict를 "현재와 미래의 모든 키 제한"을 포함하는 것으로 설명합니다. no-pty, no-port-forwarding 등을 하나씩 나열하기보다 restrict를 붙이고 필요한 것만 다시 켜면, 새 기능이 생겨도 빠지는 것이 없습니다.
# Also narrowing the source and the lifetime (the IP is a documentation address)
from="192.0.2.10",expiry-time="20261231",command="/usr/local/bin/deploy-pull",restrict ssh-ed25519 AAAA...rest-of-public-key deploy@ci-2026-10여러 키의 기한부 접근을 관리한다면 OpenSSH 인증서도 선택지입니다(ssh-keygen -s로 서명하고 -V로 유효 기간을 정하며, force-command나 source-address로 제한). 소규모라면 키 줄의 옵션만으로 충분히 시작할 수 있습니다.
자신의 환경을 확인하는 방법
아래 확인은 자신이 관리하는 서버의 설정과 로그를 읽기만 합니다. 설정을 바꿀 때는 SSH 세션 하나를 열어 둔 채 다른 터미널에서 새 연결을 시험하고, 잠기지 않았음을 확인한 뒤에 예전 세션을 닫으세요.
1. 등록된 키를 나열한다
# Show the fingerprint and comment of each key registered for root and each user
sudo ssh-keygen -lf /root/.ssh/authorized_keys
sudo ssh-keygen -lf /home/deploy/.ssh/authorized_keys키마다 256 SHA256:... deploy@ci (ED25519) 같은 줄이 하나씩 출력됩니다. 주석(끝의 deploy@ci 부분)을 단서로 출처를 모르는 키, 이미 끝난 임시 환경의 키, root에 등록된 키를 골라내세요. 예전 파일명인 authorized_keys2도 기본적으로 읽히므로, 있다면 같은 방법으로 확인하세요.
2. sshd가 실제로 쓰는 설정을 확인한다
# Check the syntax, then print the settings actually in effect
sudo sshd -t
sudo sshd -T | grep -iE 'permitrootlogin|authorizedkeysfile|pubkeyauthentication|passwordauthentication'sshd -T는 설정 파일을 읽은 뒤 실제로 적용되는 값을 출력합니다. permitrootlogin이 yes나 prohibit-password라면, 키가 있는 사람은 root로 직접 로그인할 수 있습니다. authorizedkeysfile이 기본값이 아닌 곳을 가리킨다면 그 위치도 1번 확인에 추가하세요.
3. 실제로 쓰이는 키를 본다
키 로그인에 성공하면 sshd는 보통 Accepted publickey for deploy from 192.0.2.10 port 50022 ssh2: ED25519 SHA256:... 같은 줄을 남깁니다. Debian과 Ubuntu에서는 /var/log/auth.log, Red Hat 계열에서는 /var/log/secure, systemd 호스트에서는 journalctl -u ssh(Red Hat 계열은 -u sshd)로 읽으세요.
이 지문을 1번의 목록과 대조하면 쓰이지 않는 키가 보입니다. SSH 키 관리에 관한 NIST 보고서(NISTIR 7966)는 드물게 쓰이는 키를 실수로 제거하지 않도록, 미사용 키를 제거하기 전에 대략 6~12개월간 로그를 지켜볼 것을 제안합니다. 임시 환경의 키처럼 끝난 것이 분명한 키는 바로 제거해도 됩니다.
4. 임시 환경 쪽을 확인한다
- 프로덕션에 닿는 개인 키가 CI 시크릿이나 GPU 파드의
~/.ssh에 놓여 있지 않은가? - 프로덕션으로 가는 길에
ssh -A(에이전트 포워딩)로 임시 환경에 접속하고 있지 않은가? - 하나의 키를 여러 CI 시스템이나 서버에서 공유하고 있지 않은가?
흔한 실수와 고치는 법
- 임시 환경 쪽 개인 키만 지우고 끝냄: 개인 키가 이미 복사됐다면, 공개 키가 서버에 남아 있는 한 계속 작동합니다. 반드시 프로덕션
authorized_keys에서 해당 줄을 제거하세요. command=가 호출하는 스크립트 안에서 클라이언트의 명령(SSH_ORIGINAL_COMMAND환경 변수)을 그대로 실행함: 제한이 없는 것과 같습니다. 값은 정해진 선택지 중 하나와 정확히 일치할 때만 쓰세요.- 비루트 사용자로 바꿨지만 sudo를 무제한 허용함: 그 사용자가 비밀번호 없이 무엇이든 sudo할 수 있다면 사실상 root입니다. sudo가 필요하다면 sudoers에서 명령 하나로 제한하세요.
ssh -A로 임시 환경에 들어감: 그곳에서 충분한 권한을 가진 누구든 포워딩된 연결을 통해 로컬 에이전트에 올라간 키를 쓸 수 있습니다(OpenSSH 매뉴얼이 이를 경고합니다). 호스트를 거쳐야 한다면ssh -J(점프 호스트 경유 접속)를 쓰세요.- 주석 없는 키를 남김: 점검할 때 출처를 알 수 없습니다. 만들 때
ssh-keygen -C "deploy@ci-2026-10"처럼 용도와 날짜를 넣으세요. - 하나의 키를 여러 사람이나 환경이 공유함: NISTIR 7966도 개인 키의 공유나 다른 시스템으로의 복사를 금지할 것을 요구합니다. 누가 어디서 썼는지 추적할 수 없고, 한 번 새면 모든 곳에서 교체해야 합니다.
실제 사례: 침해된 CI 도구가 수집한 것에 SSH 키가 포함됐다
2026년 3월, 오픈소스 취약점 스캐너 Trivy의 악성 릴리스가 나타났고 GitHub Actions 태그가 바꿔치기됐습니다. 개발사 Aqua Security에 따르면 악성 코드는 CI 러너에서 자격 증명을 수집해 외부로 보냈으며, 대상에는 ~/.ssh의 개인 키, authorized_keys, known_hosts, SSH 호스트 키가 포함됐습니다. Aqua는 영향받는 버전을 실행했을 가능성이 있는 사람에게, SSH 키를 포함해 그 파이프라인이 읽을 수 있었던 모든 시크릿을 노출된 것으로 보고 즉시 교체하라고 요청합니다(자세히: Trivy 공급망 침해(2026년 3월)).
2021년 Codecov 사례에서도 CI에서 실행되는 스크립트가 배포 원본에서 변조되어, 약 2개월간 고객 CI 환경의 시크릿이 외부로 보내졌습니다(→ Codecov 공급망 공격).
두 사례 모두 자신의 코드에 문제가 없어도 CI 안의 키는 탈취될 수 있음을 보여 줍니다. 탈취된 키가 프로덕션 root에 등록돼 있다면 그 시점에 프로덕션이 넘어갑니다. 전용 비루트 사용자, command=와 restrict, 그리고 from=으로 출발지를 좁혀 두면, 같은 키가 새도 정해 둔 한 가지 일만 할 수 있고 다른 곳에서의 접속은 거부됩니다.
본 사이트는 직접 어떻게 하는가
본 사이트는 서버마다 전용 키를 쓰며 키를 재사용하지 않습니다. 배포는 비루트, 용도 전용 사용자에 떨어지고, 프로덕션은 일방향입니다 — "받기만 하고, 가져오러 나가지 않음"(→ 프로덕션은 받기만). 임시 연산 자원에서 프로덕션으로 가는 상시 키를 남겨두지 않으며, 필요할 때만 최소 권한으로 연결합니다. 이유는 정확히 이 글의 것입니다: 각 키로 할 수 있는 일을 미리 작게 유지해 한 번의 침해가 전부로 번지지 못하게 하는 것.
다음으로 읽기
- 새 취약점: OpenSSH 10.6p2, PAM 없는 musl Linux에서 sshd 비밀번호 인증 우회 수정(CVE-2026-123158)
- 기본: 보안 베이스라인 체크리스트(최소 권한, 관리자 계정 보호)
- 운영: 피해를 줄이는 키 분리 / 프로덕션은 받기만 · 취약점 대응 플레이북
- 사례: Trivy 공급망 침해(CI에서 SSH 키가 노려짐) · Codecov 공급망 공격
- 사고: RCE로 키가 탈취되어 부정 청구됨 · Benesse 개인정보 유출 사건(과도한 권한과 내부 부정)
출처
- OpenBSD 매뉴얼, "sshd(8)", AUTHORIZED_KEYS FILE FORMAT(command, restrict, from, expiry-time, SSH_ORIGINAL_COMMAND): man.openbsd.org
- OpenBSD 매뉴얼, "sshd_config(5)", PermitRootLogin과 AuthorizedKeysFile: man.openbsd.org
- OpenBSD 매뉴얼, "ssh(1)", -A(에이전트 포워딩 주의)와 -J: man.openbsd.org
- OpenBSD 매뉴얼, "ssh-keygen(1)", -l, -C와 인증서 옵션: man.openbsd.org
- OpenSSH 7.2 릴리스 노트(restrict 추가): openssh.org
- OpenSSH 7.7 릴리스 노트(expiry-time 추가): openssh.org
- NIST, "NISTIR 7966: Security of Interactive and Automated Access Management Using Secure Shell (SSH)"(2015년 10월): csrc.nist.gov
- Wikibooks, "OpenSSH/Logging and Troubleshooting"(로그인 성공 로그 줄의 키 지문): en.wikibooks.org
- Aqua Security, "Trivy Supply Chain Attack: What You Need to Know"(2026): aquasec.com
- Codecov, "Bash Uploader Security Update"(2021): about.codecov.io