Skip to content

Commit

Permalink
token-auth: add vault support
Browse files Browse the repository at this point in the history
Signed-off-by: Dominik Pinsel <dominik.pinsel@daimler.com>
Co-authored-by: Paul Latzelsperger <43503240+paullatzelsperger@users.noreply.github.com>
  • Loading branch information
DominikPinsel and paullatzelsperger committed Jun 28, 2022
1 parent 385be86 commit a0d6d42
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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)

#### 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,105 @@
/*
* 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.times;
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, times(1))
.getSetting(AUTH_SETTING_APIKEY_ALIAS, null);

verify(vaultMock, times(1)).resolveSecret(VAULT_KEY);
}

@Test
public void testSecondaryMethod_loadKeyFromConfig() {

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

extension.initialize(serviceExtensionContextMock);

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

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

verify(vaultMock, times(0)).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);
}
}

0 comments on commit a0d6d42

Please sign in to comment.