Hashicorp Vault를 사용한 자동화된 비밀 검색
전제 조건:
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;
...
}
결론
특히 다른 사람이 서버에 액세스할 수 있는 경우 안전하지 않기 때문에 프로덕션 환경에서 이 솔루션을 정확히 사용하고 싶지 않을 수 있습니다. 그러나 로컬에서 사용하는 경우에는 문제가 없으며 실수로 자격 증명을 소스 제어에 커밋할 가능성을 피할 수 있습니다.
Reference
이 문제에 관하여(Hashicorp Vault를 사용한 자동화된 비밀 검색), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/johnlm/automated-secret-retrieval-using-hashicorp-vault-dgo텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)