레넌의 개발 일기
Jenkins Host key verification failed 본문
문제점
Jenkins 파이프라인을 작성하다가 Host key verification failed 문제를 맞이했다.
처음에 작성한 파이프라인은 아래와 같다.
pipeline {
agent any
stages {
stage('Checkout SCM') {
steps {
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], extensions: [[$class: 'SubmoduleOption', disableSubmodules: false, parentCredentials: true, recursiveSubmodules: true, reference: '', trackingSubmodules: true]], userRemoteConfigs: [[credentialsId: 'github_token', url: 'https://github.com/woowacourse-teams/2022-gong-seek.git']]])
}
}
stage('Build and Test') {
steps {
dir('backend') {
sh '''
./gradlew clean bootjar
'''
}
}
}
stage('Deploy') {
steps {
dir('backend/build/libs') {
sshagent(['gongseek-backend-dev']) {
sh 'scp gongseek-0.0.1-SNAPSHOT.jar ubuntu@${backend_dev_ip}:/home/ubuntu/'
sh 'ssh ubuntu@${backend_dev_ip} "sh deploy.sh" &'
}
}
}
}
}
}
아래는 문제가 발생한 젠킨스 파이프라인의 Console Output 이다.
위의 콘솔을 보면 scp 명령어로 빌드한 결과물인 jar파일을 젠킨스 서버에서 백엔드 서버로 전달해줄 때 문제가 발생했다는 것을 알 수 있다. ssh명령어와 scp명령어를 젠킨스에서 사용하기 위해서는 젠킨스 서버에서 백엔드 서버로 pem키를 통한 접속을 최초 1번 해주어야한다. 처음에는 이 작업을 왜 해야하는지 몰랐다. 단순히 '서버끼리의 통신을 하려면 최초 1번은 서로의 길을 뚫어줘야하는구나'라고만 이해하고 있었다. pem키를 통해 서버에 접속하면 어떤일이 일어나는지 알아보도록 하자.
SSH 동작 원리
그전에 AWS에서의 SSH 동작원리를 알아보도록 하자.
SSH는 Secure Shell Protocol의 줄임말로 Application Layer에 해당한다.
SSH에서는 대칭키 방식과 비대칭키 방식을 모두 사용하여 인증과 암호화를 하게 된다. 비대칭키를 통해서 서버 인증과 사용자 인증을 하고, 대칭키(세션키)를 통해 데이터를 암호화 한다.
기존 원격 접속은 ‘텔넷(Telnet)’이라는 방식을 사용했는데, 암호화를 제공하지 않기 때문에 보안상 취약하다는 단점이 있었다. 이를 해결하기 위해 SSH 기술이 등장했고, 현재 원격 접속 보안을 위한 필수적인 요소로 자리잡고 있다. 그리고 클라우드 서비스에서 제공하는 서버는 기본적으로 원격 접속을 통해 접근하고 사용한다.
– 비대칭키 방식
가장 먼저 사용자와 서버가 서로의 정체를 증명해야 한다. 보통 공개 키의 경우 .pub, 개인 키의 경우 .pem의 파일 형식을 띄고 있다. 사용자가 키 페어를 생성하면, 인스턴스에 퍼블릭 키를 저장(.ssh/authorized_keys에 저장)하고 사용자는 프라이빗 키를 저장한다. 서버는 공개키를 받아서 공개키로 암호화된 랜덤한 값을 생성한다. 이 값은 사용자가 올바른 키 페어를 가지고 있는지 확인하는 과정이다. 메시지를 받은 사용자는 가지고 있는 개인 키를 이용해 암호화를 푼다. 암호화를 풀어서 나온 값을 사용자는 다시 서버에 전송한다. 서버는 사용자로부터 전송받은 값을 자신이 처음에 낸 값과 비교한다. 두 값이 같게 되면 서버는 인증된 사용자라고 판단하고 접속을 허용해준다. 이렇게 최초 접속 시 사용자와 서버 간의 인증 절차가 비대칭키 방식을 통해 완료된다.
최초로 SSH를 통해 서버에 접속하게 되면 클라이언트의 .ssh/known_hosts에 서버의 공개키가 저장된다. 서버에 대한 식별과 중간자 공격을 막기위해 known_hosts에 저장한다. 누군가가 known_hosts에 저장된 publickey를 다른 서버로 변경한다면 man-in-the-middle-atack 공격이 식별되었음을 알 수 있다. 해킹으로 인해 호스트키가 변경되었는지 확인하여 보안을 높인다.
– 대칭키 방식
비대칭키 방식으로 접속이 완료되었을 때, 내부적으로 세션키(대칭키)를 생성하여 서로 주고받게 되고, 세션 키를 사용하여 데이터를 암호화해서 통신하게 된다. 데이터 교환이 완료되면 교환 당시 썼던 대칭 키는 폐기되고, 나중에 다시 접속할 때마다 새로운 대칭 키를 생성하여 사용하게 된다.
다시 문제 상황으로 돌아와서,
젠킨스 서버에서 백엔드 서버로의 SSH 접속을 최초 1번 진행해주었지만, 파이프라인에서 해당 문제는 해결되지 않았다. .ssh/known_hosts에 저장된 호스트 키가 수정되었거나 꼬였다고 생각되어 해당 파일을 제거해준 후, 다시 접속을 진행해주었는데도 똑같았다..
어떻게 해결?
Host Key Checking
- ssh를 이용해 원격에 있는 서버에 접근할 때 해당 서버에 대한 인증을 위해 로컬에 저장해둔 키와 서버의 키를 비교하는 작업을 진행한다.
- 이런 작업을 통해 중간자 공격을 막을 수 있고, 서버를 인증할 수 있다.
우리팀이 선택한 방법은 Host Key Checking을 비활성화 하는 것이다.
stage('Deploy') {
steps {
dir('backend/build/libs') {
sshagent(['gongseek-backend-dev']) {
sh 'scp -o StrictHostKeyChecking=no gongseek-0.0.1-SNAPSHOT.jar ubuntu@${backend_dev_ip}:/home/ubuntu/'
sh 'ssh -o StrictHostKeyChecking=no ubuntu@${backend_dev_ip} "sh deploy.sh" &'
}
}
}
}
그래서 수정한 Deploy Stage는 위와 같다. 정확한 원인은 알 수 없지만, known_hosts로 인해 scp와 ssh 명령어가 제대로 수행되지 않아 어쩔 수 없이 -o StrictHostKeyChecking=no 옵션을 부여한 부분도 있다. 하지만, 해당 옵션을 줘도 문제가 없다고 생각했다. 기본적으로 젠킨스와 백엔드 서버 사이에서 private ip로 통신을 하고 있기 때문에, 같은 VPC 내에서는 중간자 공격이 일어나기 힘들다고 판단했기 때문이다.
마치며
이 문제 때문에 삽질을 엄청많이 했다.
명확한 원인을 알지 못해서 아쉽긴 하지만 이 문제 덕분에 조금이나마 공부를 할 수 있어서 좋았다.
참고
'DevOps' 카테고리의 다른 글
Github Actions - Gradle 캐싱을 통한 CI 속도 향상 (0) | 2022.11.10 |
---|---|
Github Actions 알아보기 (0) | 2022.11.10 |
Jenkins Pipeline 구축하기 - 프론트엔드편 (0) | 2022.10.26 |
Swap Memory로 메모리 문제 해결하기 (0) | 2022.10.26 |
Jenkins Pipeline 구축하기 - 백엔드편 (0) | 2022.08.11 |