글 작성자: beaniejoy

Spring Boot 애플리케이션을 개발하다보면 민감한 정보들을 설정해야할 때가 있습니다.
DB 연동시 필수적으로 입력해야 하는 jdbc url, username, password 정보도 있고 Security 인증 관련해서 토큰 발급을 위한 secret_key도 있을 수 있습니다.

이러한 민감 정보들을 Spring 프로젝트에서 application.yml 파일에 작성해놓고 github origin repository에 push하는 순간 외부에 DB 서버 접근 정보를 노출하게 되는 심각한 상황에 마주하게 될 것입니다.

 

📌 0. Vault를 도입하게 된 직접적인 계기...

Spring Boot를 이용한 개인 애플리케이션 개발을 진행하면서 Jenkins를 통해 CI/CD 자동화 프로세스를 적용해보았는데요. 그 과정에서 DB 정보나, 민감 데이터에 대한 처리가 상당히 번거로웠습니다.

위에서 언급한 DB 정보 설정을 예시로 보겠습니다.

Jenkins pipeline step들 중 DB 설정 내용과 관련있는 step만 보면 다음과 같습니다.

> Init
  - 이 때 Jenkins에 설정된 DB Connection Credential 정보를 가져와 username, password을 가져옵니다.

> DB Validate
  - flyway migration을 통해 DB에 이미 적용된 flyway version 정보들과 비교해 최신화된 상태인지 체크
  - DATABASE_HOST 정보는 Jenkins 환경 설정에서 환경변수로 따로 등록한 내용을 가져옵니다.
  - username, password 데이터 적용

...

> Run Application with Ansible
  - ansible을 이용해 실제 애플리케이션을 배포 및 실행하는 단계입니다.
  - 여기서도 DB 정보들이 필요하기 때문에 extraVars를 이용해 ansible로 DB Connection 정보들을 전달합니다.

Jenkins pipeline script로 살펴보면 다음과 같습니다.

stage('Init') {
    steps {
        script {
            //...

            // DB Connection Credential
            DB_CONNECTION_CREDENTIAL = 'b873f9bf-03cc-4daf-be4f-7e00194aa2a0'
            withCredentials([
                    usernamePassword(
                            credentialsId: "${DB_CONNECTION_CREDENTIAL}",
                            usernameVariable: 'username',
                            passwordVariable: 'password'
                    )
            ]) {
                DATABASE_USERNAME = "${username}"
                DATABASE_PASSWORD = "${password}"
            }

            //...
        }
    }
}

stage('DB Validate') {
    steps {
        flywayrunner installationName: 'flywaytool-jenkins',
                flywayCommand: 'info validate',
                commandLineArgs: "-configFiles=${MIGRATION_WORKSPACE}/flyway-${PROJECT_PROFILE}.conf",
                credentialsId: "${DB_CONNECTION_CREDENTIAL}",
                url: "jdbc:mysql://${DATABASE_HOST}:3306/dongne",
                locations: "filesystem:${MIGRATION_WORKSPACE}/migration"
    }
}

//...

stage('Run Application with Ansible') {
    steps {
        ansiblePlaybook inventory: "${ANSIBLE_INVENTORY}",
                playbook: "${ANSIBLE_PLAYBOOK}",
                extraVars: [
                		//...
                        database_host: "${DATABASE_HOST}",
                        database_username: "${DATABASE_USERNAME}",
                        database_password: "${DATABASE_PASSWORD}"
                ]
    }
}

 

DB 연결정보를 필요로 하는 곳을 정리해보면 다음과 같습니다.

  • DB Validate시 flyway 설정
  • ansible에 외부 변수로 전달
  • ansible 내에서 전달받은 DB Connection 정보를 가지고 애플리케이션 실행시 필요

그런데 위와 같은 방식은 secret 정보들에 대한 관리가 어려워진다는 단점이 존재합니다.

DB 연결정보의 변경이 발생한다면 수정해야할 곳들이 여러 군데 발생하게 됩니다. Jenkins에 들어가서 환경변수 내용을 건드려야하고요. credential 내용도 수정해야할 것 입니다.

또한 DB 연결정보 뿐만 아니라 인증토큰, 모니터링 채널정보 등 외부에는 공개가 되면 안되는 secret 정보들을 추가로 적용하려면 이곳 저곳에 덕지덕지 secret 값들을 붙여야 할 것 입니다.
이러한 secret 정보들을 보안 안정성과 편리한 관리, 두 마리 토끼를 잡을 수 있는 방법이 있을까요?

