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

io.scalecube.security.vault.VaultServiceRolesInstaller Maven / Gradle / Ivy

The newest version!
package io.scalecube.security.vault;

import com.bettercloud.vault.json.Json;
import com.bettercloud.vault.rest.Rest;
import com.bettercloud.vault.rest.RestException;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.scalecube.security.vault.VaultServiceRolesInstaller.ServiceRoles.Role;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Function;
import java.util.function.Supplier;

public class VaultServiceRolesInstaller {

  private static final Logger LOGGER = System.getLogger(VaultServiceRolesInstaller.class.getName());

  private static final String VAULT_TOKEN_HEADER = "X-Vault-Token";

  private static final List> DEFAULT_SERVICE_ROLES_SOURCES =
      Collections.singletonList(new ResourcesServiceRolesSupplier());

  private static final ObjectMapper OBJECT_MAPPER =
      new ObjectMapper(new YAMLFactory()).setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

  private final String vaultAddress;
  private final Supplier vaultTokenSupplier;
  private final Supplier keyNameSupplier;
  private final Function roleNameBuilder;
  private final List> serviceRolesSources;
  private final String keyAlgorithm;
  private final String keyRotationPeriod;
  private final String keyVerificationTtl;
  private final String roleTtl;

  private VaultServiceRolesInstaller(Builder builder) {
    this.vaultAddress = builder.vaultAddress;
    this.vaultTokenSupplier = builder.vaultTokenSupplier;
    this.keyNameSupplier = builder.keyNameSupplier;
    this.roleNameBuilder = builder.roleNameBuilder;
    this.serviceRolesSources = builder.serviceRolesSources;
    this.keyAlgorithm = builder.keyAlgorithm;
    this.keyRotationPeriod = builder.keyRotationPeriod;
    this.keyVerificationTtl = builder.keyVerificationTtl;
    this.roleTtl = builder.roleTtl;
  }

  /**
   * Builds vault oidc micro-infrastructure (identity roles and keys) to use it for
   * machine-to-machine authentication.
   */
  public void install() {
    if (isNullOrNoneOrEmpty(vaultAddress)) {
      LOGGER.log(Level.DEBUG, "Skipping serviceRoles installation, vaultAddress not set");
      return;
    }

    final ServiceRoles serviceRoles = loadServiceRoles();
    if (serviceRoles == null || serviceRoles.roles.isEmpty()) {
      LOGGER.log(Level.DEBUG, "Skipping serviceRoles installation, serviceRoles not set");
      return;
    }

    final String token = vaultTokenSupplier.get();
    final Rest rest = new Rest().header(VAULT_TOKEN_HEADER, token);

    final String keyName = keyNameSupplier.get();
    createVaultIdentityKey(rest.url(buildVaultIdentityKeyUri(keyName)), keyName);

    for (Role role : serviceRoles.roles) {
      String roleName = roleNameBuilder.apply(role.role);
      createVaultIdentityRole(
          rest.url(buildVaultIdentityRoleUri(roleName)), keyName, roleName, role.permissions);
    }

    LOGGER.log(Level.DEBUG, "Installed serviceRoles ({0})", serviceRoles);
  }

  private ServiceRoles loadServiceRoles() {
    if (serviceRolesSources == null) {
      return null;
    }

    for (Supplier serviceRolesSource : serviceRolesSources) {
      final ServiceRoles serviceRoles = serviceRolesSource.get();
      if (serviceRoles != null) {
        return serviceRoles;
      }
    }

    return null;
  }

  private static void verifyOk(int status) {
    if (status != 200 && status != 204) {
      throw new IllegalStateException("Not expected status returned, status=" + status);
    }
  }

  private void createVaultIdentityKey(Rest rest, String keyName) {
    LOGGER.log(Level.DEBUG, "[createVaultIdentityKey] {0}", keyName);

    byte[] body =
        Json.object()
            .add("rotation_period", keyRotationPeriod)
            .add("verification_ttl", keyVerificationTtl)
            .add("allowed_client_ids", "*")
            .add("algorithm", keyAlgorithm)
            .toString()
            .getBytes();

    try {
      verifyOk(rest.body(body).post().getStatus());
    } catch (RestException e) {
      throw new RuntimeException(e);
    }
  }

  private void createVaultIdentityRole(
      Rest rest, String keyName, String roleName, List permissions) {
    LOGGER.log(Level.DEBUG, "[createVaultIdentityRole] {0}", roleName);

    byte[] body =
        Json.object()
            .add("key", keyName)
            .add("template", createTemplate(permissions))
            .add("ttl", roleTtl)
            .toString()
            .getBytes();

    try {
      verifyOk(rest.body(body).post().getStatus());
    } catch (RestException e) {
      throw new RuntimeException(e);
    }
  }

  private static String createTemplate(List permissions) {
    return Base64.getUrlEncoder()
        .encodeToString(
            Json.object().add("permissions", String.join(",", permissions)).toString().getBytes());
  }

  private String buildVaultIdentityKeyUri(String keyName) {
    return new StringJoiner("/", vaultAddress, "")
        .add("/v1/identity/oidc/key")
        .add(keyName)
        .toString();
  }

  private String buildVaultIdentityRoleUri(String roleName) {
    return new StringJoiner("/", vaultAddress, "")
        .add("/v1/identity/oidc/role")
        .add(roleName)
        .toString();
  }

  private static boolean isNullOrNoneOrEmpty(String value) {
    return Objects.isNull(value)
        || "none".equalsIgnoreCase(value)
        || "null".equals(value)
        || value.isEmpty();
  }

