Skip to content

Commit

Permalink
feat: implement StsAccountProvisioner (#458)
Browse files Browse the repository at this point in the history
* feat: implement StsClientProvisioner

* handle deleted event

* DEPENDENCIES

* added StsClientSecretGenerator

* add embedded STS and STS API to IH runtime

* AccountProvisioner is called explicitly by ParticipantContextService

* implement key-revoked and key-rotated callbacks

* add new keypair descriptor to revoke/rotate event

* make provisioner transactional

* pr remarks

* updated wording
  • Loading branch information
paullatzelsperger committed Sep 17, 2024
1 parent db0206e commit 77cee7e
Show file tree
Hide file tree
Showing 40 changed files with 928 additions and 136 deletions.
6 changes: 5 additions & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ maven/mavencentral/com.lmax/disruptor/3.4.4, Apache-2.0, approved, clearlydefine
maven/mavencentral/com.networknt/json-schema-validator/1.0.76, Apache-2.0, approved, CQ22638
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.28, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.40, Apache-2.0, approved, #15156
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.41, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.41.1, Apache-2.0, approved, clearlydefined
maven/mavencentral/com.puppycrawl.tools/checkstyle/10.18.1, LGPL-2.1-or-later AND (Apache-2.0 AND LGPL-2.1-or-later) AND Apache-2.0, approved, #16060
maven/mavencentral/com.samskivert/jmustache/1.15, BSD-2-Clause AND BSD-3-Clause, approved, clearlydefined
maven/mavencentral/com.squareup.okhttp3/okhttp-dnsoverhttps/4.12.0, Apache-2.0, approved, #11159
Expand Down Expand Up @@ -255,6 +255,10 @@ maven/mavencentral/org.eclipse.edc/identity-did-core/0.10.0-SNAPSHOT, Apache-2.0
maven/mavencentral/org.eclipse.edc/identity-did-spi/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-did-web/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-spi/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-sts-api/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-sts-core/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-sts-embedded/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-sts-spi/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/identity-trust-transform/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/jersey-core/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
maven/mavencentral/org.eclipse.edc/jersey-providers-lib/0.10.0-SNAPSHOT, Apache-2.0, approved, technology.edc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
import org.eclipse.edc.identithub.spi.did.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairActivated;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextUpdated;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContextState;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantResource;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.keys.spi.KeyParserRegistry;
import org.eclipse.edc.security.token.jwt.CryptoConverter;
import org.eclipse.edc.spi.event.Event;
Expand Down Expand Up @@ -57,16 +57,16 @@ public class DidDocumentServiceImpl implements DidDocumentService, EventSubscrib
private final TransactionContext transactionContext;
private final DidResourceStore didResourceStore;
private final DidDocumentPublisherRegistry registry;
private final ParticipantContextService participantContextService;
private final ParticipantContextStore participantContextStore;
private final Monitor monitor;
private final KeyParserRegistry keyParserRegistry;

public DidDocumentServiceImpl(TransactionContext transactionContext, DidResourceStore didResourceStore, DidDocumentPublisherRegistry registry,
ParticipantContextService participantContextService, Monitor monitor, KeyParserRegistry keyParserRegistry) {
ParticipantContextStore participantContextStore, Monitor monitor, KeyParserRegistry keyParserRegistry) {
this.transactionContext = transactionContext;
this.didResourceStore = didResourceStore;
this.registry = registry;
this.participantContextService = participantContextService;
this.participantContextStore = participantContextStore;
this.monitor = monitor;
this.keyParserRegistry = keyParserRegistry;
}
Expand Down Expand Up @@ -112,7 +112,7 @@ public ServiceResult<Void> publish(String did) {
return ServiceResult.notFound(notFoundMessage(did));
}
var participantId = existingResource.getParticipantId();
return participantContextService.getParticipantContext(participantId)
return ServiceResult.from(participantContextStore.findById(participantId))
.map(ParticipantContext::getStateAsEnum)
.compose(state -> {
var canPublish = state.equals(ParticipantContextState.ACTIVATED);
Expand Down Expand Up @@ -142,7 +142,7 @@ public ServiceResult<Void> unpublish(String did) {
}

var participantId = existingResource.getParticipantId();
return participantContextService.getParticipantContext(participantId)
return ServiceResult.from(participantContextStore.findById(participantId))
.map(ParticipantContext::getStateAsEnum)
.compose(state -> {
var canUnpublish = state.equals(ParticipantContextState.DEACTIVATED);
Expand Down Expand Up @@ -267,7 +267,7 @@ private void keyPairActivated(KeyPairActivated event) {
var publicKey = keyParserRegistry.parse(serialized);

if (publicKey.failed()) {
monitor.warning("Error adding KeyPair '%s' to DID Document of participant '%s': %s".formatted(event.getKeyPairResourceId(), event.getParticipantId(), publicKey.getFailureDetail()));
monitor.warning("Error adding KeyPair '%s' to DID Document of participant '%s': %s".formatted(event.getKeyPairResource().getId(), event.getParticipantId(), publicKey.getFailureDetail()));
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
import org.eclipse.edc.identithub.spi.did.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairActivated;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextUpdated;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.keys.spi.KeyParserRegistry;
import org.eclipse.edc.runtime.metamodel.annotation.Extension;
import org.eclipse.edc.runtime.metamodel.annotation.Inject;
Expand All @@ -39,16 +39,13 @@ public class DidServicesExtension implements ServiceExtension {
private TransactionContext transactionContext;
@Inject
private DidResourceStore didResourceStore;

@Inject
private EventRouter eventRouter;

private DidDocumentPublisherRegistry didPublisherRegistry;

@Inject
private KeyParserRegistry keyParserRegistry;
@Inject
private ParticipantContextService participantContextService;
private ParticipantContextStore participantContextStore;
private DidDocumentPublisherRegistry didPublisherRegistry;

@Override
public String name() {
Expand All @@ -66,7 +63,7 @@ public DidDocumentPublisherRegistry getDidPublisherRegistry() {
@Provider
public DidDocumentService createDidDocumentService(ServiceExtensionContext context) {
var service = new DidDocumentServiceImpl(transactionContext, didResourceStore,
getDidPublisherRegistry(), participantContextService, context.getMonitor().withPrefix("DidDocumentService"), keyParserRegistry);
getDidPublisherRegistry(), participantContextStore, context.getMonitor().withPrefix("DidDocumentService"), keyParserRegistry);
eventRouter.registerSync(ParticipantContextUpdated.class, service);
eventRouter.registerSync(KeyPairRevoked.class, service);
eventRouter.registerSync(KeyPairActivated.class, service);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@
import org.eclipse.edc.identithub.spi.did.store.DidResourceStore;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairActivated;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.participantcontext.ParticipantContextService;
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource;
import org.eclipse.edc.identityhub.spi.participantcontext.events.ParticipantContextUpdated;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContext;
import org.eclipse.edc.identityhub.spi.participantcontext.model.ParticipantContextState;
import org.eclipse.edc.identityhub.spi.store.ParticipantContextStore;
import org.eclipse.edc.keys.KeyParserRegistryImpl;
import org.eclipse.edc.keys.keyparsers.JwkParser;
import org.eclipse.edc.keys.keyparsers.PemParser;
Expand All @@ -40,7 +41,6 @@
import org.eclipse.edc.spi.monitor.Monitor;
import org.eclipse.edc.spi.query.QuerySpec;
import org.eclipse.edc.spi.result.Result;
import org.eclipse.edc.spi.result.ServiceResult;
import org.eclipse.edc.spi.result.StoreResult;
import org.eclipse.edc.transaction.spi.NoopTransactionContext;
import org.junit.jupiter.api.BeforeEach;
Expand Down Expand Up @@ -68,7 +68,7 @@ class DidDocumentServiceImplTest {
private final DidResourceStore didResourceStoreMock = mock();
private final DidDocumentPublisherRegistry publisherRegistry = mock();
private final DidDocumentPublisher publisherMock = mock();
private final ParticipantContextService participantContextServiceMock = mock();
private final ParticipantContextStore participantContextServiceMock = mock();
private DidDocumentServiceImpl service;
private Monitor monitorMock;

Expand All @@ -83,7 +83,7 @@ void setUp() {
monitorMock = mock();
service = new DidDocumentServiceImpl(trx, didResourceStoreMock, publisherRegistry, participantContextServiceMock, monitorMock, registry);

when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.ACTIVATED)
Expand Down Expand Up @@ -212,7 +212,7 @@ void unpublish() {
var did = doc.getId();
when(didResourceStoreMock.findById(eq(did))).thenReturn(DidResource.Builder.newInstance().did(did).state(DidState.PUBLISHED).document(doc).build());
when(publisherMock.unpublish(did)).thenReturn(Result.success());
when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.DEACTIVATED)
Expand Down Expand Up @@ -243,7 +243,7 @@ void unpublish_noPublisherFound() {
var did = doc.getId();
when(publisherRegistry.getPublisher(any())).thenReturn(null);
when(didResourceStoreMock.findById(eq(did))).thenReturn(DidResource.Builder.newInstance().did(did).state(DidState.PUBLISHED).document(doc).build());
when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.DEACTIVATED)
Expand All @@ -263,7 +263,7 @@ void unpublish_publisherReportsError() {
var did = doc.getId();
when(didResourceStoreMock.findById(eq(did))).thenReturn(DidResource.Builder.newInstance().did(did).state(DidState.PUBLISHED).document(doc).build());
when(publisherMock.unpublish(did)).thenReturn(Result.failure("test-failure"));
when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.DEACTIVATED)
Expand Down Expand Up @@ -435,7 +435,7 @@ void onParticipantContextUpdated_whenDeactivates_shouldUnpublish() {
when(didResourceStoreMock.query(any())).thenReturn(List.of(didResource));
when(publisherMock.unpublish(anyString())).thenReturn(Result.success());

when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.DEACTIVATED)
Expand Down Expand Up @@ -487,7 +487,7 @@ void onParticipantContextUpdated_whenDeactivated_published_shouldBeNoop() {
when(didResourceStoreMock.query(any())).thenReturn(List.of(didResource));
when(publisherMock.unpublish(anyString())).thenReturn(Result.success());

when(participantContextServiceMock.getParticipantContext(any())).thenReturn(ServiceResult.success(ParticipantContext.Builder.newInstance()
when(participantContextServiceMock.findById(any())).thenReturn(StoreResult.success(ParticipantContext.Builder.newInstance()
.participantId(TEST_PARTICIPANT_ID)
.apiTokenAlias("token")
.state(ParticipantContextState.DEACTIVATED)
Expand Down Expand Up @@ -547,7 +547,7 @@ void onKeyPairActivated() throws JOSEException {
.id(UUID.randomUUID().toString())
.payload(KeyPairActivated.Builder.newInstance()
.keyId(keyId)
.keyPairResourceId("test-resource-id")
.keyPairResource(KeyPairResource.Builder.newInstance().id(UUID.randomUUID().toString()).build())
.participantId("test-participant")
.publicKey(key.toPublicJWK().toJSONString(), JSON_WEB_KEY_2020)
.build())
Expand Down Expand Up @@ -582,7 +582,7 @@ void onKeyPairRevoked() throws JOSEException {
.id(UUID.randomUUID().toString())
.payload(KeyPairRevoked.Builder.newInstance()
.keyId(keyId)
.keyPairResourceId("test-resource-id")
.keyPairResource(KeyPairResource.Builder.newInstance().id(UUID.randomUUID().toString()).build())
.participantId("test-participant")
.build())
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairRevoked;
import org.eclipse.edc.identityhub.spi.keypair.events.KeyPairRotated;
import org.eclipse.edc.identityhub.spi.keypair.model.KeyPairResource;
import org.eclipse.edc.identityhub.spi.participantcontext.model.KeyDescriptor;
import org.eclipse.edc.spi.event.EventEnvelope;
import org.eclipse.edc.spi.event.EventRouter;
import org.jetbrains.annotations.Nullable;

import java.time.Clock;

Expand All @@ -39,29 +41,31 @@ public KeyPairEventPublisher(Clock clock, EventRouter eventRouter) {
public void added(KeyPairResource keyPair, String type) {
var event = KeyPairAdded.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyPairResource(keyPair)
.keyId(keyPair.getKeyId())
.publicKey(keyPair.getSerializedPublicKey(), type)
.build();
publish(event);
}

@Override
public void rotated(KeyPairResource keyPair) {
public void rotated(KeyPairResource keyPair, @Nullable KeyDescriptor newKeyDesc) {
var event = KeyPairRotated.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyPairResource(keyPair)
.keyId(keyPair.getKeyId())
.newKeyDescriptor(newKeyDesc)
.build();
publish(event);
}

@Override
public void revoked(KeyPairResource keyPair) {
public void revoked(KeyPairResource keyPair, @Nullable KeyDescriptor newKeyDesc) {
var event = KeyPairRevoked.Builder.newInstance()
.participantId(keyPair.getParticipantId())
.keyPairResourceId(keyPair.getId())
.keyPairResource(keyPair)
.keyId(keyPair.getKeyId())
.newKeyDescriptor(newKeyDesc)
.build();
publish(event);
}
Expand All @@ -70,7 +74,7 @@ public void revoked(KeyPairResource keyPair) {
public void activated(KeyPairResource activatedKeyPair, String type) {
var event = KeyPairActivated.Builder.newInstance()
.participantId(activatedKeyPair.getParticipantId())
.keyPairResourceId(activatedKeyPair.getId())
.keyPairResource(activatedKeyPair)
.publicKey(activatedKeyPair.getSerializedPublicKey(), type)
.keyId(activatedKeyPair.getKeyId())
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public ServiceResult<Void> addKeyPair(String participantId, KeyDescriptor keyDes
// check if the new key is not active, and no other active key exists
if (!keyDescriptor.isActive()) {

//todo: replace this with invocation to activateKeyPair()
var hasActiveKeys = keyPairResourceStore.query(ParticipantResource.queryByParticipantId(participantId).build())
.orElse(failure -> Collections.emptySet())
.stream().filter(kpr -> kpr.getState() == KeyPairState.ACTIVATED.code())
Expand Down Expand Up @@ -140,7 +139,7 @@ public ServiceResult<Void> rotateKeyPair(String oldId, @Nullable KeyDescriptor n
vault.deleteSecret(oldAlias);
oldKey.rotate(duration);
var updateResult = ServiceResult.from(keyPairResourceStore.update(oldKey))
.onSuccess(v -> observable.invokeForEach(l -> l.rotated(oldKey)));
.onSuccess(v -> observable.invokeForEach(l -> l.rotated(oldKey, newKeyDesc)));

if (newKeyDesc != null) {
return updateResult.compose(v -> addKeyPair(participantId, newKeyDesc, wasDefault));
Expand All @@ -151,7 +150,7 @@ public ServiceResult<Void> rotateKeyPair(String oldId, @Nullable KeyDescriptor n
}

@Override
public ServiceResult<Void> revokeKey(String id, @Nullable KeyDescriptor newKeySpec) {
public ServiceResult<Void> revokeKey(String id, @Nullable KeyDescriptor newKeyDesc) {
return transactionContext.execute(() -> {
var oldKey = findById(id);
if (oldKey == null) {
Expand All @@ -166,10 +165,10 @@ public ServiceResult<Void> revokeKey(String id, @Nullable KeyDescriptor newKeySp
vault.deleteSecret(oldAlias);
oldKey.revoke();
var updateResult = ServiceResult.from(keyPairResourceStore.update(oldKey))
.onSuccess(v -> observable.invokeForEach(l -> l.revoked(oldKey)));
.onSuccess(v -> observable.invokeForEach(l -> l.revoked(oldKey, newKeyDesc)));

if (newKeySpec != null) {
return updateResult.compose(v -> addKeyPair(participantId, newKeySpec, wasDefault));
if (newKeyDesc != null) {
return updateResult.compose(v -> addKeyPair(participantId, newKeyDesc, wasDefault));
}
monitor.warning("Revoking keys without a successor key may leave the participant without an active keypair.");
return updateResult;
Expand Down
Loading

0 comments on commit 77cee7e

Please sign in to comment.