All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.descoped.secrets.google.secretmanager.restapi.GoogleSecretManagerClient Maven / Gradle / Ivy

The newest version!
package io.descoped.secrets.google.secretmanager.restapi;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.ComputeEngineCredentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import io.descoped.secrets.api.SecretManagerClient;

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.atomic.AtomicBoolean;

public class GoogleSecretManagerClient implements SecretManagerClient {

    static final ObjectMapper mapper = new ObjectMapper();
    private final String projectId;
    private final HttpClient client = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build();
    private final AccessToken accessToken;
    private final AtomicBoolean closed = new AtomicBoolean(false);

    // uses compute-engine
    public GoogleSecretManagerClient(String projectId) {
        this.projectId = projectId;
        GoogleCredentials credentials = ComputeEngineCredentials.create();
        GoogleCredentials scopedCredentials = credentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
        this.accessToken = getAccessToken(scopedCredentials);
    }

    // uses service-account
    public GoogleSecretManagerClient(String projectId, String serviceAccountKeyPath) {
        this.projectId = projectId;
        GoogleCredentials credentials = getServiceAccountCredentials(serviceAccountKeyPath);
        GoogleCredentials scopedCredentials = credentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
        this.accessToken = getAccessToken(scopedCredentials);
    }

    GoogleCredentials getServiceAccountCredentials(String serviceAccountKeyPath) {
        try {
            return ServiceAccountCredentials.fromStream(Files.newInputStream(Paths.get(serviceAccountKeyPath), StandardOpenOption.READ));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    AccessToken getAccessToken(GoogleCredentials scopedCredentials) {
        try {
            return scopedCredentials.refreshAccessToken();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] readBytes(String secretName, String secretVersion) {
        if (closed.get()) {
            throw new RuntimeException("Client is closed!");
        }
        try {
            String accessToken = String.format("Bearer %s", this.accessToken.getTokenValue());
            URI uri = URI.create(String.format("https://secretmanager.googleapis.com/v1/projects/%s/secrets/%s/versions/%s:access",
                    projectId, secretName, secretVersion == null ? "latest" : secretVersion));

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(uri)
                    .header("Authorization", accessToken)
                    .header("Content-Type", "application/json")
                    .GET()
                    .build();

            HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
            if (response.statusCode() != 200) {
                throw new RuntimeException(String.format("Error (%s) reading secret '%s' cause %s", response.statusCode(), secretName, response.body()));
            }

            try {
                JsonNode responseNode = mapper.readTree(response.body());
                if (!responseNode.has("payload")) {
                    throw new RuntimeException("Unable to resolve payload from " + response.body());
                }
                if (!responseNode.get("payload").has("data")) {
                    throw new RuntimeException("Unable to resolve payload.data from " + response.body());
                }
                return responseNode.get("payload").get("data").binaryValue();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String addVersion(String secretName, byte[] secretValue) {
        if (closed.get()) {
            throw new RuntimeException("Client is closed!");
        }
        try {
            HttpResponse checkSecretResponse;
            {
                String accessToken = String.format("Bearer %s", this.accessToken.getTokenValue());
                URI uri = URI.create(String.format("https://secretmanager.googleapis.com/v1/projects/%s/secrets/%s",
                        projectId, secretName));

                HttpRequest request = HttpRequest.newBuilder()
                        .uri(uri)
                        .header("Authorization", accessToken)
                        .GET()
                        .build();

                checkSecretResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
            }
            if (checkSecretResponse.statusCode() == 200) {
                // already exists, do nothing
            } else if (checkSecretResponse.statusCode() == 404) {
                ObjectNode secret = mapper.createObjectNode();
                ObjectNode replication = secret.putObject("replication");
                ObjectNode userManaged = replication.putObject("userManaged");
                ArrayNode arrayNode = userManaged.putArray("replicas");
                ObjectNode replica = arrayNode.addObject();
                replica.put("location", "europe-north1");

                String accessToken = String.format("Bearer %s", this.accessToken.getTokenValue());
                URI uri = URI.create(String.format("https://secretmanager.googleapis.com/v1/projects/%s/secrets?secretId=%s",
                        URLEncoder.encode(projectId, StandardCharsets.UTF_8),
                        URLEncoder.encode(secretName, StandardCharsets.UTF_8)
                ));

                HttpRequest request = HttpRequest.newBuilder()
                        .uri(uri)
                        .header("Authorization", accessToken)
                        .header("Content-Type", "application/json")
                        .POST(HttpRequest.BodyPublishers.ofString(secret.toString()))
                        .build();

                HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
                if (response.statusCode() != 200) {
                    throw new RuntimeException(String.format("Error (%s) creating secret '%s' cause %s", response.statusCode(), secretName, response.body()));
                }
            } else {
                throw new RuntimeException(String.format("Error (%s) checking secret '%s' cause %s", checkSecretResponse.statusCode(), secretName, checkSecretResponse.body()));
            }
            {
                ObjectNode secretVersion = mapper.createObjectNode();
                ObjectNode payload = secretVersion.putObject("payload");
                payload.put("data", secretValue);

                String accessToken = String.format("Bearer %s", this.accessToken.getTokenValue());
                URI uri = URI.create(String.format("https://secretmanager.googleapis.com/v1/projects/%s/secrets/%s:addVersion",
                        URLEncoder.encode(projectId, StandardCharsets.UTF_8),
                        URLEncoder.encode(secretName, StandardCharsets.UTF_8)
                ));

                HttpRequest request = HttpRequest.newBuilder()
                        .uri(uri)
                        .header("Authorization", accessToken)
                        .header("Content-Type", "application/json")
                        .POST(HttpRequest.BodyPublishers.ofString(secretVersion.toString()))
                        .build();

                HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
                if (response.statusCode() != 200) {
                    throw new RuntimeException(String.format("Error (%s) creating secret '%s' cause %s", response.statusCode(), secretName, response.body()));
                }

                JsonNode responseSecretVersion = mapper.readTree(response.body());
                String versionUrl = responseSecretVersion.get("name").textValue();
                String[] versionPathElements = versionUrl.split("/");
                return versionPathElements[versionPathElements.length - 1];
            }
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void close() {
        closed.compareAndSet(false, true);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy