/*
 * Copyright (c) 2014 Red Hat, Inc. and others
 *
 * Red Hat licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 */

package io.vertx.config.vault.client;

import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpRequest;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

A very simple Vault client - does not intend to be complete.
Author:Clement Escoffier
/** * A very simple Vault client - does not intend to be complete. * * @author <a href="http://escoffier.me">Clement Escoffier</a> */
public class SlimVaultClient { public static final String TOKEN_HEADER = "X-Vault-Token"; private final WebClient client; private String token;
Creates an instance of SlimVaultClient.
Params:
  • vertx – the vert.x instance
  • configuration – the configuration. This configuration can contain the underlying Web Client configuration.
/** * Creates an instance of {@link SlimVaultClient}. * * @param vertx the vert.x instance * @param configuration the configuration. This configuration can contain the underlying Web Client configuration. */
public SlimVaultClient(Vertx vertx, JsonObject configuration) { String host = configuration.getString("host"); Integer port = configuration.getInteger("port", 8200); Objects.requireNonNull(host, "The Vault host must be set"); client = WebClient.create(vertx, new WebClientOptions(configuration) .setDefaultPort(port).setDefaultHost(host) ); setToken(configuration.getString("token")); }
Closes the client.
/** * Closes the client. */
public void close() { if (client != null) { client.close(); } }
Reads a secret from `path`.
Params:
  • path – the path
  • responseHandler – the callback invoked with the result
/** * Reads a secret from `path`. * * @param path the path * @param responseHandler the callback invoked with the result */
public void read(String path, Handler<AsyncResult<Secret>> responseHandler) { Objects.requireNonNull(responseHandler); client.get("/v1/" + Objects.requireNonNull(path)) .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "No token to access the vault")) .send(response -> { if (response.failed()) { responseHandler.handle(VaultException.toFailure("Unable to access the Vault", response.cause())); return; } HttpResponse<Buffer> result = response.result(); if (result.statusCode() != 200) { responseHandler.handle(VaultException.toFailure(result.statusMessage(), result.statusCode(), result.bodyAsString())); } else { Secret secret = result.bodyAsJson(Secret.class); responseHandler.handle(Future.succeededFuture(secret)); } }); }
Write a secret to `path`.
Params:
  • path – the path
  • resultHandler – the callback invoked with the result
/** * Write a secret to `path`. * * @param path the path * @param resultHandler the callback invoked with the result */
public void write(String path, JsonObject secrets, Handler<AsyncResult<Secret>> resultHandler) { Objects.requireNonNull(resultHandler); client.post("/v1/" + Objects.requireNonNull(path)) .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "The token must be set")) .sendJsonObject(Objects.requireNonNull(secrets, "The secret must be set"), ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } HttpResponse<Buffer> response = ar.result(); if (response.statusCode() == 200 || response.statusCode() == 204) { resultHandler.handle(Future.succeededFuture(response.bodyAsJson(Secret.class))); } else { resultHandler.handle(VaultException.toFailure(response.statusMessage(), response.statusCode(), response.bodyAsString())); } }); }
Lists secrets from path (children).
Params:
  • path – the path
  • resultHandler – the callback invoked with the result
/** * Lists secrets from path (children). * * @param path the path * @param resultHandler the callback invoked with the result */
public void list(String path, Handler<AsyncResult<List<String>>> resultHandler) { Objects.requireNonNull(path, "The path is required to list secrets"); String fullPath = path + "?list=true"; Objects.requireNonNull(resultHandler); read(fullPath, ar -> { if (ar.failed() && !(ar.cause() instanceof VaultException)) { resultHandler.handle(Future.failedFuture(ar.cause())); } else if (ar.failed()) { if (((VaultException) ar.cause()).getStatusCode() == 404) { resultHandler.handle(Future.succeededFuture(Collections.emptyList())); } else { resultHandler.handle(Future.failedFuture(ar.cause())); } } else { JsonArray keys = ar.result().getData().getJsonArray("keys"); if (keys == null) { resultHandler.handle(Future.failedFuture("Cannot find keys")); } else { List<String> list = new ArrayList<>(); keys.forEach(o -> list.add((String) o)); resultHandler.handle(Future.succeededFuture(list)); } } }); }
Deletes a secret from `path`.
Params:
  • path – the path
  • resultHandler – the callback invoked with the result
/** * Deletes a secret from `path`. * * @param path the path * @param resultHandler the callback invoked with the result */
public void delete(String path, Handler<AsyncResult<Void>> resultHandler) { Objects.requireNonNull(resultHandler); client.delete("/v1/" + Objects.requireNonNull(path)) .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "The token must be set")) .send(ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } HttpResponse<Buffer> response = ar.result(); if (response.statusCode() != 204) { resultHandler.handle(VaultException.toFailure(response.statusMessage(), response.statusCode(), response.bodyAsString())); } else { resultHandler.handle(Future.succeededFuture()); } }); }
Creates a new token.
Params:
  • tokenRequest – the token request
  • resultHandler – the callback invoked with the result.
