Hashicorp Vault를 사용한 자동화된 비밀 검색

24278 단어

전제 조건:


  • 프로젝트에서 사용할 수 있는 비밀이 포함된 Vault 경로
  • Ausername and password는 Vault 비밀(또는 대체 인증 방법)에 액세스합니다
  • .
  • 기본형Spring Boot project .

  • Vault CLI를 사용하여 비밀을 자동으로 검색하고 런타임 시 Spring 응용 프로그램에 삽입하는 데 많은 이점이 있습니다documentation. 이렇게 하면 Spring 속성 파일에 수동으로 입력할 필요가 없기 때문에 시간이 절약되고 실수로 자격 증명을 커밋하는 것을 더 어렵게 만들어 보안이 향상됩니다. 하지만 Vault CLI를 설치할 수 없거나 애플리케이션에서 사용자가 설치해야 하는 외부 종속성 수를 최소화하려는 경우에는 어떻게 해야 할까요?

    또 다른 옵션은 유사한 기능을 제공하는 Vault's HTTP API 을 사용하는 것입니다. 이 예에서는 Vault에서 검색한 사용자 이름과 비밀번호를 사용하여 다른 API를 호출하는 애플리케이션이 있다고 가정합니다.

    이 가이드는 먼저 볼트 경로에서 비밀을 검색하는 방법을 자세히 설명한 다음 애플리케이션이 실행되는 동안 비밀을 사용할 수 있게 만드는 방법을 설명합니다. 이 코드는 Java 8을 사용하여 테스트되었지만 이전 및 이후 버전에서 작동해야 합니다.

    런타임 시 비밀 검색



    나중에 응용 프로그램에 삽입할 수 있는 java.util.Properties 개체로 Vault 경로의 모든 비밀을 검색하는 헬퍼 클래스를 생성하여 시작하겠습니다. 예를 들어 main/java/org.john.example/config/VaultRetrievalUtil.java 와 같은 모든 패키지에서 이 파일을 만들 수 있습니다.

    package org.john.example.config;
    
    import org.springframework.http.*;
    import com.fasterxml.jackson.databind.JsonNode;
    import org.springframework.web.client.RestTemplate;
    
    import java.net.URI;
    import java.util.*;
    
    public class VaultRetrievalUtil{
        RestTemplate restTemplate = new RestTemplate();
    }
    


    우리가 작성할 첫 번째 방법은 Vault API에 액세스하는 데 필요한 클라이언트 토큰을 검색하기 위해 Vault 자격 증명을 사용합니다. JSON 요청 본문에서 따옴표를 이스케이프 처리해야 합니다.

        private String retrieveVaultClientToken(String vaultUsername, String vaultPassword) {
            String requestBody = "{\\"password\":\\"" + vaultPassword + "\"}";
            URI vaultLoginUri = URI.create("https://vault.mywebsite.com:8200/v1/auth/ldap/login/" + vaultUsername);
            RequestEntity<String> requestEntity = new RequestEntity<>(requestBody, HttpMethod.POST, vaultLoginUri);
            ResponseEntity<JsonNode> responseEntity = restTemplate.exchange(requestEntity, JsonNode.class);
            return Objects.requireNonNull(respone.getBody()).get("auth").get("client_token").asText();
        }
    


    토큰이 있으면 Vault 경로에 있는 모든 비밀을 포함하는 JSON 개체를 검색하는 메서드를 작성할 수 있습니다.

        private JsonNode retrieveVaultSecrets(String vaultClientToken) {
            URI vaultSecretsPath = URI.create("https://vault.mywebsite.com:8200/v1/mySecretsPath")
            HttpHeaders httpHeaders = new HttpHeaders();
            headers.put("X-Vault-Token", Collections.singletonList(vaultToken));
            HttpEntity<String> requestEntity = new HttpEntity<>(null, httpHeaders);
            ResponseEntity<JsonNode> responseEntity = restTemplate.exchange(vaultSecretsPath, HttpMethod.GET, request, JsonNode.class);
            return Objects.requireNonNull(response.getBody()).get("data");
        }
    


    반쯤 왔어! 하지만 이 JsonNode를 직접 사용할 수는 없습니다. 다음에 java.util.Properties 개체로 변환해야 합니다.

        private Properties convertSecretsToSpringProperties(JsonNode vaultSecrets) {
            Properties properties = new Properties();
            for(Iterator<Map.Entry<String, JsonNode>> iterator = vaultSecrets.fields(); iterator.hasNext();){
                Map.Entry<String, JsonNode> secret = iterator.next();
                properties.put(secret.getKey(), secret.getValue().textValue());
            }
            return properties;
        }
    


    이제 모든 것을 모아서 다른 클래스가 사용할 공용 메서드를 작성해 봅시다. 아래 코드에서는 볼트 사용자 이름과 암호를 사용하여 myVaultUsername 및 myVaultPassword 환경 변수를 설정했다고 가정합니다.

    package org.john.example.config;
    
    import org.springframework.http.*;
    import com.fasterxml.jackson.databind.JsonNode;
    import org.springframework.web.client.RestTemplate;
    
    import java.net.URI;
    import java.util.*;
    
    public class VaultRetrievalUtil{
        RestTemplate restTemplate = new RestTemplate();
    
        public Properties retrieveVaultProperties(){
            String vaultUsername = System.getenv("myVaultUsername");
            String vaultPassword = System.getenv("myVaultPassword");
    
            if(vaultUsername != null && vaultPassword != null){
                String vaultClientToken = retrieveVaultClientToken(vaultUsername, vaultPassword);
                JsonNode vaultSecrets = retrieveVaultSecrets(vaultClientToken);
                return convertSecretsToSpringProperties(vaultSecrets);
            }
            else{
                /* Assuming if these credentials are not set, the user doesn't need credentials from Vault. 
                Alternatively, we could throw an exception if they're required for the application to work. */
                return new Properties();
            }
        }   
    
        private String retrieveVaultClientToken(String vaultUsername, String vaultPassword) {
            String requestBody = "{\\"password\":\\"" + vaultPassword + "\"}";
            URI vaultLoginUri = URI.create("https://vault.mywebsite.com:8200/v1/auth/ldap/login/" + vaultUsername);
            RequestEntity<String> requestEntity = new RequestEntity<>(requestBody, HttpMethod.POST, vaultLoginUri);
            ResponseEntity<JsonNode> responseEntity = restTemplate.exchange(requestEntity, JsonNode.class);
            return Objects.requireNonNull(respone.getBody()).get("auth").get("client_token").asText();
        }
    
        private JsonNode retrieveVaultSecrets(String vaultClientToken) {
            URI vaultSecretsPath = URI.create("https://vault.mywebsite.com:8200/v1/mySecretsPath")
            HttpHeaders httpHeaders = new HttpHeaders();
            headers.put("X-Vault-Token", Collections.singletonList(vaultToken));
            HttpEntity<String> requestEntity = new HttpEntity<>(null, httpHeaders);
            ResponseEntity<JsonNode> responseEntity = restTemplate.exchange(vaultSecretsPath, HttpMethod.GET, request, JsonNode.class);
            return Objects.requireNonNull(response.getBody()).get("data");
        }
    
        private Properties convertSecretsToSpringProperties(JsonNode vaultSecrets) {
            Properties properties = new Properties();
            for(Iterator<Map.Entry<String, JsonNode>> iterator = vaultSecrets.fields(); iterator.hasNext();){
                Map.Entry<String, JsonNode> secret = iterator.next();
                properties.put(secret.getKey(), secret.getValue().textValue());
            }
            return properties;
        }
    }
    
    


    지금은 그게 다야! 다음으로 이러한 속성을 애플리케이션에 삽입합니다.

    애플리케이션에 Spring 속성 주입



    이 부분은 간단합니다. VaultRetrievalUtil의 인스턴스를 만든 다음 해당 클래스에서 검색한 속성을 기본 클래스의 기본값으로 설정합니다. 즉, 사용자가 특별히 다른 곳에서 정의하도록 선택한 경우 여전히 무시할 수 있습니다.

    package org.john.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cache.annotation.EnableCaching;
    import org.john.example.config.VaultRetrievalUtil;
    
    @SpringBootApplication
    public class Application{
        public static void main(String[] args){
            VaultRetrievalUtil vaultRetrievalUtil = new VaultRetrievalUtil();
            SpringApplication application = new SpringApplication(Application.class);
            application.setDefaultProperties(vaultRetrievalUtil.retrieveVaultProperties());
    
            application.run(args);
        }
    }
    


    그게 다야! 이제 응용 프로그램이 실행되는 동안 Vault 경로의 모든 속성에 액세스할 수 있습니다. 이제 Spring 속성처럼 검색할 수 있습니다. 예를 들면 다음과 같습니다.

    public class consumingClass{
        @Value("usernameToCallAnotherApi")
        private String usernameToCallAnotherApi;
    
        @Value("passwordToCallAnotherApi")
        private String passwordToCallAnotherApi;
    
        ...
    }
    


    결론



    특히 다른 사람이 서버에 액세스할 수 있는 경우 안전하지 않기 때문에 프로덕션 환경에서 이 솔루션을 정확히 사용하고 싶지 않을 수 있습니다. 그러나 로컬에서 사용하는 경우에는 문제가 없으며 실수로 자격 증명을 소스 제어에 커밋할 가능성을 피할 수 있습니다.

    좋은 웹페이지 즐겨찾기