Skip to content

Commit

Permalink
docs: changes registry pattern to provider with default
Browse files Browse the repository at this point in the history
  • Loading branch information
SaschaIsele committed Jan 13, 2025
1 parent 5369022 commit 2c2a490
Showing 1 changed file with 26 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,11 @@ Relevant examples are:
* [Kubernetes auth](https://developer.hashicorp.com/vault/docs/auth/kubernetes)
* [AppRole auth](https://developer.hashicorp.com/vault/docs/auth/approle)

It goes against the EDC extensibility model and needs to be remedied.
Additionally, extracting the current implementation of the token authentication into its own extension will improve readability and maintainability of the code.

## Approach

The refactor will affect only the `vault-hashicorp` extension.
It will create one additional extension called `vault-hashicorp-auth-tokenbased` that contains the token authentication and token renewal functionality.

To allow an extensible authentication for HashiCorp Vault, the implementation will follow the registry pattern.

### Hashicorp Vault Auth SPI

For the proper organisation of the interfaces needed for the refactoring, a new module named `hashicorp-vault-auth-spi` is introduced in the `spi` directory.

It will contain the following interfaces:

* [Hashicorp Vault Auth Interface](#hashicorp-vault-auth-interface)
* [Hashicorp Vault Auth Registry Interface](#hashicorp-vault-auth-registry)
To allow an extensible authentication for HashiCorp Vault, the implementation will follow the provider pattern with a default implementation.

### Hashicorp Vault Auth Interface

Expand All @@ -47,133 +34,43 @@ public interface HashicorpVaultAuth {
}
```

`HashicorpVaultAuth` implementations will be registered in `HashicorpVaultAuthRegistry` and used by the `HashicorpVaultClient` to receive the `client_token` for the request authentication.
More on that in the sections [Hashicorp Vault Auth Implementation Registration](#Hashicorp-Vault-Auth-Registration) and [HashiCorp Vault Client](#HashiCorp-Vault-Client)
`HashicorpVaultAuth` implementations will be used by the `HashicorpVaultClient` to receive the `client_token` for the request authentication.
More on that in the sections [Hashicorp Vault Auth Provision](#Hashicorp-Vault-Auth-Provision) and [HashiCorp Vault Client](#HashiCorp-Vault-Client)

### Haschicorp Vault Auth Service Extension
### Haschicorp Vault Auth Extensions

For every authentication method, an implementation of the [Hashicorp Vault Auth interface](#hashicorp-vault-auth-interface) is needed.
Each `HashicorpVaultAuth` implementation represents a different authentication method and is packaged inside its own service extension.
In this way, it can easily be added/removed from the runtime and maintained in one place.
Due to the possible differences in the needed configuration of different authentication methods, each Service Extension will need its own configuration values specific to the authentication method.
The exception will be the token authentication, which is already packaged inside the `HashicorpVaultExtension` and used as the default authentication method.

### Simple Token Auth
### Hashicorp Vault Auth Provision

To keep the HashiCorp Vault Extension functional on its own for demo and testing purposes, `SimpleTokenAuth` is implemented.
`SimpleTokenAuth` is a very rudimentary implementation of the [Hashicorp Vault Auth interface](#hashicorp-vault-auth-interface), that only stores a token and does not offer any refresh functionality.
It is always added to the registry and will be used when `fallbackToken` is selected as authentication method, more on that in the [Configuration](#configuration) section.
The `HashicorpVaultTokenAuth` implementation will serve as default authentication mechanism and will be provided by the `HashicorpVaultExtension`.

```java
public class SimpleTokenAuth implements HashicorpVaultAuth {
private String vaultToken;

SimpleTokenAuth(String vaultToken) {
this.vaultToken = vaultToken;
}

@Override
public String vaultToken() {
return vaultToken;
}
@Provider(isDefault = true)
public HashicorpVaultAuth hashicorpVaultAuth() {
return new HashicorpVaultTokenAuth();
}
```

### Hashicorp Vault Auth Registry

In line with the registry pattern, `HashicorpVaultAuthRegistry` and `HashicorpVaultAuthRegistryImpl` are created.
The `HashicorpVaultAuthRegistry` will be used to store one or more implementations of `HashicorpVaultAuth`, each representing a different authentication method.
More on the usage of the `HashicorpVaultAuthRegistry` for registration in the [HashiCorp Vault Auth Registration](#hashicorp-vault-auth-registration) section.
The `HashicorpVaultAuthRegistryImpl` is added to the constructor of `HashicorpVaultClient` and can then be used to retrieve the `vaultToken` for the Header creation, more in that in the [HashiCorp Vault Client](#hashicorp-vault-client) section.

```java
public interface HashicorpVaultAuthRegistry {

void register(String method, HashicorpVaultAuth authImplementation);

@NotNull
HashicorpVaultAuth resolve(String method);

boolean hasMethod(String method);
}
```

```java
public class HashicorpVaultAuthRegistryImpl implements HashicorpVaultAuthRegistry {

private final Map<String, HashicorpVaultAuth> services = new HashMap<>();

public HashicorpVaultAuthRegistryImpl() {
}

@Override
public void register(String method, HashicorpVaultAuth service) {
services.put(method, service);
}

@Override
public @NotNull HashicorpVaultAuth resolve(String method) {
if (services.get(method) == null){
throw new IllegalArgumentException(String.format("No authentication method registered under %s", method));
}
return services.get(method);
}

@Override
public boolean hasMethod(String method) {
return services.containsKey(method);
}
}
```

### Hashicorp Vault Auth Registration

During the `initialize()` call, service extensions providing an auth method will register an instance of their `HashicorpVaultAuth` implementation in the `HashicorpVaultAuthRegistry`.
The `HashicorpVaultAuthRegistry` is provided to the service extension through use of the provider pattern by the `HashicorpVaultExtension`.

```java
@Provider
public HashicorpVaultAuthRegistry hashicorpVaultAuthRegistry() {
return new HashicorpVaultAuthRegistryImpl();
}
```
`isDefault = true` signifies, that `HashicorpVaultTokenAuth` will be used unless another extension overwrites it by providing its own implementation without the parameter.

Inside the service extension providing an `HashicorpVaultAuth` implementation, the `HashicorpVaultAuthRegistry` is injected.
The provided implementation of `HashicorpVaultAuth` is then injected into the `HashicorpVaultExtension` to be used in the `HashicorpVaultClient` later on.

```java
@Inject
private HashicorpVaultAuthRegistry hashicorpVaultAuthRegistry;
```

The injected `HashicorpVaultAuthRegistry` is used to register the `HashicorpVaultAuth` implementation.

```java
@Override
public void initialize(ServiceExtensionContext context) {
var token = context.getSetting(VAULT_TOKEN, null);

if (hashicorpVaultAuthRegistry.hasMethod("token-based")) {
throw new EdcException("Authentication method token-based is already registered");
}

hashicorpVaultAuthRegistry.register("token-based", HashicorpVaultTokenAuth(token));

}
private HashicorpVaultAuth hashicorpVaultAuth;
```

### Configuration

A new config value is introduced to the HashiCorp Vault Extension named `edc.vault.hashicorp.auth.method`.
`edc.vault.hashicorp.auth.method` governs which `HashicorpVaultAuth` implementation is used from `HashicorpVaultAuthRegistry` and is persisted in `HashicorpVaultSettings`.

For testing and demo purposes, another setting called `edc.vault.hashicorp.auth.token.fallback` is introduced.
In case, the HashiCorp Vault Extension is used on its own, this setting can be used to store a token for authentication without any refresh mechanism.

### HashiCorp Vault Client

Since the `HashicorpVaultClient` contains a lot of authentication logic, it will also go through a refactoring.
The goal of the refactoring, is the removal of the token authentication logic from `HashicorpVaultClient` and to make the authentication logic interchangeable.
`VaultAuthenticationRegistry` is passed to `HashicorpVaultClient` during creation by `HashicorpVaultServiceExtension`.
`HashicorpVaultClient` will use `VaultAuthenticationRegistry` based on `edc.vault.hashicorp.auth.method` setting to fetch the `client_token` that is provided by the chosen `HashicorpVaultAuth` implementation.
An implementation of `HashicorpVaultAuth` is passed to `HashicorpVaultClient` during creation by `HashicorpVaultExtension`.
`HashicorpVaultClient` will use the `HashicorpVaultAuth` implementation to fetch the `client_token`.
`client_token` will then be used to generate the Headers for the HTTP requests and to authenticate with HashiCorp Vault.

Old `getHeaders()`:
Expand All @@ -193,51 +90,36 @@ New `getHeaders()`:
@NotNull
private Headers getHeaders() {
var headersBuilder = new Headers.Builder().add(VAULT_REQUEST_HEADER, Boolean.toString(true));
headersBuilder.add(VAULT_TOKEN_HEADER, vaultAuthenticationRegistry.resolve(settings.getAuthMethod()).vaultToken());
headersBuilder.add(VAULT_TOKEN_HEADER, vaultAuth.vaultToken());
return headersBuilder.build();
}
```

An important change that was made, is the point at which the headers are generated.
Previously, the headers were generated only once since the token was not liable to change.
With the addition of numerous potential authentication mechanisms, it can no longer be guaranteed, that the token never changes during refresh.
An example for this would be the kubernetes authentication method, where only short term tokens are produced each time.
An example for this would be the kubernetes authentication method, where short term tokens can be produced depending on the settings.
As such, `headers` are no longer saved as a variable inside the `HashicorpVaultClient` and `getHeaders()` is called instead to always fetch the newest token.

### HashiCorp Vault Extension refactor
### HashiCorp Vault Extension Refactor

The token based authentication logic is refactored and moved into its own extension named `HashiCorp Vault Token Auth`.
The token based authentication logic is refactored and moved into dedicated classes in the newly created `auth` package.
This includes the token refresh functionality and will lead to a refactoring of the following classes:

`HashicorpVaultExtension.java`: `initialize()` no longer will start the token refresh and `stop()` will be removed since the extension does not govern token refresh anymore.

`HashicorpVaultClient.java`: `isTokenRenewable()` and `renewToken()` and their private methods will be moved to a new dedicated client in the Token Auth Extension.
`HashicorpVaultClient.java`: `isTokenRenewable()` and `renewToken()` and their private methods will be moved to a new dedicated client class.

`HashicorpVaultSettings.java`: some settings are moved and two new ones are added as described in the sections [HashiCorp Vault Token Auth Extension Settings](#hashicorp-vault-token-auth-extension-settings) and [Configuration](#configuration).

`HashicorpVaultTokenRenewTask.java`: is moved to the HashiCorp Vault Token Auth Extension in its entirety.
`HashicorpVaultTokenRenewTask.java`: is moved to the `auth` package in its entirety.

The respective tests covering the moved functionality are also moved accordingly.

### HashiCorp Vault Token Auth Extension

The HashiCorp Vault Token Auth Extension will contain the token storage functionality and the token refresh functionality that has been removed from the HashiCorp Vault Extension.

#### HashiCorp Vault Token Auth Extension Settings

The following settings will be moved from the HashiCorp Vault Extension to th HashiCorp Vault Token Auth Extension.

Duplicate setting from the HashiCorp Vault Extension, since the HashiCorp Vault Token Auth Extension also needs access to the vault.
`edc.vault.hashicorp.url`

Settings moved from the HashiCorp Vault Extension, that only concern the token storage and token refresh.
`edc.vault.hashicorp.token`
`edc.vault.hashicorp.token.scheduled-renew-enabled`
`edc.vault.hashicorp.token.ttl`
`edc.vault.hashicorp.token.renew-buffer`

### HashiCorp Vault Health Extension

The `get()` method of `HashicorpVaultHealthCheck` is currently using a merge of the response from the Hashicorp Vault Health API and the ability to renew the given token.
After the refactor, the token renewal is no longer a part of the base `HashicorpVaultExtension`.
As such the `get()` method is adjusted and will only return the response from the Hashicorp Vault Health API.

## Further Considerations

This Decision Record only paves the way to add additional authentication methods later on.
As such, possible authentication methods that can be added in the future are not discussed in more detail here, but will require their own Decision Records containing the outline of the implementation and testing.

0 comments on commit 2c2a490

Please sign in to comment.