GitLab CI/CD를 위한 SSH Key 생성 및 배포 환경 구성

“자꾸 패스워드를 물어봅니다” GitLab CI/CD 배포 환경 구성

“자꾸 패스워드를 물어봅니다” GitLab CI/CD 배포 환경 구성

GitLab CI/CD를 위한 SSH Key 생성 및 배포 환경 구성
GitLab CI/CD를 위한 SSH Key 생성 및 배포 환경 구성

GitLab CI/CD와 SSH Key로 구축하는 자동화 배포 환경 (삽질 방지 트러블슈팅 포함)

얼마 전 진행 중인 프로젝트에서 애플리케이션 아키텍트(AA) 분에게 급한 요청이 하나 들어왔습니다. 개발, QA, 운영 서버로 이어지는 CI/CD 배포 파이프라인을 구축해 달라는 업무였죠.

사실 요즘 같은 DevOps 시대에 서버에 일일이 접속해서 git pull을 댕기거나 빌드 파일을 수동으로 올리는 건 상상하기 힘듭니다. 특히 보안이 까다로운 프로젝트일수록 비밀번호 없이 안전하게 인증하는 비밀번호 없는 인증(Passwordless Authentication)은 선택이 아닌 필수입니다.

오늘은 제가 Docker 기반의 GitLab Runner를 활용해 각 서버에 안전하게 코드를 배포하기까지, 직접 맨땅에 헤딩하며 구축한 SSH Key 관리 전략과 구성 방법을 공유해 보려고 합니다. 저와 같은 시행착오를 겪지 않도록 실무에서 바로 쓸 수 있는 스크립트와 트러블슈팅 경험까지 꾹꾹 눌러 담았습니다.

왜 귀찮게 SSH Key 방식을 써야 할까?

가장 직관적인 방법은 서버 ID와 비밀번호를 스크립트에 적어두는 것입니다. 하지만 이 방식은 몇 가지 치명적인 문제가 있습니다.

  • 무차별 대입 공격(Brute-force)의 타겟: 서버에 비밀번호 로그인 기능이 열려 있는 것만으로도 전 세계 해커들의 공격 대상이 됩니다. 실제로 로그를 보면 초 단위로 접속 시도가 들어오는 것을 볼 수 있습니다.
  • 파이프라인 자동화의 한계: 배포가 실행될 때마다 사람이 개입할 수 없으므로, 비밀번호를 어딘가 평문으로 저장해야 하는 찜찜함이 남습니다.

반면 SSH 키 쌍(Public/Private Key)을 이용하면, 물리적인 비밀키 파일 없이는 서버 접속 자체가 불가능해집니다. 게다가 GitLab의 CI/CD Variables 기능을 활용하면 서버의 민감한 정보를 외부로 노출하지 않고 안전하게 배포 경로를 제어할 수 있습니다.

1단계: 배포용 SSH 키 쌍 생성하기 (Ed25519 권장)

가장 먼저 배포의 핵심이 될 SSH 키를 생성해야 합니다. 예전에는 RSA 알고리즘을 많이 썼지만, 최근에는 연산 속도가 빠르고 보안성이 더 우수한 Ed25519 알고리즘 사용을 강력히 권장합니다.

터미널을 열고 아래 명령어를 입력합니다. 제 경우 관리의 편의성을 위해 파일명을 id_ed25519_gitlab으로 명시해 주었습니다.

Bash

# 특정 환경과 관리를 위한 키 생성
ssh-keygen -t ed25519 -C "gitlab-ci-deployment" -f ~/.ssh/id_ed25519_gitlab

명령어를 실행하면 두 개의 파일이 생깁니다.

  1. Private Key (비밀키): 절대 외부에 노출되면 안 되는 핵심 키입니다. 이 내용은 통째로 복사해서 GitLab의 CI/CD 변수에 저장할 겁니다.
  2. Public Key (공개키): 우리가 배포할 대상 서버(Dev, QA, Prod)에 널리 배포할 키입니다.

2단계: 환경별 배포 전략과 GitLab 설정

보안과 안정성을 확보하려면 개발(Dev), QA, 운영(Prod) 서버의 권한이 철저히 분리되어야 합니다. 아무리 급해도 개발 서버 배포 키로 운영 서버까지 접근할 수 있게 만들면 안 됩니다.

서버측 설정 (Public Key 등록)