  public static class ServiceRoles {

    private List roles;

    public List roles() {
      return roles;
    }

    public ServiceRoles roles(List roles) {
      this.roles = roles;
      return this;
    }

    @Override
    public String toString() {
      return new StringJoiner(", ", ServiceRoles.class.getSimpleName() + "[", "]")
          .add("roles=" + roles)
          .toString();
    }

    public static class Role {

      private String role;
      private List permissions;

      public String role() {
        return role;
      }

      public Role role(String role) {
        this.role = role;
        return this;
      }

      public List permissions() {
        return permissions;
      }

      public Role permissions(List permissions) {
        this.permissions = permissions;
        return this;
      }

      @Override
      public String toString() {
        return new StringJoiner(", ", Role.class.getSimpleName() + "[", "]")
            .add("role='" + role + "'")
            .add("permissions=" + permissions)
            .toString();
      }
    }
  }

  public static class ResourcesServiceRolesSupplier implements Supplier {

    public static final String DEFAULT_FILE_NAME = "service-roles.yaml";

    private final String fileName;

    public ResourcesServiceRolesSupplier() {
      this(DEFAULT_FILE_NAME);
    }

    public ResourcesServiceRolesSupplier(String fileName) {
      this.fileName = Objects.requireNonNull(fileName, "fileName");
    }

    @Override
    public ServiceRoles get() {
      try {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream(fileName);
        return inputStream != null
            ? OBJECT_MAPPER.readValue(inputStream, ServiceRoles.class)
            : null;
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public String toString() {
      return new StringJoiner(", ", ResourcesServiceRolesSupplier.class.getSimpleName() + "[", "]")
          .add("fileName='" + fileName + "'")
          .toString();
    }
  }

  public static class EnvironmentServiceRolesSupplier implements Supplier {

    public static final String DEFAULT_ENV_KEY = "SERVICE_ROLES";

    private final String envKey;

    public EnvironmentServiceRolesSupplier() {
      this(DEFAULT_ENV_KEY);
    }

    public EnvironmentServiceRolesSupplier(String envKey) {
      this.envKey = Objects.requireNonNull(envKey, "envKey");
    }

    @Override
    public ServiceRoles get() {
      try {
        final String value = System.getenv(envKey);
        return value != null
            ? OBJECT_MAPPER.readValue(new StringReader(value), ServiceRoles.class)
            : null;
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public String toString() {
      return new StringJoiner(
              ", ", EnvironmentServiceRolesSupplier.class.getSimpleName() + "[", "]")
          .add("envKey='" + envKey + "'")
          .toString();
    }
  }

  public static class FileServiceRolesSupplier implements Supplier {

    public static final String DEFAULT_FILE = "service-roles.yaml";

    private final String file;

    public FileServiceRolesSupplier() {
      this(DEFAULT_FILE);
    }

    public FileServiceRolesSupplier(String file) {
      this.file = Objects.requireNonNull(file, "file");
    }

    @Override
    public ServiceRoles get() {
      try {
        final File file = new File(this.file);
        if (!file.exists()) {
          return null;
        }
        try (final FileInputStream fis = new FileInputStream(file)) {
          return OBJECT_MAPPER.readValue(fis, ServiceRoles.class);
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    @Override
    public String toString() {
      return new StringJoiner(", ", FileServiceRolesSupplier.class.getSimpleName() + "[", "]")
          .add("file='" + file + "'")
          .toString();
    }
  }

  public static class Builder {

    private String vaultAddress;
    private Supplier vaultTokenSupplier;
    private Supplier keyNameSupplier;
    private Function roleNameBuilder;
    private List> serviceRolesSources = DEFAULT_SERVICE_ROLES_SOURCES;
    private String keyAlgorithm = "RS256";
    private String keyRotationPeriod = "1h";
    private String keyVerificationTtl = "1h";
    private String roleTtl = "1m";

    public Builder() {}

    public Builder vaultAddress(String vaultAddress) {
      this.vaultAddress = vaultAddress;
      return this;
    }

    public Builder vaultTokenSupplier(Supplier vaultTokenSupplier) {
      this.vaultTokenSupplier = vaultTokenSupplier;
      return this;
    }

    public Builder keyNameSupplier(Supplier keyNameSupplier) {
      this.keyNameSupplier = keyNameSupplier;
      return this;
    }

    public Builder roleNameBuilder(Function roleNameBuilder) {
      this.roleNameBuilder = roleNameBuilder;
      return this;
    }

    public Builder serviceRolesSources(List> serviceRolesSources) {
      this.serviceRolesSources = serviceRolesSources;
      return this;
    }

    public Builder serviceRolesSources(Supplier... serviceRolesSources) {
      this.serviceRolesSources = Arrays.asList(serviceRolesSources);
      return this;
    }

    public Builder keyAlgorithm(String keyAlgorithm) {
      this.keyAlgorithm = keyAlgorithm;
      return this;
    }

    public Builder keyRotationPeriod(String keyRotationPeriod) {
      this.keyRotationPeriod = keyRotationPeriod;
      return this;
    }

    public Builder keyVerificationTtl(String keyVerificationTtl) {
      this.keyVerificationTtl = keyVerificationTtl;
      return this;
    }

    public Builder roleTtl(String roleTtl) {
      this.roleTtl = roleTtl;
      return this;
    }

    public VaultServiceRolesInstaller build() {
      return new VaultServiceRolesInstaller(this);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy