Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

token-auth: add vault support #1545

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ in the detailed section referring to by linking pull requests or issues.
* Refactored query capabilities for `Asset` (#1459)
* Refactored query capabilities for `ContractDefinition` (#1458)
* Refactored state machine and in-memory persistence (#1511)
* Token based Authentication can retrieve key from vault (#1537)
* JWT audience claim check with DID (#1520)

#### Removed
Expand Down
21 changes: 21 additions & 0 deletions extensions/api/auth-tokenbased/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Token Based Authentication Service

The token based authentication service extension is used to secure connector APIs. These APIs are not protected by the `AuthenticationService` by default. To find out how a specific API is protected please consult its documentation.

APIs, protected by this extension, require a client to authenticate by adding a authentication key to the request header.

Authentication Header Example:
```
curl <url> --header "X-API-Key: <key>"
```

## Configuration

| Key | Description | Required |
|:-----------------------|:-------------------------------------------------------------|:---------|
| edc.api.auth.key | API Key Header Value | false |
| edc.api.auth.key.alias | Secret name of the API Key Header Value, stored in the vault | false |

- If the API key is stored in the Vault _and_ in the configuration, the extension will take the key from the vault.

- If no API key is defined, a random value is generated and printed out into the logs.
2 changes: 2 additions & 0 deletions extensions/api/auth-tokenbased/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ plugins {
dependencies {
api(project(":extensions:api:auth-spi"))
implementation("jakarta.ws.rs:jakarta.ws.rs-api:${rsApi}")

testImplementation(project(":extensions:junit"))
}

publishing {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
*
* Contributors:
* Microsoft Corporation - initial API and implementation
* Mercedes-Benz Tech Innovation GmbH - add README.md; authentication key can be retrieved from vault
*
*/

package org.eclipse.dataspaceconnector.api.auth;

import org.eclipse.dataspaceconnector.spi.EdcSetting;
import org.eclipse.dataspaceconnector.spi.security.Vault;
import org.eclipse.dataspaceconnector.spi.system.Inject;
import org.eclipse.dataspaceconnector.spi.system.Provides;
import org.eclipse.dataspaceconnector.spi.system.ServiceExtension;
import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext;
Expand All @@ -27,11 +31,30 @@
*/
@Provides(AuthenticationService.class)
public class TokenBasedAuthenticationExtension implements ServiceExtension {

@EdcSetting
private static final String AUTH_SETTING_APIKEY = "edc.api.auth.key";

@EdcSetting
private static final String AUTH_SETTING_APIKEY_ALIAS = "edc.api.auth.key.alias";

@Inject
private Vault vault;

@Override
public void initialize(ServiceExtensionContext context) {
var apiKey = context.getSetting(AUTH_SETTING_APIKEY, UUID.randomUUID().toString());

String apiKey = null;

var apiKeyAlias = context.getSetting(AUTH_SETTING_APIKEY_ALIAS, null);

if (apiKeyAlias != null) {
apiKey = vault.resolveSecret(apiKeyAlias);
}

if (apiKey == null) {
apiKey = context.getSetting(AUTH_SETTING_APIKEY, UUID.randomUUID().toString());
}

context.getMonitor().info(format("API Authentication: using static API Key '%s'", apiKey));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright (c) 2022 Mercedes-Benz Tech Innovation GmbH
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Mercedes-Benz Tech Innovation GmbH - initial implementation
*
*/

package org.eclipse.dataspaceconnector.api.auth;

import org.eclipse.dataspaceconnector.junit.extensions.DependencyInjectionExtension;
import org.eclipse.dataspaceconnector.spi.security.Vault;
import org.eclipse.dataspaceconnector.spi.system.ServiceExtensionContext;
import org.eclipse.dataspaceconnector.spi.system.injection.ObjectFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(DependencyInjectionExtension.class)
public class TokenBasedAuthenticationExtensionTest {

private TokenBasedAuthenticationExtension extension;

private static final String AUTH_SETTING_APIKEY = "edc.api.auth.key";

private static final String AUTH_SETTING_APIKEY_ALIAS = "edc.api.auth.key.alias";

private static final String VAULT_KEY = "foo";

private Vault vaultMock;
private ServiceExtensionContext serviceExtensionContextMock;

@BeforeEach
void setup(ServiceExtensionContext context, ObjectFactory factory) {

serviceExtensionContextMock = spy(context); //used to inject the config
vaultMock = mock(Vault.class);

context.registerService(Vault.class, vaultMock);
context.registerService(ServiceExtensionContext.class, serviceExtensionContextMock);

when(vaultMock.resolveSecret(VAULT_KEY)).thenReturn("foo");

extension = factory.constructInstance(TokenBasedAuthenticationExtension.class);
}

@Test
public void testPrimaryMethod_loadKeyFromVault() {
setAuthSettingApiKeyAlias(VAULT_KEY);
setAuthSettingApiKey("bar");

extension.initialize(serviceExtensionContextMock);

verify(serviceExtensionContextMock, never())
.getSetting(eq(AUTH_SETTING_APIKEY), anyString());

verify(serviceExtensionContextMock)
.getSetting(AUTH_SETTING_APIKEY_ALIAS, null);

verify(vaultMock).resolveSecret(VAULT_KEY);
}

@Test
public void testSecondaryMethod_loadKeyFromConfig() {

setAuthSettingApiKeyAlias(null);
setAuthSettingApiKey("bar");

extension.initialize(serviceExtensionContextMock);

verify(serviceExtensionContextMock)
.getSetting(eq(AUTH_SETTING_APIKEY), anyString());

verify(serviceExtensionContextMock)
.getSetting(AUTH_SETTING_APIKEY_ALIAS, null);

verify(vaultMock, never()).resolveSecret(anyString());
}

private void setAuthSettingApiKey(String value) {
when(serviceExtensionContextMock.getSetting(eq(AUTH_SETTING_APIKEY), anyString()))
.thenReturn(value);
}

private void setAuthSettingApiKeyAlias(String value) {
when(serviceExtensionContextMock.getSetting(eq(AUTH_SETTING_APIKEY_ALIAS), isNull()))
.thenReturn(value);
}
}