배포 대상 서버에 접속한 뒤, 방금 생성한 공개키(Public Key) 내용을 복사해서 서버의 authorized_keys 파일에 등록합니다. 보안을 위해 권한 설정도 반드시 600으로 조여주어야 합니다.

Bash

# 서버의 authorized_keys에 공개키 추가
echo "ssh-ed25519 AAAAC3Nza..." >> ~/.ssh/authorized_keys

# 권한을 엄격하게 제한 (매우 중요!)
chmod 600 ~/.ssh/authorized_keys

GitLab CI/CD Variables 설정

이제 GitLab 웹 콘솔로 이동합니다. [Settings] > [CI/CD] > [Variables] 메뉴로 들어가서 배포에 필요한 변수들을 등록해 줍니다.

변수명설명보안 설정 (추천)
SSH_PRIVATE_KEY생성한 Private Key 내용 전체Masked & Protected 체크
SERVER_IP_DEV개발 서버 IP 주소일반 설정
SERVER_IP_PROD운영 서버 IP 주소Protected 체크

💡 여기서 꿀팁! SSH_PRIVATE_KEY를 등록할 때 반드시 Masked 옵션을 켜주세요. 그래야 혹시 모를 스크립트 에러로 인해 오픈된 빌드 로그에 비밀키가 평문으로 찍히는 대참사를 막을 수 있습니다.

3단계: .gitlab-ci.yml 구성 (실전 스크립트)

Docker 기반의 GitLab Runner에서 배포가 시작될 때, 메모리상에 SSH 에이전트를 띄우고 변수로 받아온 비밀키를 주입하는 과정입니다. 아래는 제가 실제 프로젝트에 적용한 파일의 일부입니다.

YAML

before_script:
  # ssh-agent가 설치되어 있는지 확인하고 없으면 설치합니다.
  - 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
  # SSH 에이전트를 백그라운드에 실행합니다.
  - eval $(ssh-agent -s)
  # GitLab 변수에 저장된 Private Key를 에이전트에 등록합니다.
  - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
  # SSH 디렉토리를 만들고 권한을 설정합니다.
  - mkdir -p ~/.ssh
  - chmod 700 ~/.ssh
  # 서버 변조 공격을 막기 위해 대상 서버의 SSH 호스트 키를 신뢰할 수 있는 목록에 추가합니다.
  - ssh-keyscan $SERVER_IP_PROD >> ~/.ssh/known_hosts

deploy_prod:
  stage: deploy
  script:
    # root가 아닌 일반 배포 계정(webadmin)으로 접속하여 무중단 배포 명령을 내립니다.
    - ssh webadmin@$SERVER_IP_PROD "cd /var/www/app && git pull origin main && docker-compose up -d --build"
  only:
    - main
  environment:
    name: production

🛠️ 구조로 이해하는 데이터의 흐름

이 배포 파이프라인이 돌아갈 때 내부 아키텍처는 꽤나 유기적으로 움직입니다.

  1. 개발자가 main 브랜치에 코드를 푸시하면 파이프라인이 켜집니다.
  2. GitLab 서버가 격리된 환경인 GitLab Runner(Docker)에게 “너 지금 배포해”라며 암호화된 SSH_PRIVATE_KEY를 슥 던져줍니다.
  3. Runner는 이 키를 하드디스크가 아닌 메모리(RAM)에만 잠깐 올린 뒤, 대상 서버의 일반 계정(webadmin)으로 SSH 접속을 시도합니다.
  4. 배포 명령(docker-compose up)이 무사히 끝나면 Runner 컨테이너는 통째로 파괴됩니다. 메모리에 있던 비밀키 흔적도 완벽하게 사라지므로 물리 서버에 키 파일을 저장하는 것보다 훨씬 안전합니다.
문제 해결 방법
문제 해결 방법

🚨 트러블슈팅: “자꾸 패스워드를 물어봅니다!”

설정을 다 끝내고 신나게 깃랩 파이프라인을 돌렸는데, 로그창에 Password:라는 글자만 뜬 채 멈춰버리는 경우가 있습니다. 자동화 배포에서 비밀번호 입력 창이 뜬다는 건 그냥 배포 실패를 의미하죠. 제가 이 문제로 반나절을 날리며 알아낸 3가지 원인과 해결책입니다.

