SSH Google Authenticator 2FA 적용 후 GitHub Actions 배포가 막혔을 때 — 전용 배포 사용자로 해결하기
보안을 강화했더니 CI/CD 파이프라인이 망가졌습니다.
ssh: handshake failed: unable to authenticate에러를 마주한 개발자라면 이 글이 도움이 될 겁니다.
보안도 지키고 자동 배포도 유지하는 현실적인 해결법을 정리했습니다.
🚨 문제점 — 2FA가 자동화를 막아버렸다
지난 글에서 EC2 서버에 Google Authenticator 기반 SSH 2차 인증을 적용했습니다. .pem 키가 유출되어도 OTP 없이는 접속 불가능한, 아주 튼튼한 방어막이 생겼죠.
그런데 다음날 아침, 코드를 푸시하자 GitHub Actions에서 이런 에러가 뜨기 시작했습니다:
2026/04/16 12:24:02 error copy file to dest: ***, error message:
ssh: handshake failed: ssh: unable to authenticate,
attempted methods [none publickey], no supported methods remain
drone-scp error: error copy file to dest: ***, error message:
ssh: handshake failed: ssh: unable to authenticate,
attempted methods [none publickey], no supported methods remain
배포 파이프라인이 완전히 막혔습니다.
왜 이런 일이 발생했을까?
지난 글에서 sshd_config에 아래 설정을 넣었습니다:
AuthenticationMethods publickey,keyboard-interactive
이 설정은 "모든 SSH 접속에 .pem 키 AND OTP를 요구"합니다. 쉼표가 AND의 의미라 둘 다 통과해야 접속이 허용되죠.
사람이 직접 접속할 때는 문제 없습니다. 핸드폰 꺼내서 OTP 6자리 입력하면 되니까요. 하지만 GitHub Actions는 사람이 아닙니다. 자동화된 스크립트는:
- 핸드폰을 가지고 있지 않고
- OTP 앱을 열 수 없고
- 6자리 코드를 입력할 방법이 없습니다
에러 메시지를 해석하면 이해가 쉽습니다: attempted methods [none publickey]: GitHub Actions가 공개키로만 시도함no supported methods remain: 서버가 keyboard-interactive(OTP 단계)도 요구하는데 GitHub Actions가 그걸 처리할 수 없어서 포기- 보안을 올렸더니 자동화가 끊긴 고전적인 딜레마*입니다.
🛡️ 해결 방법 — 전용 배포 사용자 + 제한된 권한
올바른 해결책의 핵심 아이디어는:
"사람용 계정은 강한 2FA를 유지하고, 자동화용 계정은 제한된 권한으로만 동작하게 한다"
즉, ubuntu 같은 일반 사용자는 2FA 필수 유지, 새로 만든 deploy 전용 사용자만 .pem 키로 통과시키되 그 사용자가 할 수 있는 일을 배포 작업에만 제한하는 것입니다.
[ubuntu 계정] — 개발자 SSH 접속용 — .pem + OTP 필수 (기존 유지)
[deploy 계정] — GitHub Actions 전용 — .pem만 허용, 하지만 권한은 배포에만 한정
이렇게 하면:
- GitHub Actions는 OTP 없이 배포 가능 ✅
- 사람의 접속은 여전히 2FA 보호 ✅
- deploy 키가 유출되어도 배포 작업 외의 침해는 불가능 ✅Step 1 — 배포 전용 사용자 생성
# 비밀번호 없이 deploy 사용자 생성
sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
--disabled-password 옵션으로 비밀번호 로그인 자체를 차단합니다. 이 사용자는 SSH 키로만 접속 가능합니다.
Step 2 — GitHub Actions 전용 SSH 키 생성
기존 .pem 키와 분리된 새 키를 만드는 게 중요합니다. 한 키가 여러 용도에 쓰이면 유출 시 피해가 커지니까요.
로컬 PC에서:
ssh-keygen -t ed25519 -f deploy_key -C "github-actions-deploy" -N ""
-t ed25519: 최신 타원곡선 알고리즘 (RSA보다 짧고 안전)-N "": passphrase 없음 (자동화용이라 필요)
실행하면 두 파일이 생성됩니다:deploy_key— 개인키 (GitHub Secrets에 저장할 것)deploy_key.pub— 공개키 (서버에 등록할 것)Step 3 — 공개키를 서버에 등록
# 서버에서
sudo nano /home/deploy/.ssh/authorized_keys
deploy_key.pub 내용을 복사해서 붙여넣습니다. 저장 후 권한 설정:
sudo chmod 600 /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh
권한이 중요합니다. SSH는 authorized_keys의 권한이 너무 열려 있으면 보안상 키를 무시해버립니다. 600과 소유자 deploy가 정확해야 합니다.
Step 4 — PAM에서 deploy 사용자만 OTP 예외 처리
sudo nano /etc/pam.d/sshd
맨 위의 설정을 다음과 같이 수정합니다:
# 기존 (전체 사용자 OTP 요구)
# auth required pam_google_authenticator.so nullok
# 변경 — deploy 사용자는 OTP 건너뛰기
auth [success=1 default=ignore] pam_succeed_if.so user = deploy
auth required pam_google_authenticator.so nullok
pam_succeed_if.so가 "현재 사용자가 deploy라면 다음 한 줄을 건너뛰어라"라는 의미입니다. 즉 deploy 사용자만 OTP 단계를 생략하게 됩니다.
Step 5 — SSH 설정에서 deploy 사용자만 인증 방식 완화
sudo nano /etc/ssh/sshd_config.d/99-2fa.conf
내용:
# 기본: 모두 publickey + OTP
UsePAM yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
PasswordAuthentication no
PermitRootLogin no
# deploy 사용자만 publickey만으로 통과
Match User deploy
AuthenticationMethods publickey
PasswordAuthentication no
주의: Match User 블록은 파일의 가장 마지막에 있어야 합니다. 그 아래에 다른 전역 설정이 또 있으면 그것까지 deploy 전용 설정으로 잡아먹혀 버립니다.
Step 6 — deploy 사용자의 권한 최소화 ⭐ 가장 중요
여기가 이 방법의 핵심입니다. deploy 사용자가 접속은 가능하지만, 배포 작업 외에는 아무것도 못 하게 만들어야 합니다.
먼저 배포 대상 디렉터리의 소유권을 deploy에게 부여:
sudo chown -R deploy:deploy /opt/app
그리고 앱 재시작에만 필요한 명령을 sudo 없이 실행할 수 있게 합니다. 개별 명령마다 sudoers에 등록하는 건 문법 에러도 자주 나고 관리도 힘드니, 배포용 스크립트 하나로 묶는 방식이 깔끔합니다.
배포 스크립트 작성:
sudo nano /opt/app/restart-app.sh
내용:
#!/bin/bash
set -e
echo ">>> 기존 프로세스 종료"
PID=$(/usr/bin/lsof -t -i:8080 || true)
if [ -n "$PID" ]; then
/bin/kill -15 $PID
sleep 5
if /bin/kill -0 $PID 2>/dev/null; then
/bin/kill -9 $PID
fi
sleep 2
fi
echo ">>> 애플리케이션 재시작"
/bin/systemctl restart myapp
/bin/systemctl status myapp --no-pager
소유자를 root로 고정하는 것이 중요합니다 (deploy가 수정하면 권한 탈취 가능):
sudo chmod 755 /opt/app/restart-app.sh
sudo chown root:root /opt/app/restart-app.sh
그리고 이 스크립트 하나만 sudo 예외 처리:
sudo visudo -f /etc/sudoers.d/deploy
내용:
deploy ALL=(ALL) NOPASSWD: /opt/app/restart-app.sh
visudo는 저장 시 문법 검사를 자동으로 해줍니다. 에러가 나면 저장을 거부해서 시스템이 망가지는 걸 방지합니다.
Step 7 — 설정 검증
# SSH 설정 문법 검사
sudo sshd -t
# deploy 사용자에게 어떤 인증 방식이 요구되는지 확인
sudo sshd -T -C user=deploy | grep -i authenticationmethods
# → "authenticationmethods publickey" 가 나와야 정상
# ubuntu 사용자는 여전히 2FA 요구하는지 확인
sudo sshd -T -C user=ubuntu | grep -i authenticationmethods
# → "authenticationmethods publickey,keyboard-interactive" 가 나와야 정상
두 사용자의 인증 요구사항이 다르게 나오면 설정이 제대로 적용된 겁니다.
Step 8 — SSH 재시작 및 로컬 테스트
⚠️ 반드시 기존 SSH 세션을 열어둔 채로 재시작하세요.
sudo systemctl restart ssh
새 터미널에서 deploy 사용자로 로컬 테스트:
ssh -i deploy_key deploy@{서버주소}
OTP 입력 없이 바로 deploy 쉘로 들어가면 성공입니다. 이걸 확인해야 GitHub Actions도 작동합니다.
만약 막히면 -v 옵션으로 디버깅:
ssh -i deploy_key -v deploy@{서버주소}
Step 9 — GitHub Secrets 교체
리포지토리의 Settings → Secrets and variables → Actions 에서:
| Secret 이름 | 값 |
|---|---|
EC2_USER |
deploy (기존: ubuntu) |
EC2_SSH_PRIVATE_KEY |
deploy_key 파일 전체 내용 |
개인키 복사 시 주의사항:
-----BEGIN OPENSSH PRIVATE KEY-----부터-----END OPENSSH PRIVATE KEY-----까지- 모든 줄바꿈 유지한 채 전체 복사Step 10 — 배포 스크립트 수정
deploy.sh (혹은 .github/workflows/*.yml) 에서 재시작 부분을 수정:
# JAR 파일 교체 (deploy가 /opt/app 소유자라 sudo 불필요)
cp /tmp/app.jar /opt/app/app.jar
# 애플리케이션 재시작 (sudo로 스크립트 실행)
sudo /opt/app/restart-app.sh
그리고 로컬의 deploy_key 파일은 GitHub Secrets 등록 후 즉시 삭제하세요. 어디에도 남겨두지 마세요.
⚠️ 주의사항 — 꼭 챙길 것들
1. 설정 변경 전 기존 세션 유지
SSH 설정을 잘못 저장하면 새 접속이 막힙니다. 작업 중인 터미널은 절대 닫지 말고, 새 터미널로 검증한 뒤에 닫으세요.
2. Match User 블록의 위치
sshd_config에서 Match 블록 이후의 설정은 모두 해당 조건에 속해버립니다. 반드시 파일의 맨 마지막에 배치하세요.
3. 배포 스크립트 소유자는 root
/opt/app/restart-app.sh의 소유자가 deploy면 공격자가 스크립트 내용을 조작해서 root 권한으로 임의 명령을 실행할 수 있습니다. 반드시 root:root 소유여야 합니다.
ls -la /opt/app/restart-app.sh
# -rwxr-xr-x 1 root root ... restart-app.sh ← 이렇게 나와야 함
4. deploy 키는 한 번만 로컬에 존재
deploy_key를 GitHub Secrets에 등록한 후 로컬 파일은 즉시 삭제하세요. 나중에 다시 필요하면 새로 생성하는 게 낫습니다. 키 복사본이 여러 곳에 남을수록 유출 위험이 커집니다.
📝 최종 정리
적용 전후 변화
| 항목 | 적용 전 | 적용 후 |
|---|---|---|
| SSH 2FA | 전체 사용자 적용, CI/CD 막힘 | 사람은 2FA, 자동화는 제한 권한 |
| GitHub Actions 배포 | ❌ 실패 | ✅ 정상 |
| 개발자 접속 보안 | ✅ 2FA 보호 | ✅ 2FA 보호 (변화 없음) |
| 배포 키 유출 시 피해 | (기존: 전체 서버 침해) | 앱 재시작 범위로 제한 |
| 최소 권한 원칙 | ❌ | ✅ |
🏷️ 태그: #AWS #EC2 #SSH #GitHubActions #CICD #2FA #보안 #DevOps #최소권한원칙 #PAM #sudoers
'트레이드마인 > 보안' 카테고리의 다른 글
| JWT 시크릿키가 유출되면 생기는 일들 (0) | 2026.04.16 |
|---|---|
| EC2 서버에 Google Authenticator로 SSH 2차 인증 구축하기 — .pem 키 탈취 방어 실전 (0) | 2026.04.16 |