pivotal-cf / java-cfenv

Apache License 2.0
91 stars 56 forks source link

EnvProcessor for vault service is not being executed #168

Closed artsiom-tsaryonau closed 2 years ago

artsiom-tsaryonau commented 2 years ago

I have a processor that supposed to process vault configuration from VCAP_SERVICES property.

public class VaultServiceProcessor implements CfEnvProcessor {
        @Override
        public boolean accept(CfService service) {
            return service.existsByLabelStartsWith("cf-vault");
        }

        @Override
        public void process(CfCredentials cfCredentials, Map<String, Object> properties) {
            properties.put("endpoint", cfCredentials.getString("endpoint"));
            properties.put("role_id", cfCredentials.getString("role_id"));
            properties.put("secret_id", cfCredentials.getString("secret_id"));
            properties.put("org_secret_path", cfCredentials.getString("org_secret_path"));
            properties.put("space_secret_path", cfCredentials.getString("space_secret_path"));
            properties.put("service_secret_path", cfCredentials.getString("service_secret_path"));

            properties.put("spring.cloud.vault.host", cfCredentials.getString("endpoint"));
            String port = cfCredentials.getString("port"); // does not exist
            if (null == port) { // are there cases when it is not https?
                port = cfCredentials.getString("endpoint").startsWith("https") ? "443" : "80";
            }
            properties.put("spring.cloud.vault.port", port);
            properties.put("spring.cloud.vault.scheme", "443".equals(port) ? "https" : "http");

            properties.put("spring.cloud.vault.authentication", "APPROLE");
            properties.put("spring.cloud.vault.app-role.role-id", cfCredentials.getString("role_id"));
            properties.put("spring.cloud.vault.app-role.secret-id", cfCredentials.getString("secret_id"));
        }

        @Override
        public CfEnvProcessorProperties getProperties() {
            return CfEnvProcessorProperties.builder()
                .propertyPrefixes("core.application.vault,spring.cloud.vault")
                .serviceName("vault") // not sure if really needed
                .build();
        }
    }

As far as I understand this env processor supposed to populate properties map which is Spring configuration (same as in application.yml for example).

I have specified processor in META-INF

META-INF/
    services/
        spring.factories
    spring.factories

In folder services the file contains a single like this

io.pivotal.cfenv.spring.boot.CfEnvProcessor=\
core.application.vault.cloud.cfprocessor.VaultServiceProcessor

While another spring.factories contains this

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
core.application.vault.config.MultiBackendsVaultConnectorBootstrapConfiguration

I specified configuration object (to have properties in some object at least)

@Profile("cloud")
@Configuration
@ConfigurationProperties("core.application.vault")
public class VaultCloudProperties {
    private String roleId;
    private String secretId;
    private String orgSecretPath;
    private String spaceSecretPath;
    private String serviceSecretPath;

    public String getRoleId() {
        return roleId;
    }

    public void setRoleId(String roleId) {
        this.roleId = roleId;
    }

    public String getSecretId() {
        return secretId;
    }

    public void setSecretId(String secretId) {
        this.secretId = secretId;
    }

    public String getOrgSecretPath() {
        return orgSecretPath;
    }

    public void setOrgSecretPath(String orgSecretPath) {
        this.orgSecretPath = orgSecretPath;
    }

    public String getSpaceSecretPath() {
        return spaceSecretPath;
    }

    public void setSpaceSecretPath(String spaceSecretPath) {
        this.spaceSecretPath = spaceSecretPath;
    }

    public String getServiceSecretPath() {
        return serviceSecretPath;
    }

    public void setServiceSecretPath(String serviceSecretPath) {
        this.serviceSecretPath = serviceSecretPath;
    }

    @Override
    public String toString() {
        return "VaultServiceInfo [roleId=" + roleId + ", secretId=" + secretId + ", orgSecretPath="
            + orgSecretPath + ", spaceSecretPath=" + spaceSecretPath + ", serviceSecretPath="
            + serviceSecretPath + "]";
    }
}

I have this bean that supposed to deal with vault properties that came from VCAP_SERIVCES

@Configuration
@Profile("cloud")
@ConditionalOnProperty(name = "spring.cloud.vault.enabled", matchIfMissing = false)
@EnableConfigurationProperties(VaultGenericBackendProperties.class)
// @Import(VaultBootstrapConfiguration.class)
@Order(Ordered.LOWEST_PRECEDENCE - 100)
public class MultiBackendsVaultConnectorBootstrapConfiguration {

  private static final Logger log =
      LoggerFactory.getLogger(MultiBackendsVaultConnectorBootstrapConfiguration.class);
  private static final String VERSION_BACKEND_PREFIX = "^/v1/";

  @Bean
  public VaultConfigurer cloudVaultConfigurer(Environment environment/*, VaultCloudProperties vaultProperties*/) {

    List<SecretBackendMetadata> backends = new ArrayList<>();
    String keyName = environment.getProperty("spring.cloud.vault.defaultKey");

    // failed to inject VaultCloudProperties as it could not find the bean
    backends.add(
      GenericSecretBackendMetadata
        .create(environment.getProperty("core.application.vault.service_secret_path")
            .replaceAll(VERSION_BACKEND_PREFIX, ""), keyName));
    backends.add(
      GenericSecretBackendMetadata
        .create(environment.getProperty("core.application.vault.space_secret_path")
            .replaceAll(VERSION_BACKEND_PREFIX, ""), keyName));
    backends.add(
      GenericSecretBackendMetadata
        .create(environment.getProperty("core.application.vault.org_secret_path")
            .replaceAll(VERSION_BACKEND_PREFIX, ""), keyName));

    return new VaultConnectorConfigurer(backends);
  }

   static class VaultConnectorConfigurer implements VaultConfigurer {

    private final Collection<SecretBackendMetadata> backends;

    VaultConnectorConfigurer(Collection<SecretBackendMetadata> backends) {

      this.backends = backends;
    }

    @Override
    public void addSecretBackends(SecretBackendConfigurer configurer) {

      for (SecretBackendMetadata metadata : backends) {
        configurer.add(metadata);
      }
      configurer.registerDefaultDiscoveredSecretBackends(true);
    }
  }
}

I commented VaultBootstrapConfiguration.class configuration because after moving away from connectors to cf-env it is unable instantiate clientAuthentication anymore.

But when I run the application, it tries to instantiate VaultConnectorConfigurer but fails to do that. And debug stop in VaultServiceProcessor is never being executed (just like I cannot autowire VaultCloudProperties).

What might be the reason?

artsiom-tsaryonau commented 2 years ago

Ok, when I remove file in services folder and put everything into a single spring.factories file, it started to pick up processor.