1. 서버 디렉토리 권한이 너무 혜자(?)스러울 때

OpenSSH 시스템은 보안에 결벽증이 있습니다. 키 파일이나 상위 디렉토리 권한이 너무 느슨하면, “이 서버는 위험하다”고 판단해 키 인증을 아예 무시하고 차선책인 패스워드 인증을 요구합니다. 배포 대상 서버에 들어가서 다음 권한을 칼같이 맞춰주세요.

Bash

# 1. 유저 홈 디렉토리 권한 체크 (그룹이나 타인에게 쓰기 권한이 있으면 안 됩니다)
chmod 755 /home/webadmin

# 2. .ssh 디렉토리는 소유자만 접근 가능하게
chmod 700 /home/webadmin/.ssh

# 3. authorized_keys 파일은 오직 읽고 쓰기만 가능하게
chmod 600 /home/webadmin/.ssh/authorized_keys

2. 키 쌍이 어긋났거나 엉뚱한 키를 바라볼 때

이것저것 테스트하느라 키를 여러 개 만들다 보면 깃랩에 등록한 Private Key와 서버에 넣은 Public Key가 짝이 안 맞는 경우가 생깁니다. 혹은 템플릿에 지정된 기본 키 경로(~/.ssh/id_rsa)와 내가 생성한 파일명이 다를 때 발생합니다. 이럴 때는 배포 스크립트에서 명시적으로 -i 옵션을 주어 키 경로를 지정하는 것이 안전합니다.

Bash

ssh -i ~/.ssh/id_ed25519_gitlab webadmin@$SERVER_IP_PROD "echo '접속 성공!'"

3. 서버 SSH 설정에서 키 인증이 꺼져 있을 때

가끔 보안이 극도로 높은 공공기관 인프라나 구형 서버의 경우, 애초에 공개키 인증 방식 자체가 비활성화되어 있는 황당한 경우가 있습니다. 서버의 /etc/ssh/sshd_config 파일을 열어 아래 옵션을 확인해 보세요.

Plaintext

# 이 부분이 no나 주석(#) 처리가 되어 있다면 yes로 변경해야 합니다.
PubkeyAuthentication yes

설정을 바꿨다면 주저 없이 SSH 서비스를 재시작해 줍니다.

Bash

sudo systemctl restart sshd

🔐 실무자를 위한 최종 보안 권장사항

안전한 DevOps 환경을 위해 제 경험을 바탕으로 3가지 철칙을 제안합니다.

  • 최소 권한 원칙(root 계정 사용 금지): 배포 편하자고 root 계정으로 SSH 접속하게 만들면 안 됩니다. 스크립트 실수 한 번에 서버 전체가 날아갈 수 있습니다. 반드시 webadmin이나 deployer 같은 일반 계정을 파서 서비스 디렉토리에만 쓰기 권한을 주어야 합니다.
  • 로컬의 개인키 흔적 지우기: GitLab Variables에 개인키 등록이 끝났다면, 키를 생성했던 로컬 PC나 임시 서버의 .ssh 폴더에서 개인키 파일(id_ed25519_gitlab)은 즉시 지우세요. 깃랩 서버가 안전하게 보관해 주니 로컬에 남겨둘 이유가 없습니다. 파일이 존재하지 않아야 유출도 안 됩니다.
  • 정기적인 키 로테이션: 아무리 철통 보안을 자랑해도 관리자 부주의로 키가 샐 수 있습니다. 저희 팀은 최소 1년 단위로 SSH 키를 재생성해서 교체하는 프로세스를 지키고 있습니다.

마치며

GitLab CI/CD를 통한 배포 자동화는 개발 생산성을 엄청나게 끌어올려 줍니다. 하지만 그 기반이 되는 SSH 인증 체계가 허술하다면, 기업의 가장 소중한 자산인 소스 코드와 인프라가 통째로 위협받게 됩니다.

오늘 정리한 내용들을 바탕으로 지금 운영 중인 파이프라인의 보안 설정을 다시 한번 점검해 보시는 건 어떨까요? 안전한 자동화가 곧 비즈니스의 신뢰를 만듭니다.

전문가의 인사이트 : 함께 읽어 보세요.

[RHEL 9.6: Nginx – Tomcat 리버스 프록시 구성]

참조 및 출처 URL:

https://docs.gitlab.com/ci/jobs/ssh_keys