-
Notifications
You must be signed in to change notification settings - Fork 240
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a new management api for secrets (#4138)
* feat: introduced API for managing secrets * feat: introduced API for managing secrets - rebased * feat: introduced API for managing secrets - fix SecretApiControllerTest tests * feat: introduced API for managing secrets * feat: introduced API for managing secrets - removed key from model, using id instead * feat: introduced API for managing secrets - fix SecretApiControllerTest tests * code review * Code review --------- Co-authored-by: Benjamin SCHOLTES <[email protected]>
- Loading branch information
1 parent
873e86b
commit 50f91f7
Showing
36 changed files
with
2,279 additions
and
3 deletions.
There are no files selected for viewing
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
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
74 changes: 74 additions & 0 deletions
74
...main/java/org/eclipse/edc/connector/controlplane/services/secret/SecretEventListener.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,74 @@ | ||
/* | ||
* Copyright (c) 2024 Amadeus | ||
* | ||
* 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: | ||
* Amadeus - Initial API and Implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.controlplane.services.secret; | ||
|
||
import org.eclipse.edc.connector.secret.spi.event.SecretCreated; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretDeleted; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretEvent; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretUpdated; | ||
import org.eclipse.edc.connector.secret.spi.observe.SecretListener; | ||
import org.eclipse.edc.spi.event.EventEnvelope; | ||
import org.eclipse.edc.spi.event.EventRouter; | ||
import org.eclipse.edc.spi.types.domain.secret.Secret; | ||
|
||
import java.time.Clock; | ||
|
||
/** | ||
* Listener responsible for creating and publishing events regarding Secret state changes | ||
*/ | ||
public class SecretEventListener implements SecretListener { | ||
private final Clock clock; | ||
private final EventRouter eventRouter; | ||
|
||
public SecretEventListener(Clock clock, EventRouter eventRouter) { | ||
this.clock = clock; | ||
this.eventRouter = eventRouter; | ||
} | ||
|
||
@Override | ||
public void created(Secret secret) { | ||
var event = SecretCreated.Builder.newInstance() | ||
.secretId(secret.getId()) | ||
.build(); | ||
|
||
publish(event); | ||
} | ||
|
||
@Override | ||
public void deleted(Secret secret) { | ||
var event = SecretDeleted.Builder.newInstance() | ||
.secretId(secret.getId()) | ||
.build(); | ||
|
||
publish(event); | ||
} | ||
|
||
@Override | ||
public void updated(Secret secret) { | ||
var event = SecretUpdated.Builder.newInstance() | ||
.secretId(secret.getId()) | ||
.build(); | ||
|
||
publish(event); | ||
} | ||
|
||
private void publish(SecretEvent event) { | ||
var envelope = EventEnvelope.Builder.newInstance() | ||
.payload(event) | ||
.at(clock.millis()) | ||
.build(); | ||
eventRouter.publish(envelope); | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
...c/main/java/org/eclipse/edc/connector/controlplane/services/secret/SecretServiceImpl.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,88 @@ | ||
/* | ||
* Copyright (c) 2024 Amadeus | ||
* | ||
* 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: | ||
* Amadeus - Initial API and Implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.controlplane.services.secret; | ||
|
||
import org.eclipse.edc.connector.secret.spi.observe.SecretObservable; | ||
import org.eclipse.edc.connector.spi.service.SecretService; | ||
import org.eclipse.edc.spi.result.ServiceResult; | ||
import org.eclipse.edc.spi.security.Vault; | ||
import org.eclipse.edc.spi.types.domain.secret.Secret; | ||
|
||
import static java.util.Optional.ofNullable; | ||
import static org.eclipse.edc.spi.result.ServiceResult.badRequest; | ||
import static org.eclipse.edc.spi.result.ServiceResult.conflict; | ||
import static org.eclipse.edc.spi.result.ServiceResult.notFound; | ||
import static org.eclipse.edc.spi.result.ServiceResult.success; | ||
|
||
public class SecretServiceImpl implements SecretService { | ||
private final Vault vault; | ||
private final SecretObservable observable; | ||
|
||
public SecretServiceImpl(Vault vault, SecretObservable observable) { | ||
this.vault = vault; | ||
this.observable = observable; | ||
} | ||
|
||
@Override | ||
public Secret findById(String secretId) { | ||
return ofNullable(vault.resolveSecret(secretId)) | ||
.map(secretValue -> Secret.Builder.newInstance() | ||
.value(secretValue) | ||
.id(secretId) | ||
.build()) | ||
.orElse(null); | ||
} | ||
|
||
@Override | ||
public ServiceResult<Secret> create(Secret secret) { | ||
var existing = findById(secret.getId()); | ||
if (existing != null) { | ||
return conflict("Secret " + secret.getId() + " already exist"); | ||
} | ||
|
||
return vault.storeSecret(secret.getId(), secret.getValue()) | ||
.onSuccess(unused -> observable.invokeForEach(l -> l.created(secret))) | ||
.map(unused -> success(secret)) | ||
.orElse(failure -> badRequest(failure.getFailureDetail())); | ||
} | ||
|
||
@Override | ||
public ServiceResult<Secret> delete(String secretKey) { | ||
var existing = findById(secretKey); | ||
if (existing == null) { | ||
return notFound("Secret " + secretKey + " not found"); | ||
} | ||
|
||
return vault.deleteSecret(secretKey) | ||
.onSuccess(unused -> observable.invokeForEach(l -> l.deleted(existing))) | ||
.map(unused -> success(existing)) | ||
.orElse(failure -> badRequest(failure.getFailureDetail())); | ||
} | ||
|
||
@Override | ||
public ServiceResult<Secret> update(Secret secret) { | ||
var existing = findById(secret.getId()); | ||
if (existing == null) { | ||
return notFound("Secret " + secret.getId() + " not found"); | ||
} | ||
|
||
return vault.storeSecret(secret.getId(), secret.getValue()) | ||
.onSuccess(unused -> observable.invokeForEach(l -> l.updated(secret))) | ||
.map(unused -> success(secret)) | ||
.orElse(failure -> badRequest(failure.getFailureDetail())); | ||
} | ||
|
||
} | ||
|
73 changes: 73 additions & 0 deletions
73
.../java/org/eclipse/edc/connector/controlplane/services/secret/SecretEventDispatchTest.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,73 @@ | ||
/* | ||
* Copyright (c) 2024 Amadeus | ||
* | ||
* 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: | ||
* Amadeus - Initial API and Implementation | ||
* | ||
*/ | ||
|
||
package org.eclipse.edc.connector.controlplane.services.secret; | ||
|
||
import org.eclipse.edc.connector.dataplane.selector.spi.store.DataPlaneInstanceStore; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretCreated; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretDeleted; | ||
import org.eclipse.edc.connector.secret.spi.event.SecretEvent; | ||
import org.eclipse.edc.connector.spi.service.SecretService; | ||
import org.eclipse.edc.junit.extensions.EdcExtension; | ||
import org.eclipse.edc.spi.event.EventRouter; | ||
import org.eclipse.edc.spi.event.EventSubscriber; | ||
import org.eclipse.edc.spi.iam.IdentityService; | ||
import org.eclipse.edc.spi.protocol.ProtocolWebhook; | ||
import org.eclipse.edc.spi.types.domain.secret.Secret; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
import java.util.Map; | ||
|
||
import static org.awaitility.Awaitility.await; | ||
import static org.eclipse.edc.junit.matchers.EventEnvelopeMatcher.isEnvelopeOf; | ||
import static org.eclipse.edc.util.io.Ports.getFreePort; | ||
import static org.mockito.ArgumentMatchers.argThat; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
|
||
@ExtendWith(EdcExtension.class) | ||
public class SecretEventDispatchTest { | ||
|
||
private final EventSubscriber eventSubscriber = mock(EventSubscriber.class); | ||
|
||
@BeforeEach | ||
void setUp(EdcExtension extension) { | ||
extension.registerServiceMock(ProtocolWebhook.class, mock(ProtocolWebhook.class)); | ||
extension.registerServiceMock(DataPlaneInstanceStore.class, mock(DataPlaneInstanceStore.class)); | ||
extension.registerServiceMock(IdentityService.class, mock()); | ||
extension.setConfiguration(Map.of( | ||
"web.http.port", String.valueOf(getFreePort()), | ||
"web.http.path", "/api" | ||
)); | ||
} | ||
|
||
@Test | ||
void shouldDispatchEventsOnSecretCreationAndDeletion(SecretService service, EventRouter eventRouter) { | ||
eventRouter.register(SecretEvent.class, eventSubscriber); | ||
var secret = Secret.Builder.newInstance().id("secretId").value("secret-value").build(); | ||
|
||
var result = service.create(secret); | ||
await().untilAsserted(() -> { | ||
verify(eventSubscriber).on(argThat(isEnvelopeOf(SecretCreated.class))); | ||
}); | ||
|
||
|
||
service.delete(secret.getId()); | ||
await().untilAsserted(() -> { | ||
verify(eventSubscriber).on(argThat(isEnvelopeOf(SecretDeleted.class))); | ||
}); | ||
} | ||
} |
Oops, something went wrong.