여기서 vault를 주목하게 됩니다.

 

📌 1. Vault의 개념? 보다는 바로 설치해보자

vault란 무엇일까는 한 번 공식사이트(튜토리얼)나 다른 블로그를 참고하실 것을 추천드립니다. 왜냐하면 저는 vault를 단순히 제 개인 프로젝트의 secret 정보들을 편리하게 관리하기 위해서 적용해보기만 한 것이지 이것에 대한 정확한 개념이나 사용법을 알지 못합니다.

 

Tutorials | Vault | HashiCorp Developer

Explore Vault product documentation, tutorials, and examples.

developer.hashicorp.com

그럼에도 vault에 대한 블로그를 왜 작성했냐라고 의문을 가지실 수 있을텐데요. 단순히 개발하는데 있어서 이러한 불편함이 있었고 이렇게 해결해보았습니다를 저도 다시 참고할 수 있고 다른 사람들에게도 공유할 수 있고 추후에 혹여나 면접을 보게 될 때 고민의 발자취를 남기고자 글을 작성하게 되었습니다.

그래서 이번 글도 보실 때 "아, 저 사람은 개인적으로 애플리케이션 개발할 때 이런 불편함과 고민들이 있었고 이런 방식으로도 해결을 해보았구나"라고 귀엽게 봐주시면 될 것 같습니다. 덤으로 거기서 아이디어를 얻어가셨다면 저에게는 더할나위없이 보람찰 것 같네요.
(그리고 블로그 글에 작성하는 내용은 대부분 이미 제가 속한 실무 뿐만아니라 여러 곳에서 보편적으로  사용하고 있는 기술이기도 합니다.)

이번 주제에서 살짝 벗어난 얘기를 길게 늘어놓았는데요. 위에서 vault를 사용하게 된 계기를 어느정도 알게 되었으니 본격적으로 vault를 사용해보고자 합니다. 테스트 단계에서 vault를 사용하는 방식은 여러가지가 있을 수 있는데요. 저는 클라우드 서버 인스턴스를 따로 생성해서 해당 서버에 vault 서버를 설치하는 방식으로 진행하였습니다.

어디서 개발하든 어떤 로컬환경에서 개발하든 바로바로 적용해볼 수 있고 관리가 아주 편하기 때문에 사비가 살짝 들더라도 클라우드 서버를 이용하게 되었습니다. (상대적으로 저렴한 AWS Lightsail을 사용하였습니다.)

vault를 서버를 야매로 적용해본 것이기에 이를 감안해서 봐주시면 될 것 같습니다.

 

📌 2. vault 서버 설치하기(공식사이트의 Tutorials를 보면 다 나오는,,,)

Vault는 dev 모드의 in-memory 방식으로 설치하는 방식이 있고 production 모드의 실제 서버를 실행해서 설치하는 방식이 있습니다. 저는 클라우드 서버에서 상시로 접근이 가능한 상태로 운영해야하기에 production 모드로 설치를 진행하였습니다.

설치하는 방법은 정말 간단합니다. Vault 공식사이트의 듀토리얼 내용을 그대로 따라하시면 됩니다.

 

Install Vault | Vault | HashiCorp Developer

The first step to using Vault is to get it installed.

developer.hashicorp.com

설치 방법은 끝입니다. 네 정말 끝입니다.

그런데 저는 멍청이라서 친절한 듀토리얼 설명에도 설치하다가 막혔던 것들이 있었는데요. 그 내용만 짚고 넘어가도록 하겠습니다.

$ sudo yum -y install vault

Linux에서 Amazon Linux 방식으로 vault 패키지를 설치해주면 다음의 디렉토리에서 vault 내용이 설치된 것을 확인할 수 있습니다.

1. /opt/vault/
2. /etc/vault.d/

1번 디렉토리안에는 data, tls 디렉토리가 담겨있고, 2번 디렉토리 안에는 환경변수를 담은 파일과 기본 설정 파일(vault.hcl)이 담겨있습니다. 우선 이 정도만 알면 될 것 같습니다.

 

📌 3. 설정파일 만들고 실행해보자

