Skip to content

Commit 8733e88

Browse files
committed
docs: adds decision record for HashiCorp Vault auth refactor
1 parent 7eb2ef7 commit 8733e88

File tree

1 file changed

+243
-0
lines changed
  • docs/developer/decision-records/2024-12-13-vault-authentication-refactor

1 file changed

+243
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Hashicorp Vault authentication refactor
2+
3+
## Decision
4+
5+
Refactor the HashiCorp Vault extension and make it extensible with different methods of authentication.
6+
7+
## Rationale
8+
9+
The current implementation of the authentication in the HashiCorp Vault extension has no way to add new authentication methods.
10+
The full list of possible authentication methods can be found in the [HashiCorp Vault Documentation](https://developer.hashicorp.com/vault/docs/auth)
11+
Relevant examples are:
12+
13+
* [Token auth](https://developer.hashicorp.com/vault/docs/auth/token)
14+
* [Kubernetes auth](https://developer.hashicorp.com/vault/docs/auth/kubernetes)
15+
* [AppRole auth](https://developer.hashicorp.com/vault/docs/auth/approle)
16+
17+
It goes against the EDC extensibility model and needs to be remedied.
18+
Additionally, extracting the current implementation of the token authentication into its own extension will improve readability and maintainability of the code.
19+
20+
## Approach
21+
22+
The refactor will affect only the `vault-hashicorp` extension.
23+
It will create one additional extension called `vault-hashicorp-auth-tokenbased` that contains the token authentication and token renewal functionality.
24+
25+
To allow an extensible authentication for HashiCorp Vault, the implementation will follow the registry pattern.
26+
27+
### Hashicorp Vault Auth SPI
28+
29+
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.
30+
31+
It will contain the following interfaces:
32+
33+
* [Hashicorp Vault Auth Interface](#hashicorp-vault-auth-interface)
34+
* [Hashicorp Vault Auth Registry Interface](#hashicorp-vault-auth-registry)
35+
36+
### Hashicorp Vault Auth Interface
37+
38+
To implement a multitude of different authentication methods, an interface for the Hashicorp Vault authentication is needed.
39+
The sole common point between the authentication methods is the ability to provide a `client_token` that can be used to authenticate with HashiCorp Vault.
40+
41+
```java
42+
public interface HashicorpVaultAuth {
43+
44+
// The stored token is returned.
45+
String vaultToken();
46+
47+
}
48+
```
49+
50+
`HashicorpVaultAuth` implementations will be registered in `HashicorpVaultAuthRegistry` and used by the `HashicorpVaultClient` to receive the `client_token` for the request authentication.
51+
More on that in the sections [Hashicorp Vault Auth Implementation Registration](#Hashicorp-Vault-Auth-Registration) and [HashiCorp Vault Client](#HashiCorp-Vault-Client)
52+
53+
### Haschicorp Vault Auth Service Extension
54+
55+
For every authentication method, an implementation of the [Hashicorp Vault Auth interface](#hashicorp-vault-auth-interface) is needed.
56+
Each `HashicorpVaultAuth` implementation represents a different authentication method and is packaged inside its own service extension.
57+
In this way, it can easily be added/removed from the runtime and maintained in one place.
58+
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.
59+
60+
### Simple Token Auth
61+
62+
To keep the HashiCorp Vault Extension functional on its own for demo and testing purposes, `SimpleTokenAuth` is implemented.
63+
`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.
64+
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.
65+
66+
```java
67+
public class SimpleTokenAuth implements HashicorpVaultAuth {
68+
private String vaultToken;
69+
70+
SimpleTokenAuth(String vaultToken) {
71+
this.vaultToken = vaultToken;
72+
}
73+
74+
@Override
75+
public String vaultToken() {
76+
return vaultToken;
77+
}
78+
}
79+
```
80+
81+
### Hashicorp Vault Auth Registry
82+
83+
In line with the registry pattern, `HashicorpVaultAuthRegistry` and `HashicorpVaultAuthRegistryImpl` are created.
84+
The `HashicorpVaultAuthRegistry` will be used to store one or more implementations of `HashicorpVaultAuth`, each representing a different authentication method.
85+
More on the usage of the `HashicorpVaultAuthRegistry` for registration in the [HashiCorp Vault Auth Registration](#hashicorp-vault-auth-registration) section.
86+
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.
87+
88+
```java
89+
public interface HashicorpVaultAuthRegistry {
90+
91+
void register(String method, HashicorpVaultAuth authImplementation);
92+
93+
@NotNull
94+
HashicorpVaultAuth resolve(String method);
95+
96+
boolean hasMethod(String method);
97+
}
98+
```
99+
100+
```java
101+
public class HashicorpVaultAuthRegistryImpl implements HashicorpVaultAuthRegistry {
102+
103+
private final Map<String, HashicorpVaultAuth> services = new HashMap<>();
104+
105+
public HashicorpVaultAuthRegistryImpl() {
106+
}
107+
108+
@Override
109+
public void register(String method, HashicorpVaultAuth service) {
110+
services.put(method, service);
111+
}
112+
113+
@Override
114+
public @NotNull HashicorpVaultAuth resolve(String method) {
115+
if (services.get(method) == null){
116+
throw new IllegalArgumentException(String.format("No authentication method registered under %s", method));
117+
}
118+
return services.get(method);
119+
}
120+
121+
@Override
122+
public boolean hasMethod(String method) {
123+
return services.containsKey(method);
124+
}
125+
}
126+
```
127+
128+
### Hashicorp Vault Auth Registration
129+
130+
During the `initialize()` call, service extensions providing an auth method will register an instance of their `HashicorpVaultAuth` implementation in the `HashicorpVaultAuthRegistry`.
131+
The `HashicorpVaultAuthRegistry` is provided to the service extension through use of the provider pattern by the `HashicorpVaultExtension`.
132+
133+
```java
134+
@Provider
135+
public HashicorpVaultAuthRegistry hashicorpVaultAuthRegistry() {
136+
return new HashicorpVaultAuthRegistryImpl();
137+
}
138+
```
139+
140+
Inside the service extension providing an `HashicorpVaultAuth` implementation, the `HashicorpVaultAuthRegistry` is injected.
141+
142+
```java
143+
@Inject
144+
private HashicorpVaultAuthRegistry hashicorpVaultAuthRegistry;
145+
```
146+
147+
The injected `HashicorpVaultAuthRegistry` is used to register the `HashicorpVaultAuth` implementation.
148+
149+
```java
150+
@Override
151+
public void initialize(ServiceExtensionContext context) {
152+
var token = context.getSetting(VAULT_TOKEN, null);
153+
154+
if (hashicorpVaultAuthRegistry.hasMethod("token-based")) {
155+
throw new EdcException("Authentication method token-based is already registered");
156+
}
157+
158+
hashicorpVaultAuthRegistry.register("token-based", HashicorpVaultTokenAuth(token));
159+
160+
}
161+
```
162+
163+
### Configuration
164+
165+
A new config value is introduced to the HashiCorp Vault Extension named `edc.vault.hashicorp.auth.method`.
166+
`edc.vault.hashicorp.auth.method` governs which `HashicorpVaultAuth` implementation is used from `HashicorpVaultAuthRegistry` and is persisted in `HashicorpVaultSettings`.
167+
168+
For testing and demo purposes, another setting called `edc.vault.hashicorp.auth.token.fallback` is introduced.
169+
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.
170+
171+
### HashiCorp Vault Client
172+
173+
Since the `HashicorpVaultClient` contains a lot of authentication logic, it will also go through a refactoring.
174+
The goal of the refactoring, is the removal of the token authentication logic from `HashicorpVaultClient` and to make the authentication logic interchangeable.
175+
`VaultAuthenticationRegistry` is passed to `HashicorpVaultClient` during creation by `HashicorpVaultServiceExtension`.
176+
`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.
177+
`client_token` will then be used to generate the Headers for the HTTP requests and to authenticate with HashiCorp Vault.
178+
179+
Old `getHeaders()`:
180+
181+
```java
182+
@NotNull
183+
private Headers getHeaders() {
184+
var headersBuilder = new Headers.Builder().add(VAULT_REQUEST_HEADER, Boolean.toString(true));
185+
headersBuilder.add(VAULT_TOKEN_HEADER, settings.token());
186+
return headersBuilder.build();
187+
}
188+
```
189+
190+
New `getHeaders()`:
191+
192+
```java
193+
@NotNull
194+
private Headers getHeaders() {
195+
var headersBuilder = new Headers.Builder().add(VAULT_REQUEST_HEADER, Boolean.toString(true));
196+
headersBuilder.add(VAULT_TOKEN_HEADER, vaultAuthenticationRegistry.resolve(settings.getAuthMethod()).vaultToken());
197+
return headersBuilder.build();
198+
}
199+
```
200+
201+
An important change that was made, is the point at which the headers are generated.
202+
Previously, the headers were generated only once since the token was not liable to change.
203+
With the addition of numerous potential authentication mechanisms, it can no longer be guaranteed, that the token never changes during refresh.
204+
An example for this would be the kubernetes authentication method, where only short term tokens are produced each time.
205+
As such, `headers` are no longer saved as a variable inside the `HashicorpVaultClient` and `getHeaders()` is called instead to always fetch the newest token.
206+
207+
### HashiCorp Vault Extension refactor
208+
209+
The token based authentication logic is refactored and moved into its own extension named `HashiCorp Vault Token Auth`.
210+
This includes the token refresh functionality and will lead to a refactoring of the following classes:
211+
212+
`HashicorpVaultExtension.java`: `initialize()` no longer will start the token refresh and `stop()` will be removed since the extension does not govern token refresh anymore.
213+
214+
`HashicorpVaultClient.java`: `isTokenRenewable()` and `renewToken()` and their private methods will be moved to a new dedicated client in the Token Auth Extension.
215+
216+
`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).
217+
218+
`HashicorpVaultTokenRenewTask.java`: is moved to the HashiCorp Vault Token Auth Extension in its entirety.
219+
220+
The respective tests covering the moved functionality are also moved accordingly.
221+
222+
### HashiCorp Vault Token Auth Extension
223+
224+
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.
225+
226+
#### HashiCorp Vault Token Auth Extension Settings
227+
228+
The following settings will be moved from the HashiCorp Vault Extension to th HashiCorp Vault Token Auth Extension.
229+
230+
Duplicate setting from the HashiCorp Vault Extension, since the HashiCorp Vault Token Auth Extension also needs access to the vault.
231+
`edc.vault.hashicorp.url`
232+
233+
Settings moved from the HashiCorp Vault Extension, that only concern the token storage and token refresh.
234+
`edc.vault.hashicorp.token`
235+
`edc.vault.hashicorp.token.scheduled-renew-enabled`
236+
`edc.vault.hashicorp.token.ttl`
237+
`edc.vault.hashicorp.token.renew-buffer`
238+
239+
### HashiCorp Vault Health Extension
240+
241+
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.
242+
After the refactor, the token renewal is no longer a part of the base `HashicorpVaultExtension`.
243+
As such the `get()` method is adjusted and will only return the response from the Hashicorp Vault Health API.

0 commit comments

Comments
 (0)