spring-cloud / spring-cloud-vault

Configuration Integration with HashiCorp Vault
http://cloud.spring.io/spring-cloud-vault/
Apache License 2.0
276 stars 150 forks source link

Add support for multiple database secrets generation #459

Closed paskos closed 2 years ago

paskos commented 4 years ago

Is your feature request related to a problem? Please describe. I'm working on multiple spring-boot based micro-services that use mariadb to store relational data. it uses liquibase for schema/data migration. Our company has a policy where 'business' data access code (spring-data @Repository) and 'admin' data access code (liquibase) cannot have the same database grants and therefore the same credentials.

I tried to declare 2 spring.cloud.vault.database config elements but my app won't start due to yaml parsing issues.

Describe the solution you'd like Ideally it would be nice if spring-cloud-vault-databases could support something along the way of:

spring.cloud.vault:
    databases:
        - role: mariadb-business
          backend: mariadb-business
          username-property: spring.datasource.username
          password-property: spring.datasource.password 
        - role: mariadb-admin
          backend: mariadb-admin
          username-property: spring.liquibase.user
          password-property: spring.liquibase.password

Describe alternatives you've considered I just started using spring-cloud-vault, I'd love to get a pointer on how to implement this.

mp911de commented 4 years ago

Thanks for your suggestion. We also thought it would be nice to have such a feature. Do you want to try to come up with a pull request?

paskos commented 4 years ago

@mp911de I tried to add configuration support for a collection of VaultSecretBackendDescriptor or even DatabaseSecretProperties. I started with assumption that creating VaultDatabasesProperties containing a List of VaultDatabaseProperties and then adding support for it. But I kept getting pulled deeper into making changes in spring-cloud-vault-config project. After 2 days I came to the conclusion that I was doing it wrong or that I misunderstood the library design.

mp911de commented 4 years ago

Spring Cloud Vault is organized in a way that there are VaultSecretBackendDescriptors (typically backed by properties) and SecretBackendMetadataFactory (transforms VaultSecretBackendDescriptor into a SecretBackendMetadata descriptor that tells Spring Cloud Vault which Vault path to fetch and what property transformations to apply).

Modelling multiple spring.cloud.vault.databases would require a @ConfigurationProperties at spring.cloud.vault with a List or Map named databases. Your example above maps to a list. Right now, we have a 1:1 mapping between VaultSecretBackendDescriptor and SecretBackendMetadataFactory in terms of multiplicity. One VaultSecretBackendDescriptor results in a single SecretBackendMetadata.

What we would require here is a component that returns multiple VaultSecretBackendDescriptor from the config properties object so that we are able to pick up the individual database configuration items and transform these later on into SecretBackendMetadatas.

erikgollot commented 2 years ago

Hi, we've the same requirement. We found a solution, extend DatabaseSecretProperties class (see below) and add this class into a spring.factories file (org.springframework.cloud.bootstrap.BootstrapConfiguration=.infrastructure.config.vault.VaultFlywayProperties)

My question is : is DatabaseSecretProperties a lasting class ?

Our class for flyway dedicated secrets:

@Profile("!local")
@ConfigurationProperties("spring.cloud.vault.flyway")
@Configuration
public class VaultFlywayProperties implements DatabaseSecretProperties {

    /**
     * Enable flyway backend usage.
     */
    private boolean enabled = false;

    /**
     * Role name for credentials.
     */
    @NonNull
    private String role;

    /**
     * postgresql backend path.
     */

    @NonNull
    private String backend;

    /**
     * Target property for the obtained username.
     */
    private String usernameProperty = "spring.flyway.user";

    /**
     * Target property for the obtained username.
     */
    private String passwordProperty = "spring.flyway.password";

    @Override
    public boolean isEnabled() {
        return this.enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    @Override
    public String getRole() {
        return this.role;
    }

    public void setRole(@Nullable String role) {
        this.role = role;
    }

    @Override
    public boolean isStaticRole() {
        return false;
    }

    @Override
    public String getBackend() {
        return this.backend;
    }

    public void setBackend(String backend) {
        this.backend = backend;
    }

    @Override
    public String getUsernameProperty() {
        return this.usernameProperty;
    }

    public void setUsernameProperty(String usernameProperty) {
        this.usernameProperty = usernameProperty;
    }

    @Override
    public String getPasswordProperty() {
        return this.passwordProperty;
    }

    public void setPasswordProperty(String passwordProperty) {
        this.passwordProperty = passwordProperty;
    }

}