vault 서버를 실행하기 위해서는 실행을 위한 설정파일이 있어야합니다. 설정파일은 주로 hcl 확장자로 이루어진 파일을 사용하게 되는데요. hcl은 Hashicorp Configuration Language의 약자라고 하네요. hashicorp는 vault를 개발하고 관리하는 회사이고 그 회사에서 자체적으로 개발한 설정용 언어인 것 같습니다. 심심하시면 링크 걸어드린 github 내용 참고해보시거나 다른 글 참고해보시면 될 것 같습니다.

# /etc/vault.d/config.hcl
ui = true

#mlock = true
disable_mlock = true

storage "file" {
  path = "/opt/vault/data"
}

# HTTPS listener
listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_disable 	= "true"
#  tls_cert_file = "/opt/vault/tls/tls.crt"
#  tls_key_file  = "/opt/vault/tls/tls.key"
}

api_addr = "http://127.0.0.1:8200"
cluster_addr = "http://127.0.0.1:8201"

/etc/vault.d/ 디렉토리에 새로운 설정파일(config.hcl)을 만들었습니다. 위의 내용들을 공식사이트 듀토리얼에 다 나오는 내용인데요. 몇 가지 주의할 점이 있습니다. storage 설정에서는 secret 내용이나 authentication 정보를 어떻게 저장하고 관리할 것인지 설정하는데요. file 방식으로 했고 디렉토리는 기본적으로 생성된 /opt/vault/data를 바라보게 하였습니다.
(raft 방식도 있는데 정확히 어떤 것인지 몰라 가장 간단해보이는 file 방식으로 하였습니다. raft 방식으로 해보신 분 있으시면 이게 무슨 개념인지 알려주시면 감사하겠습니다)

listener는 어떤 요청방식에 응답할 것인지 설정하는 것인데요. tcp, unix 두 개 방식이 있고 보편적으로 사용되는 것 같아보이는 tcp 방식으로 설정했습니다. tcp에는 tls 인증서도 적용할 수 있는데요. 저는 따로 하지않고 tls 자체를 비활성화 처리했습니다.

추후에 글을 작성하겠지만 저는 돈을 조금이라도 더 아끼고자 하나의 AWS instance에 vault와 jenkins 두 개의 서버를 적용했는데요. nginx를 통해 https를 적용할 것이기 때문에 vault 자체적으로는 따로 tls 설정을 하지 않았습니다.
(혹시 vault의 tls 설정이 정확하게 어떤 것을 의미하는지 아시는 분 계시면 알려주시면 감사하겠습니다.)

요건 좀 중요한데요. listener에서 address를 "0.0.0.0:8200"로 하지 않으면 외부에서 해당 포트로 vault 접속이 안됩니다. 누락되지 않도록 주의해주세요. (8200번 포트는 vault의 기본 포트입니다.)

$ vault server -config=/etc/vault.d/config.hcl

위의 설정파일을 가지고 vault를 실행해줍니다. 그러면 뭐라고 콘솔에 나오면서 Vault server started! 라고 나오면 서버가 잘 실행된 것입니다.

 

📌 4. vault 서버 초기화(Unsealing Vault server) 및 접속

vault는 서버를 실행하면 끝이 아니라 초기화 작업을 해주어야 합니다. vault 공식문서를 보면 내부 동작원리를 설명하고 있는데요. 저는 아직 이부분에 대해서 이해를 하지 못해 아쉽게도 자세하게 설명을 드릴 순 없을 것 같습니다. 

그 중에 알고 넘어가야 하는 부분이 있는데요. Vault Seal & Unseal 개념입니다.
(제가 참고했던 유투브 강의가 있는데 시간 되시는 분들은 보시는 걸 추천드립니다.)

Vault는 sealed, 즉 봉인된 상태로 서버가 실행이 됩니다. Vault 보안을 특히 중시하고 있어서 vault로 물리저장소에 저장된 secret 데이터에 접근은 할 수 있지만 encrypted된 상태이기에 그 자체로 데이터를 사용할 수 없게 되어있습니다.

물리저장소에 암호화된 데이터를 꺼내서 사용할 수 있는 유일한 방법은 vault 자체를 unseal, 즉 봉인해제를 하는 방법인데요. 여기서 shared shamir keys가 사용됩니다. 

shared shamir keys 개념은 아주아주 복잡하지만 정말 간단하게 얘기하면 비밀내용을 여러 조각으로 쪼개서 일정 갯수 이상의 조각이 모였을때만 비밀내용을 다시 복원할 수 있도록 하는 방법입니다. vault를 unsealing하기 위해 unseal key가 필요한데 그 키를 여러 조각으로 쪼개서 보관한다고 생각하시면 됩니다.

vault 서버 초기화는 바로 shared shamir keys를 생성하고 unsealing 작업을 해주는 것이라 보면 됩니다.
(shared shamir keys에 대한 내용, 그리고 관련한 암호학에 대해서도 공부해보면 좋겠다는 생각도 들었네요.)

$ export VAULT_ADDR='http://127.0.0.1:8200'

$ vault operator init

가장 먼저 linux 서버의 환경변수로 VAULT_ADDR를 설정해줍니다. 이걸 해야 초기화 작업을 진행할 수 있습니다. 그 다음 vault init을 시작합니다.

Unseal Key 1: 4jYbl2CBIv6SpkKj6Hos9iD32k5RfGkLzlosrrq/JgOm
Unseal Key 2: B05G1DRtfYckFV5BbdBvXq0wkK5HFqB9g2jcDmNfTQiS
Unseal Key 3: Arig0N9rN9ezkTRo7qTB7gsIZDaonOcc53EHo83F5chA
Unseal Key 4: 0cZE0C/gEk3YHaKjIWxhyyfs8REhqkRW/CSXTnmTilv+
Unseal Key 5: fYhZOseRgzxmJCmIqUdxEm9C3jB5Q27AowER9w4FC2Ck

Initial Root Token: s.KkNJYWF5g0pomcCLEmDdOVCW

init을 실행하면 위와 같이 unseal key에 대한 5개의 shared shamir key를 생성해주고 root token도 생성해줍니다. 해당 정보를 꼭 메모장이든 어디든 기록해두시고 지워지지 않도록 은밀한 곳에 잘 보관하셔야 합니다.

 vault operator unseal

Unseal Key (will be hidden): [unseal key 입력]
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true
Total Shares       5
Threshold          3
Unseal Progress    1/3
Unseal Nonce       d3d06528-aafd-c63d-a93c-e63ddb34b2a9
Version            1.7.0
Storage Type       raft
HA Enabled         true

vault operator 명령어로 unseal 작업을 진행합니다. 위와 같이 unseal 명령어를 입력하면 unseal key를 입력하라고 나오는데요. 거기에 init할 때 받았던 5개의 unseal key 조각들 중 아무거나 입력하면 됩니다.

한 번에 하나씩만 입력되기 때문에 위 작업을 3번 해주어야 unseal 작업이 완료됩니다.

unseal 작업이 완료되면 이제 브라우저 화면을 띄워서 http://[AWS Instance hostname]:8200에 접속해줍니다.
(외부에서 AWS instance 접속하실 때 방화벽 설정에서 8200번 포트가 적절하게 open되어 있는지 꼭 체크해주세요.)

vault에 접속하면 위와 같이 vault 로그인 화면이 나옵니다. 기본 method가 Token으로 나오는데요. 여기에 init할 때 받은 Initial Root Token 값을 입력해줍니다. 

인증하고 접속한 vault 관리 화면

인증이 완료되면 위와 같이 vault 관리 화면이 나오게 됩니다. 축하드립니다. 여기까지 성공하셨다면 정말 기본적인 vault 서버 설치를 완료하신 겁니다.

 

📌 5. 마무리

Vault는 실무에서도 거의 필수로 사용되는 secret 정보 관리 툴입니다. 그만큼 뛰어난 보안과 관리의 편리함을 제공해주는데요. 위의 구축된 내용을 기반으로 Spring Boot Application에 어떻게 연동을 하는지에 대해서 따로 게시글을 작성해보려 합니다.

vault root token, unseal keys는 아주아주 중요한 token 값들이기 때문에 외부에 노출되거나 잃어버리는 일이 없도록 잘 관리하셔야 합니다. 

vault의 개념을 좀더 알고 싶으신 분들은 이번 게시글 중간에 링크드린 유투브 강의 꼭 보시는 것을 추천드립니다. 1시간 분량이고 영어로 되어있긴 하지만 자막으로 충분히 커버 가능하고, 처음 vault를 접해보는 분들에게 정말 유익한 강의라고 생각합니다.

틀린 내용 있을 수 있습니다. 피드백 언제나 환영합니다. 감사합니다.