-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: improve Public Key Resolver (#3799)
* feat: improves PublicKeyResolver * pr suggestions
- Loading branch information
Showing
17 changed files
with
455 additions
and
88 deletions.
There are no files selected for viewing
118 changes: 118 additions & 0 deletions
118
...tor-core/src/main/java/org/eclipse/edc/connector/core/LocalPublicKeyDefaultExtension.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* 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: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.core; | ||
|
||
import org.eclipse.edc.connector.core.security.LocalPublicKeyServiceImpl; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Extension; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Inject; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Provider; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Provides; | ||
import org.eclipse.edc.runtime.metamodel.annotation.Setting; | ||
import org.eclipse.edc.spi.EdcException; | ||
import org.eclipse.edc.spi.iam.LocalPublicKeyService; | ||
import org.eclipse.edc.spi.result.Result; | ||
import org.eclipse.edc.spi.security.KeyParserRegistry; | ||
import org.eclipse.edc.spi.security.Vault; | ||
import org.eclipse.edc.spi.system.ServiceExtension; | ||
import org.eclipse.edc.spi.system.ServiceExtensionContext; | ||
import org.eclipse.edc.spi.system.configuration.Config; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
import static java.util.function.Function.identity; | ||
import static org.eclipse.edc.connector.core.SecurityDefaultServicesExtension.NAME; | ||
|
||
@Extension(value = NAME) | ||
@Provides(LocalPublicKeyService.class) | ||
public class LocalPublicKeyDefaultExtension implements ServiceExtension { | ||
|
||
public static final String NAME = "Local Public Key Default Extension"; | ||
|
||
public static final String EDC_PUBLIC_KEYS_PREFIX = "edc.iam.publickeys"; | ||
|
||
public static final String CONFIG_ALIAS = EDC_PUBLIC_KEYS_PREFIX + ".<pkAlias>."; | ||
|
||
@Setting(context = CONFIG_ALIAS, value = "ID of the public key.", required = true) | ||
public static final String ID_SUFFIX = "id"; | ||
|
||
@Setting(context = CONFIG_ALIAS, value = "Value of the public key. Multiple formats are supported, depending on the KeyParsers registered in the runtime") | ||
public static final String VALUE_SUFFIX = "value"; | ||
|
||
@Setting(context = CONFIG_ALIAS, value = "Path to a file that holds the public key, e.g. a PEM file. Multiple formats are supported, depending on the KeyParsers registered in the runtime") | ||
public static final String PATH_SUFFIX = "path"; | ||
|
||
@Inject | ||
public KeyParserRegistry keyParserRegistry; | ||
|
||
private Config keysConfiguration; | ||
|
||
private LocalPublicKeyServiceImpl localPublicKeyService; | ||
@Inject | ||
private Vault vault; | ||
|
||
@Provider(isDefault = true) | ||
public LocalPublicKeyService localPublicKeyService() { | ||
return localPublicKeyServiceImpl(); | ||
} | ||
|
||
private LocalPublicKeyServiceImpl localPublicKeyServiceImpl() { | ||
if (localPublicKeyService == null) { | ||
localPublicKeyService = new LocalPublicKeyServiceImpl(vault, keyParserRegistry); | ||
} | ||
return localPublicKeyService; | ||
} | ||
|
||
@Override | ||
public void initialize(ServiceExtensionContext context) { | ||
keysConfiguration = context.getConfig(EDC_PUBLIC_KEYS_PREFIX); | ||
} | ||
|
||
@Override | ||
public void prepare() { | ||
if (keysConfiguration != null) { | ||
var result = keysConfiguration.partition().map(this::readPublicKey) | ||
.map(entry -> localPublicKeyServiceImpl().addRawKey(entry.getKey(), entry.getValue())) | ||
.reduce(Result.success(), Result::merge); | ||
|
||
result.orElseThrow((failure) -> new EdcException(failure.getFailureDetail())); | ||
} | ||
} | ||
|
||
private Map.Entry<String, String> readPublicKey(Config config) { | ||
var id = config.getString(ID_SUFFIX); | ||
return readFrom(config, VALUE_SUFFIX, identity()) | ||
.or(() -> readFrom(config, PATH_SUFFIX, this::readFromPath)) | ||
.map(key -> Map.entry(id, key)) | ||
.orElseThrow(() -> new EdcException("")); | ||
} | ||
|
||
private String readFromPath(String path) { | ||
try { | ||
return Files.readString(Path.of(path)); | ||
} catch (IOException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private Optional<String> readFrom(Config config, String setting, Function<String, String> mapper) { | ||
return Optional.ofNullable(config.getString(setting, null)) | ||
.map(mapper); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
72 changes: 72 additions & 0 deletions
72
...core/src/main/java/org/eclipse/edc/connector/core/security/LocalPublicKeyServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* 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: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.core.security; | ||
|
||
import org.eclipse.edc.spi.iam.LocalPublicKeyService; | ||
import org.eclipse.edc.spi.result.Result; | ||
import org.eclipse.edc.spi.security.KeyParserRegistry; | ||
import org.eclipse.edc.spi.security.Vault; | ||
|
||
import java.security.PublicKey; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
||
/** | ||
* Implementation of {@link LocalPublicKeyService} which looks-up for the key by id | ||
* first in the locally cached keys and as fallback in the vault. | ||
*/ | ||
public class LocalPublicKeyServiceImpl implements LocalPublicKeyService { | ||
private final Vault vault; | ||
|
||
private final KeyParserRegistry registry; | ||
|
||
private final Map<String, PublicKey> cachedKeys = new HashMap<>(); | ||
|
||
public LocalPublicKeyServiceImpl(Vault vault, KeyParserRegistry registry) { | ||
this.vault = vault; | ||
this.registry = registry; | ||
} | ||
|
||
@Override | ||
public Result<PublicKey> resolveKey(String id) { | ||
return resolveFromCache(id) | ||
.map(Result::success) | ||
.or(() -> resolveFromVault(id).map(this::parseKey)) | ||
.orElseGet(() -> Result.failure("No public key could be resolved for key-ID '%s'".formatted(id))); | ||
} | ||
|
||
private Optional<String> resolveFromVault(String id) { | ||
return Optional.ofNullable(vault.resolveSecret(id)); | ||
} | ||
|
||
private Optional<PublicKey> resolveFromCache(String id) { | ||
return Optional.ofNullable(cachedKeys.get(id)); | ||
} | ||
|
||
private Result<PublicKey> parseKey(String encodedKey) { | ||
return registry.parse(encodedKey).compose(pk -> { | ||
if (pk instanceof PublicKey publicKey) { | ||
return Result.success(publicKey); | ||
} else { | ||
return Result.failure("The specified resource did not contain public key material."); | ||
} | ||
}); | ||
} | ||
|
||
public Result<Void> addRawKey(String id, String rawKey) { | ||
return parseKey(rawKey).onSuccess((pk) -> cachedKeys.put(id, pk)).mapTo(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
...core/src/test/java/org/eclipse/edc/connector/core/LocalPublicKeyDefaultExtensionTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* Copyright (c) 2024 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) | ||
* | ||
* 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: | ||
* Bayerische Motoren Werke Aktiengesellschaft (BMW AG) - initial API and implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.core; | ||
|
||
import org.eclipse.edc.connector.core.security.LocalPublicKeyServiceImpl; | ||
import org.eclipse.edc.junit.extensions.DependencyInjectionExtension; | ||
import org.eclipse.edc.junit.testfixtures.TestUtils; | ||
import org.eclipse.edc.spi.EdcException; | ||
import org.eclipse.edc.spi.result.Result; | ||
import org.eclipse.edc.spi.security.KeyParserRegistry; | ||
import org.eclipse.edc.spi.system.ServiceExtensionContext; | ||
import org.eclipse.edc.spi.system.configuration.ConfigFactory; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
import java.security.PublicKey; | ||
import java.util.Map; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.eclipse.edc.connector.core.LocalPublicKeyDefaultExtension.EDC_PUBLIC_KEYS_PREFIX; | ||
import static org.eclipse.edc.junit.assertions.AbstractResultAssert.assertThat; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
|
||
@ExtendWith(DependencyInjectionExtension.class) | ||
class LocalPublicKeyDefaultExtensionTest { | ||
|
||
private final KeyParserRegistry keyParserRegistry = mock(); | ||
|
||
@BeforeEach | ||
void setUp(ServiceExtensionContext context) { | ||
context.registerService(KeyParserRegistry.class, keyParserRegistry); | ||
} | ||
|
||
@Test | ||
void localPublicKeyService(LocalPublicKeyDefaultExtension extension) { | ||
assertThat(extension.localPublicKeyService()).isInstanceOf(LocalPublicKeyServiceImpl.class); | ||
} | ||
|
||
@Test | ||
void localPublicKeyService_withValueConfig(LocalPublicKeyDefaultExtension extension, ServiceExtensionContext context) { | ||
|
||
var keys = Map.of( | ||
"key1.id", "key1", | ||
"key1.value", "value"); | ||
|
||
when(keyParserRegistry.parse("value")).thenReturn(Result.success(mock(PublicKey.class))); | ||
when(context.getConfig(EDC_PUBLIC_KEYS_PREFIX)).thenReturn(ConfigFactory.fromMap(keys)); | ||
var localPublicKeyService = extension.localPublicKeyService(); | ||
extension.initialize(context); | ||
extension.prepare(); | ||
|
||
assertThat(localPublicKeyService.resolveKey("key1")).isSucceeded(); | ||
} | ||
|
||
@Test | ||
void localPublicKeyService_withPathConfig(LocalPublicKeyDefaultExtension extension, ServiceExtensionContext context) { | ||
var path = TestUtils.getResource("rsa_2048.pem"); | ||
var value = TestUtils.getResourceFileContentAsString("rsa_2048.pem"); | ||
var keys = Map.of( | ||
"key1.id", "key1", | ||
"key1.path", path.getPath()); | ||
|
||
when(keyParserRegistry.parse(value)).thenReturn(Result.success(mock(PublicKey.class))); | ||
when(context.getConfig(EDC_PUBLIC_KEYS_PREFIX)).thenReturn(ConfigFactory.fromMap(keys)); | ||
var localPublicKeyService = extension.localPublicKeyService(); | ||
extension.initialize(context); | ||
extension.prepare(); | ||
|
||
assertThat(localPublicKeyService.resolveKey("key1")).isSucceeded(); | ||
} | ||
|
||
@Test | ||
void localPublicKeyService_shouldRaiseException_withoutValueOrPath(LocalPublicKeyDefaultExtension extension, ServiceExtensionContext context) { | ||
var keys = Map.of( | ||
"key1.id", "key1"); | ||
|
||
when(context.getConfig(EDC_PUBLIC_KEYS_PREFIX)).thenReturn(ConfigFactory.fromMap(keys)); | ||
extension.initialize(context); | ||
|
||
assertThatThrownBy(() -> extension.prepare()).isInstanceOf(EdcException.class); | ||
} | ||
|
||
} |
Oops, something went wrong.