/** * Creates a new token. * * @param tokenRequest the token request * @param resultHandler the callback invoked with the result. */
public void createToken(TokenRequest tokenRequest, Handler<AsyncResult<Auth>> resultHandler) { client.post("/v1/auth/token/create" + ((tokenRequest.getRole() == null) ? "" : "/" + tokenRequest.getRole())) .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "The token must be set")) .sendJsonObject(tokenRequest.toPayload(), ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } manageAuthResult(resultHandler, ar.result()); }); } private void manageAuthResult(Handler<AsyncResult<Auth>> resultHandler, HttpResponse<Buffer> response) { if (response.statusCode() != 200) { resultHandler.handle(VaultException.toFailure(response.statusMessage(), response.statusCode(), response.bodyAsString())); } else { JsonObject object = response.bodyAsJsonObject(); Auth auth = object.getJsonObject("auth").mapTo(Auth.class); resultHandler.handle(Future.succeededFuture(auth)); } }
Logs in against the `AppRole` backend.
Params:
  • roleId – the role id
  • secretId – the secret id
  • resultHandler – the callback invoked with the result
/** * Logs in against the `AppRole` backend. * * @param roleId the role id * @param secretId the secret id * @param resultHandler the callback invoked with the result */
public void loginWithAppRole(String roleId, String secretId, Handler<AsyncResult<Auth>> resultHandler) { JsonObject payload = new JsonObject() .put("role_id", Objects.requireNonNull(roleId, "The role must not be null")) .put("secret_id", Objects.requireNonNull(secretId, "The secret must not be null")); client.post("/v1/auth/approle/login") .sendJsonObject(payload, ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } manageAuthResult(resultHandler, ar.result()); }); }
Logs in against the `userpass` backend.
Params:
  • username – the username
  • password – the password
  • resultHandler – the callback invoked with the result
/** * Logs in against the `userpass` backend. * * @param username the username * @param password the password * @param resultHandler the callback invoked with the result */
public void loginWithUserCredentials(String username, String password, Handler<AsyncResult<Auth>> resultHandler) { JsonObject payload = new JsonObject() .put("password", Objects.requireNonNull(password, "The password must not be null")); client.post("/v1/auth/userpass/login/" + Objects.requireNonNull(username, "The username must not be null")) .sendJsonObject(payload, ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } manageAuthResult(resultHandler, ar.result()); }); }
Logs in against the `Cert` backend. Certificates are configured directly on the client instance.
Params:
  • resultHandler – the callback invoked with the result
/** * Logs in against the `Cert` backend. Certificates are configured directly on the client instance. * * @param resultHandler the callback invoked with the result */
public void loginWithCert(Handler<AsyncResult<Auth>> resultHandler) { client.post("/v1/auth/cert/login") .send(ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } manageAuthResult(resultHandler, ar.result()); }); }
Renews the current token.
Params:
  • leaseDurationInSecond – the extension in second
  • resultHandler – the callback invoked with the result
/** * Renews the current token. * * @param leaseDurationInSecond the extension in second * @param resultHandler the callback invoked with the result */
public void renewSelf(long leaseDurationInSecond, Handler<AsyncResult<Auth>> resultHandler) { JsonObject payload = null; if (leaseDurationInSecond > 0) { payload = new JsonObject().put("increment", leaseDurationInSecond); } HttpRequest<Buffer> request = client.post("/v1/auth/token/renew-self") .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "The token must not be null")); Handler<AsyncResult<HttpResponse<Buffer>>> handler = ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } manageAuthResult(resultHandler, ar.result()); }; if (payload != null) { request.sendJsonObject(payload, handler); } else { request.send(handler); } }
Looks up for the current token metadata.
Params:
  • resultHandler – the callback invoked with the result
/** * Looks up for the current token metadata. * * @param resultHandler the callback invoked with the result */
public void lookupSelf(Handler<AsyncResult<Lookup>> resultHandler) { client.get("/v1/auth/token/lookup-self") .putHeader(TOKEN_HEADER, Objects.requireNonNull(getToken(), "The token must not be null")) .send(ar -> { if (ar.failed()) { resultHandler.handle(VaultException.toFailure("Unable to access the Vault", ar.cause())); return; } HttpResponse<Buffer> response = ar.result(); if (response.statusCode() != 200) { resultHandler.handle(VaultException.toFailure(response.statusMessage(), response.statusCode(), response.bodyAsString())); } else { JsonObject object = response.bodyAsJsonObject(); Lookup lookup = object.getJsonObject("data").mapTo(Lookup.class); resultHandler.handle(Future.succeededFuture(lookup)); } }); }
Returns:the current token.
/** * @return the current token. */
public synchronized String getToken() { return token; }
Sets the token.
Params:
  • token – the new token
Returns:the current SlimVaultClient
/** * Sets the token. * * @param token the new token * @return the current {@link SlimVaultClient} */
public synchronized SlimVaultClient setToken(String token) { this.token = token; return this; } }