From ba407e8bf96f7921c70295343dab85e27677e677 Mon Sep 17 00:00:00 2001
From: Benoit Bordigoni
Date: Thu, 26 Sep 2024 17:22:54 +0200
Subject: [PATCH 01/15] feat: rtsec step1
---
.../node/api/secrets/model/Secret.java | 4 +
.../node/api/secrets/model/SecretMap.java | 8 +
.../node/api/secrets/model/SecretMount.java | 6 +-
.../node/api/secrets/model/SecretURL.java | 16 +-
.../runtime/RuntimeSecretException.java | 22 ++
.../secrets/runtime/discovery/Definition.java | 5 +
.../runtime/discovery/DefinitionBrowser.java | 15 ++
.../discovery/DefinitionPayloadNotifier.java | 9 +
.../runtime/discovery/DiscoveryContext.java | 9 +
.../runtime/discovery/DiscoveryLocation.java | 9 +
.../runtime/discovery/PayloadLocation.java | 6 +
.../api/secrets/runtime/discovery/Ref.java | 64 ++++++
.../secrets/runtime/grant/GrantService.java | 18 ++
.../runtime/providers/ResolverService.java | 16 ++
.../providers/SecretProviderDeployer.java | 11 +
.../node/api/secrets/runtime/spec/ACLs.java | 13 ++
.../secrets/runtime/spec/RenewalPolicy.java | 15 ++
.../node/api/secrets/runtime/spec/Spec.java | 57 +++++
.../runtime/spec/SpecLifecycleService.java | 16 ++
.../api/secrets/runtime/storage/Cache.java | 25 ++
.../api/secrets/runtime/storage/Entry.java | 21 ++
.../node/api/secrets/model/SecretMapTest.java | 6 +-
.../api/secrets/model/SecretMountTest.java | 7 +-
.../node/api/secrets/model/SecretURLTest.java | 14 +-
.../internal/test/TestSecretProvider.java | 2 +-
.../gravitee-node-secrets-runtime/pom.xml | 59 +++++
.../RuntimeSecretProcessingService.java | 117 ++++++++++
.../runtimesecrets/config/Config.java | 10 +
.../discovery/ContextRegistry.java | 53 +++++
.../discovery/DefinitionBrowserRegistry.java | 29 +++
.../discovery/PayloadRefParser.java | 80 +++++++
.../runtimesecrets/discovery/RefParser.java | 213 ++++++++++++++++++
.../runtimesecrets/el/ContextUpdater.java | 25 ++
.../services/runtimesecrets/el/Formatter.java | 91 ++++++++
.../services/runtimesecrets/el/Result.java | 16 ++
.../services/runtimesecrets/el/Service.java | 138 ++++++++++++
.../errors/SecretAccessDeniedException.java | 14 ++
.../errors/SecretEmptyException.java | 14 ++
.../errors/SecretKeyNotFoundException.java | 14 ++
.../errors/SecretNotFoundException.java | 14 ++
.../errors/SecretProviderException.java | 14 ++
.../SecretProviderNotFoundException.java | 14 ++
.../errors/SecretRefParsingException.java | 14 ++
.../errors/SecretSpecNotFoundException.java | 14 ++
.../grant/DefaultGrantService.java | 111 +++++++++
.../runtimesecrets/grant/GrantRegistry.java | 30 +++
.../providers/DefaultRuntimeResolver.java | 35 +++
...omConfigurationSecretProviderDeployer.java | 35 +++
.../providers/SecretProviderRegistry.java | 49 ++++
.../spec/DefaultSpecLifecycleService.java | 84 +++++++
.../spec/registry/EnvAwareSpecRegistry.java | 51 +++++
.../spec/registry/SpecRegistry.java | 91 ++++++++
.../runtimesecrets/spring/BeanFactory.java | 116 ++++++++++
.../storage/SimpleOffHeapCache.java | 92 ++++++++
.../runtimesecrets/api/model/RefTest.java | 128 +++++++++++
.../discovery/RefDiscovererTest.java | 88 ++++++++
.../grant/DefaultGrantServiceTest.java | 134 +++++++++++
.../storage/SimpleOffHeapCacheTest.java | 104 +++++++++
...ConfigurationSecretResolverDispatcher.java | 2 +-
...eeConfigurationSecretPropertyResolver.java | 2 +-
...igurationSecretResolverDispatcherTest.java | 16 +-
.../service/test/TestSecretProvider.java | 2 +-
gravitee-node-secrets/pom.xml | 9 +
63 files changed, 2483 insertions(+), 33 deletions(-)
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/RuntimeSecretException.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Definition.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryContext.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryLocation.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/PayloadLocation.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/ACLs.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/SpecLifecycleService.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Cache.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Entry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/Secret.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/Secret.java
index d3c529f2b..d479fb771 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/Secret.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/Secret.java
@@ -20,6 +20,10 @@ public final class Secret {
private final Object data;
private final boolean base64Encoded;
+ public Secret() {
+ this("");
+ }
+
/**
* Builds a secret value assuming it is not base64 encoded
*
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMap.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMap.java
index 92969d0ff..166a0a925 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMap.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMap.java
@@ -45,6 +45,14 @@ public SecretMap(Map map, Instant expireAt) {
this.expireAt = expireAt;
}
+ /**
+ *
+ * @return a copy f the secrets
+ */
+ public Map asMap() {
+ return Map.copyOf(map);
+ }
+
/**
* Builds a secret map where secrets are base64 encoded
*
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
index 517f71311..d1d2fd2c4 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
@@ -10,7 +10,7 @@
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
-public record SecretMount(String provider, SecretLocation location, String key, SecretURL secretURL) {
+public record SecretMount(String provider, SecretLocation location, String key, SecretURL secretURL, boolean withRetries) {
/**
* Test the presence of a key
*
@@ -19,4 +19,8 @@ public record SecretMount(String provider, SecretLocation location, String key,
public boolean isKeyEmpty() {
return Strings.isNullOrEmpty(key);
}
+
+ public SecretMount withoutRetries() {
+ return new SecretMount(provider, location, key, secretURL, false);
+ }
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
index ee310987e..4173d7c7e 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
@@ -3,7 +3,10 @@
import com.google.common.base.Splitter;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
-import java.util.*;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@@ -27,7 +30,7 @@ public record SecretURL(String provider, String path, String key, Multimap
* the format is : secret://<provider>/<path or name>[:<key>][?option=value1&option=value2]
*
- * secret://is mandatory
+ * secret://
is mandatory is includesSchema is true
* provider is mandatory and should match a secret provider id
* "path or name" is mandatory, a free string that can contain forward slashes ('/').
* If an empty string or spaces are found between two forward slashes (eg. //
or / /
) parsing will fail.
@@ -35,16 +38,17 @@ public record SecretURL(String provider, String path, String key, Multimapquery string is optional and is simply split into key/value pairs.
* Pair are always list as can be specified more than once. If no value is parsed, then true
is set
*
- * @param url the string to parse
+ * @param url the string to parse
+ * @param includesSchema to indicate
* @return SecretURL object
* @throws IllegalArgumentException when failing to parse
*/
- public static SecretURL from(String url) {
+ public static SecretURL from(String url, boolean includesSchema) {
url = Objects.requireNonNull(url).trim();
- if (!url.startsWith(SCHEME)) {
+ if (includesSchema && !url.startsWith(SCHEME)) {
throwFormatError(url);
}
- String schemeLess = url.substring(SCHEME.length());
+ String schemeLess = includesSchema ? url.substring(SCHEME.length()) : url.substring(1);
int firstSlash = schemeLess.indexOf('/');
if (firstSlash < 0 || firstSlash == schemeLess.length() - 1) {
throwFormatError(url);
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/RuntimeSecretException.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/RuntimeSecretException.java
new file mode 100644
index 000000000..3f32f7879
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/RuntimeSecretException.java
@@ -0,0 +1,22 @@
+package io.gravitee.node.api.secrets.runtime;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class RuntimeSecretException extends RuntimeException {
+
+ public RuntimeSecretException() {}
+
+ public RuntimeSecretException(String message) {
+ super(message);
+ }
+
+ public RuntimeSecretException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public RuntimeSecretException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Definition.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Definition.java
new file mode 100644
index 000000000..4698e60ab
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Definition.java
@@ -0,0 +1,5 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+import java.util.Optional;
+
+public record Definition(String kind, String id, Optional revision) {}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
new file mode 100644
index 000000000..1a7e374ef
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
@@ -0,0 +1,15 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface DefinitionBrowser {
+ boolean canHandle(T definition);
+
+ Definition getDefinitionKindLocation(T definition, Map metadata);
+
+ void findPayloads(DefinitionPayloadNotifier notifier);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
new file mode 100644
index 000000000..3a4ac4646
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
@@ -0,0 +1,9 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface DefinitionPayloadNotifier {
+ void onPayload(String payload, PayloadLocation location);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryContext.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryContext.java
new file mode 100644
index 000000000..31afeddaa
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryContext.java
@@ -0,0 +1,9 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+import java.util.UUID;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record DiscoveryContext(UUID id, String envId, Ref ref, DiscoveryLocation location) {}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryLocation.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryLocation.java
new file mode 100644
index 000000000..19411609e
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DiscoveryLocation.java
@@ -0,0 +1,9 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record DiscoveryLocation(Definition definition, PayloadLocation... payloadLocations) {
+ public record Definition(String kind, String id) {}
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/PayloadLocation.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/PayloadLocation.java
new file mode 100644
index 000000000..65d6f6a54
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/PayloadLocation.java
@@ -0,0 +1,6 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+public record PayloadLocation(String kind, String id) {
+ public static final String PLUGIN_KIND = "plugin";
+ public static final PayloadLocation NOWHERE = new PayloadLocation("", "");
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
new file mode 100644
index 000000000..064913f48
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
@@ -0,0 +1,64 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+
+public record Ref(
+ MainType mainType,
+ Expression mainExpression,
+ SecondaryType secondaryType,
+ Expression secondaryExpression,
+ String rawRef
+) {
+ public record Expression(String value, boolean isEL) {
+ public boolean isLiteral() {
+ return !isEL;
+ }
+ }
+
+ public enum MainType {
+ NAME,
+ URI,
+ }
+
+ public enum SecondaryType {
+ NAME,
+ URI,
+ KEY,
+ }
+
+ public static final String URI_KEY_SEPARATOR = ":";
+
+ public Spec toRuntimeSpec(String envId) {
+ return new Spec(null, null, mainExpression.value(), secondaryExpression().value(), null, false, true, null, null, envId);
+ }
+
+ /**
+ * Check type are compatible and call {@link #formatUriAndKey(String, String)}
+ * @return main value and secondary value with key alias separator
+ */
+ public String uriAndKey() {
+ if (mainType == MainType.URI && secondaryType == SecondaryType.KEY) {
+ return formatUriAndKey(mainExpression.value(), secondaryExpression.value());
+ }
+ throw new IllegalArgumentException(
+ "cannot format main is %s and secondary is %s it should %s and %s".formatted(
+ mainType,
+ secondaryType,
+ MainType.URI,
+ SecondaryType.KEY
+ )
+ );
+ }
+
+ /**
+ * @return main value and secondary value with key alias separator
+ */
+ public static String formatUriAndKey(String uri, String key) {
+ return uri + URI_KEY_SEPARATOR + key;
+ }
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
new file mode 100644
index 000000000..6dcba6514
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
@@ -0,0 +1,18 @@
+package io.gravitee.node.api.secrets.runtime.grant;
+
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface GrantService {
+ boolean isGranted(String token);
+
+ boolean authorize(DiscoveryContext context, Spec spec);
+
+ void grant(DiscoveryContext context);
+
+ void revoke(DiscoveryContext context);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
new file mode 100644
index 000000000..56305d41c
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
@@ -0,0 +1,16 @@
+package io.gravitee.node.api.secrets.runtime.providers;
+
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.reactivex.rxjava3.core.Single;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface ResolverService {
+ Single resolve(String envId, SecretMount secretMount);
+
+ SecretMount toSecretMount(String envId, SecretURL secretURL);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
new file mode 100644
index 000000000..8da82de32
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
@@ -0,0 +1,11 @@
+package io.gravitee.node.api.secrets.runtime.providers;
+
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface SecretProviderDeployer {
+ void deploy(String id, String pluginId, Map config, String envId);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/ACLs.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/ACLs.java
new file mode 100644
index 000000000..84d777605
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/ACLs.java
@@ -0,0 +1,13 @@
+package io.gravitee.node.api.secrets.runtime.spec;
+
+import java.util.List;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+
+public record ACLs(List definitions, List plugins) {
+ public record DefinitionACL(String kind, List ids) {}
+ public record PluginACL(String id, List fields) {}
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java
new file mode 100644
index 000000000..319f9e035
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java
@@ -0,0 +1,15 @@
+package io.gravitee.node.api.secrets.runtime.spec;
+
+import java.time.Duration;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record RenewalPolicy(Type type, Duration duration, Duration checkBeforeTTL) {
+ public enum Type {
+ NONE,
+ TTL,
+ POLL,
+ }
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
new file mode 100644
index 000000000..5f84bf879
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
@@ -0,0 +1,57 @@
+package io.gravitee.node.api.secrets.runtime.spec;
+
+import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
+
+import io.gravitee.node.api.secrets.model.SecretURL;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record Spec(
+ String id,
+ String name,
+ String uri,
+ String key,
+ List children,
+ boolean usesDynamicKey,
+ boolean isRuntime,
+ RenewalPolicy renewalPolicy,
+ ACLs acls,
+ String envId
+) {
+ public boolean hasChildren() {
+ return children != null && !children.isEmpty();
+ }
+
+ public Optional findChildrenFromName(String query) {
+ if (hasChildren()) {
+ return children.stream().filter(child -> Objects.equals(child.name(), query)).findFirst();
+ }
+ return Optional.empty();
+ }
+
+ public Optional findChildrenFromUri(String query) {
+ if (hasChildren()) {
+ return children.stream().filter(child -> Objects.equals(child.uri(), query)).findFirst();
+ }
+ return Optional.empty();
+ }
+
+ public String uriAndKey() {
+ return uri + URI_KEY_SEPARATOR + key;
+ }
+
+ public SecretURL toSecretURL() {
+ return SecretURL.from(uriAndKey(), false);
+ }
+
+ public String naturalId() {
+ return name != null && !name.isEmpty() ? name : uri;
+ }
+
+ public record ChildSpec(String name, String uri, String key) {}
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/SpecLifecycleService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/SpecLifecycleService.java
new file mode 100644
index 000000000..ca0d9cd5e
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/SpecLifecycleService.java
@@ -0,0 +1,16 @@
+package io.gravitee.node.api.secrets.runtime.spec;
+
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface SpecLifecycleService {
+ boolean shouldDeployOnTheFly(Ref ref);
+ Spec deployOnTheFly(String envId, Ref ref);
+
+ void deploy(Spec spec);
+
+ void undeploy(Spec spec);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Cache.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Cache.java
new file mode 100644
index 000000000..305694b39
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Cache.java
@@ -0,0 +1,25 @@
+package io.gravitee.node.api.secrets.runtime.storage;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface Cache {
+ CacheKey put(String envId, String naturalId, Entry value);
+
+ Optional get(String envId, String naturalId);
+
+ void computeIfAbsent(String envId, String naturalId, Supplier supplier);
+
+ void evict(String envId, String naturalId);
+
+ record CacheKey(String envId, String naturalId) {
+ @Override
+ public String toString() {
+ return envId + "-" + naturalId;
+ }
+ }
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Entry.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Entry.java
new file mode 100644
index 000000000..02ec4eb14
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/storage/Entry.java
@@ -0,0 +1,21 @@
+package io.gravitee.node.api.secrets.runtime.storage;
+
+import io.gravitee.node.api.secrets.model.Secret;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record Entry(Type type, Map value, String error) {
+ public Entry {
+ value = value != null ? new HashMap<>(value) : new HashMap<>();
+ }
+ public enum Type {
+ VALUE,
+ EMPTY,
+ NOT_FOUND,
+ ERROR,
+ }
+}
diff --git a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMapTest.java b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMapTest.java
index c5ca74f73..698dff479 100644
--- a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMapTest.java
+++ b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMapTest.java
@@ -37,9 +37,9 @@ public static Stream secretMaps() {
@ParameterizedTest(name = "{0}")
@MethodSource("secretMaps")
void should_get_secret_from_map(String name, SecretMap secretMap) {
- SecretMount pass = new SecretMount(null, null, KEY, null);
+ SecretMount pass = new SecretMount(null, null, KEY, null, true);
assertThat(secretMap.getSecret(pass)).isPresent().get().extracting(Secret::asString).isEqualTo(SECRET);
- assertThat(secretMap.getSecret(new SecretMount(null, null, "bar", null))).isNotPresent();
+ assertThat(secretMap.getSecret(new SecretMount(null, null, "bar", null, true))).isNotPresent();
}
@Test
@@ -58,7 +58,7 @@ void should_have_expireAt_set() {
void should_have_well_know_data() {
SecretMap secretMap = SecretMap.of(Map.of(KEY, SECRET));
secretMap.handleWellKnownSecretKeys(Map.of(KEY, SecretMap.WellKnownSecretKey.PASSWORD));
- assertThat(secretMap.getSecret(new SecretMount(null, null, KEY, null)))
+ assertThat(secretMap.getSecret(new SecretMount(null, null, KEY, null, true)))
.isPresent()
.get()
.extracting(Secret::asString)
diff --git a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMountTest.java b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMountTest.java
index 8f6302e49..5439ffec0 100644
--- a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMountTest.java
+++ b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretMountTest.java
@@ -1,7 +1,6 @@
package io.gravitee.node.api.secrets.model;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
@@ -16,11 +15,11 @@ class SecretMountTest {
@Test
void should_know_if_key_is_empty() {
- SecretMount secretMount = new SecretMount(null, null, null, null);
+ SecretMount secretMount = new SecretMount(null, null, null, null, true);
assertThat(secretMount.isKeyEmpty()).isTrue();
- secretMount = new SecretMount(null, null, "", null);
+ secretMount = new SecretMount(null, null, "", null, true);
assertThat(secretMount.isKeyEmpty()).isTrue();
- secretMount = new SecretMount(null, null, "foo", null);
+ secretMount = new SecretMount(null, null, "foo", null, true);
assertThat(secretMount.isKeyEmpty()).isFalse();
}
}
diff --git a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretURLTest.java b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretURLTest.java
index f59488e0d..0fa59fc61 100644
--- a/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretURLTest.java
+++ b/gravitee-node-api/src/test/java/io/gravitee/node/api/secrets/model/SecretURLTest.java
@@ -3,14 +3,14 @@
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
/**
@@ -56,7 +56,7 @@ static Stream workingURLs() {
@ParameterizedTest
@MethodSource("workingURLs")
void should_parse_url(String url, String provider, String path, String key, Map> query, boolean watch) {
- SecretURL cut = SecretURL.from(url);
+ SecretURL cut = SecretURL.from(url, true);
assertThat(cut.provider()).isEqualTo(provider);
assertThat(cut.path()).isEqualTo(path);
assertThat(cut.key()).isEqualTo(key);
@@ -84,7 +84,7 @@ static Stream nonWorkingURLs() {
@ParameterizedTest
@MethodSource("nonWorkingURLs")
void should_not_parse_url(String url) {
- assertThatCode(() -> SecretURL.from(url))
+ assertThatCode(() -> SecretURL.from(url, true))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("should have the following format");
}
@@ -120,7 +120,7 @@ public static Stream wellKnowKeysURLs() {
@ParameterizedTest
@MethodSource("wellKnowKeysURLs")
void should_have_well_known_mapping(String url, Map expected) {
- SecretURL cut = SecretURL.from(url);
+ SecretURL cut = SecretURL.from(url, true);
assertThat(cut.wellKnowKeyMap()).containsAllEntriesOf(expected);
}
@@ -140,7 +140,7 @@ public static Stream failingWellKnownKeysURLs() {
@ParameterizedTest
@MethodSource("failingWellKnownKeysURLs")
void should_fail_parsing_well_known_mapping_error(String url) {
- SecretURL cut = SecretURL.from(url);
+ SecretURL cut = SecretURL.from(url, true);
assertThatThrownBy(cut::wellKnowKeyMap).isInstanceOf(IllegalArgumentException.class);
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-plugin-handler/src/test/java/io/gravitee/node/secrets/plugins/internal/test/TestSecretProvider.java b/gravitee-node-secrets/gravitee-node-secrets-plugin-handler/src/test/java/io/gravitee/node/secrets/plugins/internal/test/TestSecretProvider.java
index 1640a274e..db3af062f 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-plugin-handler/src/test/java/io/gravitee/node/secrets/plugins/internal/test/TestSecretProvider.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-plugin-handler/src/test/java/io/gravitee/node/secrets/plugins/internal/test/TestSecretProvider.java
@@ -20,6 +20,6 @@ public Flowable watch(SecretMount secretMount) {
@Override
public SecretMount fromURL(SecretURL url) {
- return new SecretMount("test", new SecretLocation(), "password", url);
+ return new SecretMount("test", new SecretLocation(), "password", url, true);
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
new file mode 100644
index 000000000..c411edeef
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ 4.0.0
+
+ io.gravitee.node
+ gravitee-node-secrets
+ 6.4.2
+
+
+ gravitee-node-secrets-runtime
+ Gravitee.io - Node - Secrets - Runtime
+
+
+
+ org.springframework
+ spring-core
+
+
+ io.gravitee.node
+ gravitee-node-api
+
+
+ io.reactivex.rxjava3
+ rxjava
+
+
+ io.gravitee.el
+ gravitee-expression-language
+ 3.1.0
+ compile
+
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+
+
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java
new file mode 100644
index 000000000..bc86cce51
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java
@@ -0,0 +1,117 @@
+package com.graviteesource.services.runtimesecrets;
+
+import com.graviteesource.services.runtimesecrets.discovery.ContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.PayloadRefParser;
+import com.graviteesource.services.runtimesecrets.discovery.RefParser;
+import com.graviteesource.services.runtimesecrets.el.Formatter;
+import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import io.gravitee.node.api.secrets.runtime.discovery.*;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import java.util.*;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class RuntimeSecretProcessingService {
+
+ private final DefinitionBrowserRegistry definitionBrowserRegistry;
+ private final ContextRegistry contextRegistry;
+ private final GrantService grantService;
+ private final SpecLifecycleService specLifecycleService;
+ private final EnvAwareSpecRegistry specRegistry;
+
+ /**
+ * finds a {@link DefinitionBrowser}
+ * Run it to get {@link DiscoveryContext}
+ * Inject EL {@link PayloadRefParser}
+ * Find {@link Spec}
+ * Grant {@link DiscoveryContext}
+ * @param definition the secret naturalId container
+ * @param metadata some optional metadata
+ * @param the kind of subject
+ */
+ void processSecrets(String envId, @Nonnull T definition, @Nullable Map metadata) {
+ Optional> browser = definitionBrowserRegistry.findBrowser(definition);
+ if (browser.isEmpty()) {
+ log.info("No definition browser found for kind [{}]", definition.getClass());
+ return;
+ }
+
+ DefinitionBrowser definitionBrowser = browser.get();
+ Definition rootDefinition = definitionBrowser.getDefinitionKindLocation(definition, metadata);
+ DefaultPayloadNotifier notifier = new DefaultPayloadNotifier(rootDefinition, envId);
+ definitionBrowser.findPayloads(notifier);
+
+ // register contexts by naturalId and definition
+ for (DiscoveryContext context : notifier.getContextList()) {
+ contextRegistry.register(context, rootDefinition);
+ // get spec
+ Spec spec = specRegistry.fromRef(context.envId(), context.ref());
+ if (spec == null && specLifecycleService.shouldDeployOnTheFly(context.ref())) {
+ spec = specLifecycleService.deployOnTheFly(envId, context.ref());
+ }
+ boolean granted = grantService.authorize(context, spec);
+ if (granted && context.ref().mainExpression().isLiteral()) {
+ grantService.grant(context);
+ }
+ }
+ }
+
+ static class DefaultPayloadNotifier implements DefinitionPayloadNotifier {
+
+ @Getter
+ final List contextList = new ArrayList<>();
+
+ final Definition rootDefinition;
+ final String envId;
+
+ DefaultPayloadNotifier(Definition rootDefinition, String envId) {
+ this.rootDefinition = rootDefinition;
+ this.envId = envId;
+ }
+
+ @Override
+ public void onPayload(String payload, PayloadLocation payloadLocation) {
+ PayloadRefParser payloadRefParser = new PayloadRefParser(payload);
+ List discoveryContexts = payloadRefParser
+ .runDiscovery()
+ .stream()
+ .map(raw -> RefParser.parse(raw.ref()))
+ .map(ref ->
+ new DiscoveryContext(
+ UUID.randomUUID(),
+ envId,
+ ref,
+ new DiscoveryLocation(
+ new DiscoveryLocation.Definition(this.rootDefinition.kind(), this.rootDefinition.id()),
+ payloadLocation
+ )
+ )
+ )
+ .toList();
+ contextList.addAll(discoveryContexts);
+ List ELs = discoveryContexts
+ .stream()
+ .map(context -> {
+ if (context.ref().mainExpression().isLiteral()) {
+ return Formatter.computeELFromStatic(context, envId);
+ } else {
+ return Formatter.computeELFromEL(context, envId);
+ }
+ })
+ .toList();
+ payloadRefParser.replaceRefs(ELs);
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
new file mode 100644
index 000000000..c0a87ac24
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
@@ -0,0 +1,10 @@
+package com.graviteesource.services.runtimesecrets.config;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record Config(boolean allowOnTheFlySpecs, boolean allowEmptyACLSpecs) {
+ public static final String ALLOW_EMPTY_ACL_SPECS = "api.secrets.allowEmptyNoACLsSpecs";
+ public static final String ALLOW_ON_THE_FLY_SPECS = "api.secrets.allowOnTheFlySpecs";
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
new file mode 100644
index 000000000..8ec071864
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
@@ -0,0 +1,53 @@
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import io.gravitee.node.api.secrets.runtime.discovery.Definition;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class ContextRegistry {
+
+ private final Multimap byName = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byUri = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byUriAndKey = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byDefinitionSpec = MultimapBuilder.hashKeys().arrayListValues().build();
+
+ public void register(DiscoveryContext context, Definition definition) {
+ if (context.ref().mainType() == Ref.MainType.NAME && context.ref().mainExpression().isLiteral()) {
+ byName.put(context.ref().mainExpression().value(), context);
+ }
+ if (context.ref().mainType() == Ref.MainType.URI && context.ref().mainExpression().isLiteral()) {
+ byUri.put(context.ref().mainExpression().value(), context);
+ if (context.ref().secondaryType() == Ref.SecondaryType.KEY && context.ref().secondaryExpression().isLiteral()) {
+ byUriAndKey.put(context.ref().uriAndKey(), context);
+ }
+ }
+ byDefinitionSpec.put(definition, context);
+ }
+
+ public List findBySpec(Spec spec) {
+ List result = new ArrayList<>();
+ if (spec.name() != null && !spec.name().isEmpty()) {
+ result.addAll(byName.get(spec.name()));
+ }
+ if (spec.uri() != null && !spec.uri().isEmpty()) {
+ result.addAll(byUri.get(spec.name()));
+ }
+ if (spec.key() != null && !spec.key().isEmpty()) {
+ result.addAll(byUriAndKey.get(spec.uriAndKey()));
+ }
+ return result;
+ }
+
+ public List getByDefinition(Definition definition) {
+ return (List) byDefinitionSpec.get(definition);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
new file mode 100644
index 000000000..d66827147
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
@@ -0,0 +1,29 @@
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import io.gravitee.node.api.secrets.runtime.discovery.DefinitionBrowser;
+import java.util.Collection;
+import java.util.Optional;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class DefinitionBrowserRegistry {
+
+ private final Collection browsers;
+
+ @Autowired
+ public DefinitionBrowserRegistry(Collection browsers) {
+ this.browsers = browsers;
+ }
+
+ public Optional> findBrowser(T definition) {
+ for (DefinitionBrowser browser : this.browsers) {
+ if (browser.canHandle(definition)) {
+ return Optional.of(browser);
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
new file mode 100644
index 000000000..b8c6c6de3
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
@@ -0,0 +1,80 @@
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+
+public class PayloadRefParser {
+
+ private final StringBuilder payload;
+
+ @Getter(AccessLevel.PACKAGE)
+ private List rawRefs;
+
+ public PayloadRefParser(String payload) {
+ this.payload = new StringBuilder(payload);
+ }
+
+ @AllArgsConstructor
+ static class Position {
+
+ private int start;
+ private int end;
+
+ private void move(int quantity) {
+ start += quantity;
+ end += quantity;
+ }
+ }
+
+ public record RawSecretRef(String ref, Position position) {}
+
+ public List runDiscovery() {
+ int start = 0;
+ List result = new ArrayList<>();
+ do {
+ start = payload.indexOf(RefParser.BEGIN_SEPARATOR, start);
+ if (start >= 0) {
+ int end = payload.indexOf(RefParser.END_SEPARATOR, start);
+ int afterEnd = end + RefParser.END_SEPARATOR.length();
+ String ref = payload.substring(start, afterEnd);
+ result.add(new RawSecretRef(ref, new Position(start, afterEnd)));
+ start = afterEnd;
+ }
+ } while (start >= 0);
+ this.rawRefs = result;
+ return result;
+ }
+
+ public String replaceRefs(List expressions) {
+ if (expressions.size() != this.rawRefs.size()) {
+ throw new IllegalArgumentException("naturalId and replacement list don't match in size");
+ }
+
+ for (int i = 0; i < rawRefs.size(); i++) {
+ String replacement = expressions.get(i);
+
+ // replace naturalId by expression
+ Position position = rawRefs.get(i).position;
+ payload.replace(position.start, position.end, replacement);
+
+ // compute lengthDiff in position
+ int refStringLength = position.end - position.start;
+ int replacementLength = replacement.length();
+ int lengthDiff = replacementLength - refStringLength;
+ // apply offset change on next naturalId positions
+ for (int p = i + 1; p < expressions.size(); p++) {
+ rawRefs.get(p).position.move(lengthDiff);
+ }
+ }
+
+ return payload.toString();
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
new file mode 100644
index 000000000..cc945c56d
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
@@ -0,0 +1,213 @@
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
+
+import com.graviteesource.services.runtimesecrets.errors.SecretRefParsingException;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import java.util.List;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class RefParser {
+
+ public static final String BEGIN_SEPARATOR = "<<";
+ public static final String END_SEPARATOR = ">>";
+ private static final String NAME_TYPE = " name ";
+ private static final int MAX_MAIN_TYPE_SIZE = NAME_TYPE.length();
+ private static final String URI_TYPE = " uri ";
+ private static final String KEY_TYPE = " key ";
+
+ private static final List AFTER_MAIN_TOKENS = List.of(NAME_TYPE, URI_TYPE, KEY_TYPE, END_SEPARATOR);
+
+ private static final char EL_START_CB = '{';
+ private static final char EL_START_HASH = '#';
+ private static final List EL_START = List.of("" + EL_START_CB + EL_START_HASH, "" + EL_START_HASH);
+
+ /**
+ * << [name|uri] [EL_]EXPRESSION [[name | uri | key] [EL_]EXPRESSION] >>
+ *
+ * <space+>
= one or several spaces (\u0020
)
+ * BEGIN_SEPARATOR
"<<
" optionally followed by
+ * END_SEPARATOR
">>
" optionally preceded by
+ * MAIN_TYPE
is literal case-sensitive string: "uri
" or "name
" preceded AND followed by
+ * OPTIONAL_TYPE
extends MAIN_TYPE
with literal "key
"
+ * KEY
is a string or an EL_STRING
that designate the key to get from a secret map
+ * EXPRESSION
is any string NOT starting with '#
' or "{#
". When MAIN_TYPE
is absent or "uri
" then a EL_EXPRESSION
ending with ":
" and followed is an alias of "key KEY
"
+ * EL_EXPRESSION
= string starting with '#
' or "{#
". After parsing {
and }
is removed (a.k.a mixin).
+ *
+ * @param ref the full naturalId (with start and end separator
+ * @return a SecretRef
+ * @throws SecretRefParsingException when parsing fails
+ */
+ public static Ref parse(String ref) {
+ if (ref == null || ref.isBlank()) {
+ throw new SecretRefParsingException("naturalId is null or empty");
+ }
+ var buffer = new StringBuilder(ref);
+ // delete
+ buffer.delete(0, BEGIN_SEPARATOR.length());
+
+ final String typeString = mainType(buffer);
+ final Ref.MainType mainType;
+ try {
+ mainType = Ref.MainType.valueOf(typeString.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw enumError("unknown kind: '%s' for secret reference '%s'".formatted(typeString, ref));
+ }
+ final RefParsing refParsing = mainExpression(buffer);
+
+ Ref.SecondaryType secondaryType = null;
+ if (refParsing.secondaryType() != null) {
+ try {
+ secondaryType = Ref.SecondaryType.valueOf(refParsing.secondaryType().toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw enumError("unknown kind: '%s' for secret reference '%s'".formatted(refParsing.secondaryType(), ref));
+ }
+ if (mainType == Ref.MainType.URI && secondaryType != Ref.SecondaryType.KEY) {
+ throw new SecretRefParsingException(
+ "reference of kind '%s' can only be followed by keyword '%s' or contain '%s' in reference %s".formatted(
+ URI_TYPE.trim(),
+ KEY_TYPE.trim(),
+ URI_KEY_SEPARATOR,
+ ref
+ )
+ );
+ }
+ if (isEL(refParsing.mainExpression)) {
+ throw new SecretRefParsingException(
+ "reference of kind '%s' is using an EL expression, it cannot be followed a keyword in reference %s".formatted(
+ URI_TYPE.trim(),
+ ref
+ )
+ );
+ }
+ }
+
+ return new Ref(
+ mainType,
+ toExpression(refParsing.mainExpression()),
+ secondaryType,
+ secondaryType != null ? toExpression(refParsing.secondaryExpression()) : null,
+ ref
+ );
+ }
+
+ private static String mainType(StringBuilder buffer) {
+ while (buffer.charAt(0) == ' ') {
+ buffer.delete(0, 1);
+ }
+ int typeEnd = buffer.indexOf(" ");
+ // reach end without spaces or bigger than a main kind
+ if (typeEnd == -1 || typeEnd > MAX_MAIN_TYPE_SIZE) {
+ if (buffer.charAt(0) == SecretURL.URL_SEPARATOR) {
+ return URI_TYPE.trim();
+ }
+ if (isEL(buffer.toString())) {
+ throw new SecretRefParsingException(
+ "EL expression must be preceded by '%s' or '%s' when starting the secret reference".formatted(
+ NAME_TYPE.stripLeading(),
+ URI_TYPE.stripLeading()
+ )
+ );
+ }
+ return NAME_TYPE.trim();
+ }
+ String type = buffer.substring(0, typeEnd);
+ buffer.delete(0, typeEnd);
+ return type;
+ }
+
+ record RefParsing(String mainExpression, String secondaryType, String secondaryExpression) {}
+
+ private static RefParsing mainExpression(StringBuilder buffer) {
+ String foundToken = null;
+ String expression = null;
+ int end = 0;
+ for (String token : AFTER_MAIN_TOKENS) {
+ end = buffer.indexOf(token);
+ if (end != -1) {
+ foundToken = token;
+ expression = buffer.substring(0, end);
+ buffer.delete(0, end);
+ break;
+ }
+ }
+
+ if (foundToken == null) {
+ throw new SecretRefParsingException("reference %s syntax is incorrect looks like nothing is specified");
+ }
+
+ String uriOrName = expression.trim();
+ String secondaryType = null;
+ String secondary = null;
+
+ if (!foundToken.equals(END_SEPARATOR)) {
+ secondary = buffer.substring(foundToken.length(), buffer.length() - END_SEPARATOR.length()).trim();
+ secondaryType = foundToken.trim();
+ }
+ if (!foundToken.equals(KEY_TYPE) && expression.contains(URI_KEY_SEPARATOR)) {
+ UriAndKey uriAndKey = parseUriAndKey(expression, end);
+ uriOrName = uriAndKey.uri();
+ secondaryType = KEY_TYPE.trim();
+ secondary = uriAndKey.key();
+ }
+
+ return new RefParsing(uriOrName, secondaryType, secondary);
+ }
+
+ public record UriAndKey(String uri, String key) {
+ public Ref asRef() {
+ return new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression(uri, false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression(key, false),
+ asString()
+ );
+ }
+
+ private String asString() {
+ return Ref.formatUriAndKey(uri, key);
+ }
+ }
+
+ public static UriAndKey parseUriAndKey(String expression, int end) {
+ int keyIndex = expression.indexOf(URI_KEY_SEPARATOR);
+ String uri = expression.substring(0, keyIndex).trim();
+ String key = expression.substring(keyIndex + 1, end).trim();
+ return new UriAndKey(uri, key);
+ }
+
+ private static boolean isEL(String buffer) {
+ if (buffer == null || buffer.isBlank()) {
+ return false;
+ }
+ for (String start : EL_START) {
+ if (buffer.indexOf(start) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static Ref.Expression toExpression(String spec) {
+ if (isEL(spec)) {
+ return new Ref.Expression(cleanEL(spec), true);
+ }
+ return new Ref.Expression(spec, false);
+ }
+
+ private static String cleanEL(String el) {
+ if (el.charAt(0) == EL_START_CB) {
+ return el.substring(1, el.length() - 1);
+ }
+ return el;
+ }
+
+ private static SecretRefParsingException enumError(String typeString) {
+ return new SecretRefParsingException(typeString);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
new file mode 100644
index 000000000..cee1d535d
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
@@ -0,0 +1,25 @@
+package com.graviteesource.services.runtimesecrets.el;
+
+import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import io.gravitee.el.TemplateContext;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+public class ContextUpdater {
+
+ private final Cache cache;
+ private final GrantService grantService;
+ private final SpecLifecycleService specLifecycleService;
+ private final EnvAwareSpecRegistry specRegistry;
+
+ public void addRuntimeSecretsService(TemplateContext context) {
+ context.setVariable("secrets", new Service(cache, grantService, specLifecycleService, specRegistry));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
new file mode 100644
index 000000000..dea549c3b
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
@@ -0,0 +1,91 @@
+package com.graviteesource.services.runtimesecrets.el;
+
+import io.gravitee.node.api.secrets.runtime.discovery.Definition;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import java.util.UUID;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class Formatter {
+
+ public static final String FROM_GRANT_TEMPLATE = "{#secrets.fromGrant('%s', '%s', '%s', %s)}";
+ public static final String METHOD_NAME_SUFFIX = "WithName";
+ public static final String METHOD_URI_SUFFIX = "WithUri";
+ public static final String FROM_GRANT_WITH_TEMPLATE = "{#secrets.fromGrant%s('%s', '%s', '%s', %s)}";
+ public static final String FROM_EL_WITH_TEMPLATE = "{#secrets.fromEL%s('%s', '%s', '%s', '%s'%s)}";
+
+ public static String computeELFromStatic(DiscoveryContext context, String envId) {
+ if (context.ref().mainExpression().isEL()) {
+ throw new IllegalArgumentException("mis-usage this method only supports main secret expression as a literal string");
+ }
+ final String mainSpec = context.ref().mainExpression().value();
+ String el;
+ switch (context.ref().secondaryType()) {
+ case KEY -> el = fromGrant(context.id(), envId, mainSpec, context.ref().secondaryExpression());
+ case NAME -> el = fromGrantWithTemplate(METHOD_NAME_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
+ case URI -> el = fromGrantWithTemplate(METHOD_URI_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
+ default -> {
+ throw new IllegalArgumentException("secondary type unknown: %s".formatted(context.ref().secondaryType()));
+ }
+ }
+ return el;
+ }
+
+ public static String computeELFromEL(DiscoveryContext context, String envId, Definition... others) {
+ if (context.ref().mainExpression().isLiteral()) {
+ throw new IllegalArgumentException("mis-usage this method only supports main secret expression as an EL");
+ }
+ String el;
+ switch (context.ref().mainType()) {
+ case NAME -> el =
+ FROM_EL_WITH_TEMPLATE.formatted(
+ METHOD_NAME_SUFFIX,
+ envId,
+ context.ref().mainExpression().value(),
+ context.location().definition().kind(),
+ context.location().definition().id(),
+ others(context.location().payloadLocations())
+ );
+ case URI -> el =
+ FROM_EL_WITH_TEMPLATE.formatted(
+ METHOD_URI_SUFFIX,
+ envId,
+ context.ref().mainExpression().value(),
+ context.location().definition().kind(),
+ context.location().definition().id(),
+ others(context.location().payloadLocations())
+ );
+ default -> {
+ throw new IllegalArgumentException("main type unknown: %s".formatted(context.ref().secondaryType()));
+ }
+ }
+ return el;
+ }
+
+ private static String others(PayloadLocation... ignoredTODO) {
+ return "";
+ }
+
+ private static String fromGrantWithTemplate(
+ String methodSuffix,
+ UUID id,
+ String envId,
+ String literalExpression,
+ Ref.Expression secondaryExpression
+ ) {
+ return FROM_GRANT_WITH_TEMPLATE.formatted(methodSuffix, id, envId, literalExpression, quoteLiteral(secondaryExpression));
+ }
+
+ private static String fromGrant(UUID id, String envId, String expression, Ref.Expression keySpec) {
+ return FROM_GRANT_TEMPLATE.formatted(id, envId, expression, quoteLiteral(keySpec));
+ }
+
+ private static String quoteLiteral(Ref.Expression expression) {
+ // literal string or EL (as is)
+ return expression.isLiteral() ? "'%s'".formatted(expression.value()) : expression.value();
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
new file mode 100644
index 000000000..be7c59635
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
@@ -0,0 +1,16 @@
+package com.graviteesource.services.runtimesecrets.el;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record Result(Type type, String value) {
+ enum Type {
+ VALUE,
+ EMPTY,
+ NOT_FOUND,
+ KEY_NOT_FOUND,
+ DENIED,
+ ERROR,
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
new file mode 100644
index 000000000..161427246
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
@@ -0,0 +1,138 @@
+package com.graviteesource.services.runtimesecrets.el;
+
+import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
+
+import com.graviteesource.services.runtimesecrets.discovery.RefParser;
+import com.graviteesource.services.runtimesecrets.errors.*;
+import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import io.gravitee.node.api.secrets.model.Secret;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryLocation;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+public class Service {
+
+ private final Cache cache;
+ private final GrantService grantService;
+ private final SpecLifecycleService specLifecycleService;
+ private final EnvAwareSpecRegistry specRegistry;
+
+ public String fromGrant(String token, String envId, String expression, String key) {
+ boolean granted = grantService.isGranted(token);
+ if (!granted) {
+ return resultToValue(new Result(Result.Type.DENIED, "secret [%s] is denied in environment [%s]".formatted(expression, envId)));
+ }
+ return resultToValue(
+ toResult(
+ cache
+ .get(envId, expression)
+ .orElse(
+ new Entry(Entry.Type.EMPTY, null, "no value in cache for [%s] in environment [%s]".formatted(expression, envId))
+ ),
+ key
+ )
+ );
+ }
+
+ public String fromGrantWithName(String token, String envId, String name, String childName) {
+ return null;
+ }
+
+ public String fromGrantWithUri(String token, String envId, String name, String childUri) {
+ return null;
+ }
+
+ public String fromELWithUri(String envId, String uriWithKey, String definitionKind, String definitionId) {
+ if (uriWithKey.contains(URI_KEY_SEPARATOR)) {
+ RefParser.UriAndKey uriAndKey = RefParser.parseUriAndKey(uriWithKey, uriWithKey.length());
+ Ref ref = uriAndKey.asRef();
+ Spec spec = specRegistry.getFromUriAndKey(envId, uriWithKey);
+ if (spec == null && specLifecycleService.shouldDeployOnTheFly(ref)) {
+ spec = specLifecycleService.deployOnTheFly(envId, ref);
+ }
+ return grantAndGet(envId, definitionKind, definitionId, spec, ref, uriAndKey.uri(), uriAndKey.key());
+ } else {
+ return resultToValue(new Result(Result.Type.ERROR, "uri must contain a key like such: /provider/uri:key"));
+ }
+ }
+
+ public String fromELWithName(String envId, String name, String definitionKind, String definitionId) {
+ Ref ref = new Ref(Ref.MainType.NAME, new Ref.Expression(name, false), null, null, name);
+ Spec spec = specRegistry.getFromName(envId, name);
+ return grantAndGet(envId, definitionKind, definitionId, spec, ref, name, spec.key());
+ }
+
+ private String grantAndGet(String envId, String definitionKind, String definitionId, Spec spec, Ref ref, String naturalId, String key) {
+ boolean granted = grantService.authorize(
+ new DiscoveryContext(null, envId, ref, new DiscoveryLocation(new DiscoveryLocation.Definition(definitionKind, definitionId))),
+ spec
+ );
+ if (!granted) {
+ resultToValue(new Result(Result.Type.DENIED, "secret [%s] is denied in environment [%s]".formatted(naturalId, envId)));
+ }
+ return resultToValue(
+ toResult(
+ cache
+ .get(envId, naturalId)
+ .orElse(
+ new Entry(Entry.Type.EMPTY, null, "no value in cache for [%s] in environment [%s]".formatted(naturalId, envId))
+ ),
+ key
+ )
+ );
+ }
+
+ private Result toResult(Entry entry, String key) {
+ Result result;
+ switch (entry.type()) {
+ case VALUE -> {
+ Map secretMap = entry.value();
+ Secret secret = secretMap.get(key);
+ if (secret != null) {
+ result = new Result(Result.Type.VALUE, secret.asString());
+ } else {
+ result = new Result(Result.Type.KEY_NOT_FOUND, "key [%s] not found");
+ }
+ }
+ case EMPTY -> {
+ result = new Result(Result.Type.EMPTY, entry.error());
+ }
+ case NOT_FOUND -> {
+ result = new Result(Result.Type.NOT_FOUND, entry.error());
+ }
+ case ERROR -> {
+ result = new Result(Result.Type.ERROR, entry.error());
+ }
+ default -> result = null;
+ }
+ return result;
+ }
+
+ private String resultToValue(Result result) {
+ if (result != null) {
+ switch (result.type()) {
+ case VALUE -> {
+ return result.value();
+ }
+ case NOT_FOUND -> throw new SecretNotFoundException(result.value());
+ case KEY_NOT_FOUND -> throw new SecretKeyNotFoundException(result.value());
+ case EMPTY -> throw new SecretEmptyException(result.value());
+ case ERROR -> throw new SecretProviderException(result.value());
+ case DENIED -> throw new SecretAccessDeniedException(result.value());
+ }
+ }
+ return null;
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
new file mode 100644
index 000000000..1141a5dad
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretAccessDeniedException extends RuntimeSecretException {
+
+ public SecretAccessDeniedException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
new file mode 100644
index 000000000..a33bb6d63
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretEmptyException extends RuntimeSecretException {
+
+ public SecretEmptyException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
new file mode 100644
index 000000000..f2fc6f4e8
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretKeyNotFoundException extends RuntimeSecretException {
+
+ public SecretKeyNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
new file mode 100644
index 000000000..27cd15442
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretNotFoundException extends RuntimeSecretException {
+
+ public SecretNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
new file mode 100644
index 000000000..ad9af9a76
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretProviderException extends RuntimeSecretException {
+
+ public SecretProviderException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
new file mode 100644
index 000000000..2f060cd7a
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretProviderNotFoundException extends RuntimeSecretException {
+
+ public SecretProviderNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
new file mode 100644
index 000000000..12d0aacde
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretRefParsingException extends RuntimeSecretException {
+
+ public SecretRefParsingException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
new file mode 100644
index 000000000..7370d3886
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
@@ -0,0 +1,14 @@
+package com.graviteesource.services.runtimesecrets.errors;
+
+import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretSpecNotFoundException extends RuntimeSecretException {
+
+ public SecretSpecNotFoundException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
new file mode 100644
index 000000000..84c8833c0
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
@@ -0,0 +1,111 @@
+package com.graviteesource.services.runtimesecrets.grant;
+
+import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_EMPTY_ACL_SPECS;
+import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_ON_THE_FLY_SPECS;
+import static io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation.PLUGIN_KIND;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.errors.SecretSpecNotFoundException;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.spec.ACLs;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Predicate;
+import javax.annotation.Nonnull;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+@Slf4j
+public class DefaultGrantService implements GrantService {
+
+ private final GrantRegistry grantRegistry;
+ private final Config config;
+
+ @Override
+ public boolean authorize(@Nonnull DiscoveryContext context, Spec spec) {
+ if (spec == null) {
+ throw new SecretSpecNotFoundException(
+ "no spec found or created on-the-fly for ref [%s] in envId [%s], %s=%s".formatted(
+ context.ref().rawRef(),
+ context.envId(),
+ ALLOW_ON_THE_FLY_SPECS,
+ config.allowOnTheFlySpecs()
+ )
+ );
+ }
+ if (spec.acls() == null) {
+ if (!config.allowEmptyACLSpecs()) {
+ log.warn(
+ "secret spec for ref [{}] is not granted because is does not contains ACLs and this is not allowed. see: {}",
+ context.ref().rawRef(),
+ ALLOW_EMPTY_ACL_SPECS
+ );
+ return false;
+ } else {
+ return Objects.equals(context.envId(), spec.envId());
+ }
+ }
+
+ return checkACLs(spec, context);
+ }
+
+ public boolean isGranted(@Nonnull String token) {
+ return grantRegistry.exists(token);
+ }
+
+ @Override
+ public void grant(@Nonnull DiscoveryContext context) {
+ grantRegistry.register(context);
+ }
+
+ @Override
+ public void revoke(@Nonnull DiscoveryContext context) {
+ grantRegistry.unregister(context);
+ }
+
+ private boolean checkACLs(Spec spec, DiscoveryContext context) {
+ Predicate noDefKind = acls ->
+ acls.definitions() == null ||
+ acls.definitions().isEmpty() ||
+ acls.definitions().stream().allMatch(def -> def.kind() == null || def.kind().isEmpty());
+
+ Predicate defKind = acls ->
+ acls.definitions().stream().anyMatch(defACLs -> defACLs.kind().contains(context.location().definition().kind()));
+
+ Predicate noDefId = acls ->
+ acls.definitions() == null ||
+ acls.definitions().isEmpty() ||
+ acls.definitions().stream().allMatch(def -> def.ids() == null || def.ids().isEmpty());
+
+ Predicate defId = acls ->
+ acls.definitions().stream().anyMatch(defACLs -> defACLs.ids().contains(context.location().definition().id()));
+
+ Predicate noPlugin = acls -> acls.plugins() == null || acls.plugins().isEmpty();
+
+ Predicate plugin = acls ->
+ acls
+ .plugins()
+ .stream()
+ .anyMatch(pluginACL ->
+ Objects.equals(
+ pluginACL.id(),
+ Arrays
+ .stream(context.location().payloadLocations())
+ .filter(pl -> pl.kind().equals(PLUGIN_KIND))
+ .findFirst()
+ .orElse(PayloadLocation.NOWHERE)
+ .id()
+ )
+ );
+
+ return noDefKind.or(defKind).and(noDefId.or(defId)).and(noPlugin.or(plugin)).test(spec.acls());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
new file mode 100644
index 000000000..fcab420a7
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
@@ -0,0 +1,30 @@
+package com.graviteesource.services.runtimesecrets.grant;
+
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class GrantRegistry {
+
+ private final Map grants = new ConcurrentHashMap<>();
+
+ public void register(DiscoveryContext context) {
+ grants.put(context.id().toString(), null);
+ }
+
+ public void unregister(DiscoveryContext... contexts) {
+ if (contexts != null) {
+ Arrays.stream(contexts).map(DiscoveryContext::id).map(UUID::toString).forEach(grants::remove);
+ }
+ }
+
+ public boolean exists(String token) {
+ return grants.containsKey(token);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
new file mode 100644
index 000000000..db3ba255a
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
@@ -0,0 +1,35 @@
+package com.graviteesource.services.runtimesecrets.providers;
+
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.reactivex.rxjava3.core.Single;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class DefaultRuntimeResolver implements ResolverService {
+
+ private final SecretProviderRegistry secretProviderRegistry;
+
+ public DefaultRuntimeResolver(SecretProviderRegistry secretProviderRegistry) {
+ this.secretProviderRegistry = secretProviderRegistry;
+ }
+
+ @Override
+ public Single resolve(String envId, SecretMount secretMount) {
+ SecretProvider secretProvider = secretProviderRegistry.get(envId, secretMount.provider());
+ return secretProvider
+ .resolve(secretMount)
+ .map(secretMap -> new Entry(Entry.Type.VALUE, secretMap.asMap(), null))
+ .defaultIfEmpty(new Entry(Entry.Type.NOT_FOUND, null, null));
+ }
+
+ @Override
+ public SecretMount toSecretMount(String envId, SecretURL secretURL) {
+ return secretProviderRegistry.get(envId, secretURL.provider()).fromURL(secretURL);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
new file mode 100644
index 000000000..b08d34155
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
@@ -0,0 +1,35 @@
+package com.graviteesource.services.runtimesecrets.providers;
+
+import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import java.util.Map;
+import org.springframework.core.env.Environment;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class FromConfigurationSecretProviderDeployer implements SecretProviderDeployer {
+
+ private boolean init;
+ private final Environment environment;
+
+ public FromConfigurationSecretProviderDeployer(Environment environment) {
+ this.environment = environment;
+ }
+
+ public void init() {
+ if (!init) {
+ doInit();
+ init = true;
+ }
+ }
+
+ private void doInit() {
+ // TODO
+ }
+
+ @Override
+ public void deploy(String id, String pluginId, Map config, String envId) {
+ // TODO
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
new file mode 100644
index 000000000..a309aa278
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
@@ -0,0 +1,49 @@
+package com.graviteesource.services.runtimesecrets.providers;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import com.graviteesource.services.runtimesecrets.errors.SecretProviderNotFoundException;
+import io.gravitee.node.api.secrets.SecretProvider;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretProviderRegistry {
+
+ Multimap perEnv = MultimapBuilder.hashKeys().arrayListValues().build();
+ Map allEnvs = new HashMap<>();
+
+ public void register(String id, SecretProvider provider, String envId) {
+ if (envId == null || envId.isEmpty()) {
+ allEnvs.put(id, provider);
+ } else {
+ perEnv.put(envId, new SecretProviderEntry(id, provider));
+ }
+ }
+
+ /**
+ *
+ * @param envId environment ID
+ * @param id is of the provider
+ * @return a secret provider
+ * @throws SecretProviderNotFoundException if the provider is not found
+ */
+ public SecretProvider get(String envId, String id) {
+ return perEnv
+ .get(envId)
+ .stream()
+ .filter(entry -> entry.id().equals(id))
+ .map(SecretProviderEntry::provider)
+ .findFirst()
+ .or(() -> Optional.ofNullable(allEnvs.get(id)))
+ .orElseThrow(() ->
+ new SecretProviderNotFoundException("Cannot find secret provider with id [%s] for environmentID [%s]".formatted(id, envId))
+ );
+ }
+
+ public record SecretProviderEntry(String id, SecretProvider provider) {}
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
new file mode 100644
index 000000000..3ea641241
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
@@ -0,0 +1,84 @@
+package com.graviteesource.services.runtimesecrets.spec;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+public class DefaultSpecLifecycleService implements SpecLifecycleService {
+
+ private final EnvAwareSpecRegistry specRegistry;
+ private final Cache cache;
+ private final ResolverService resolverService;
+ private final Config config;
+
+ @Override
+ public boolean shouldDeployOnTheFly(Ref ref) {
+ return (ref.mainType() == Ref.MainType.URI && ref.mainExpression().isLiteral() && config.allowOnTheFlySpecs());
+ }
+
+ @Override
+ public Spec deployOnTheFly(String envId, Ref ref) {
+ Spec runtimeSpec = ref.toRuntimeSpec(envId);
+ cache.computeIfAbsent(
+ envId,
+ runtimeSpec.naturalId(),
+ () -> {
+ specRegistry.register(envId, runtimeSpec);
+ SecretURL secretURL = runtimeSpec.toSecretURL();
+ SecretMount mount = resolverService.toSecretMount(envId, secretURL).withoutRetries();
+ return resolverService
+ .resolve(envId, mount)
+ .subscribeOn(Schedulers.io())
+ .onErrorResumeNext(t -> {
+ Entry entry = new Entry(Entry.Type.ERROR, null, t.getMessage());
+ asyncResolution(runtimeSpec);
+ return Single.just(entry);
+ })
+ .blockingGet();
+ }
+ );
+
+ return runtimeSpec;
+ }
+
+ private Disposable asyncResolution(Spec spec) {
+ SecretURL secretURL = spec.toSecretURL();
+ String envId = spec.envId();
+ SecretMount mount = resolverService.toSecretMount(envId, secretURL);
+ return resolverService
+ .resolve(envId, mount)
+ .subscribeOn(Schedulers.io())
+ .onErrorResumeNext(t -> Single.just(new Entry(Entry.Type.ERROR, null, t.getMessage())))
+ .subscribe(entry -> cache.put(envId, spec.naturalId(), entry));
+ }
+
+ @Override
+ public void deploy(Spec spec) {
+ specRegistry.register(spec.envId(), spec);
+ // TODO check diff
+ // TODO if change clean by old name or uri
+ Disposable disposable = asyncResolution(spec/*, cleanupLambda*/);
+ }
+
+ @Override
+ public void undeploy(Spec spec) {
+ specRegistry.unregister(spec.envId(), spec);
+ cache.evict(spec.envId(), spec.naturalId());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
new file mode 100644
index 000000000..d56004af9
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
@@ -0,0 +1,51 @@
+package com.graviteesource.services.runtimesecrets.spec.registry;
+
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EnvAwareSpecRegistry {
+
+ private final Map registries = new HashMap<>();
+
+ public void register(String envId, Spec spec) {
+ registry(envId).register(spec);
+ }
+
+ public void unregister(String envId, Spec spec) {
+ registry(envId).unregister(spec);
+ }
+
+ public Spec getFromName(String envId, String name) {
+ return registry(envId).getFromName(name);
+ }
+
+ public Spec getFromUri(String envId, String uri) {
+ return registry(envId).getFromUri(uri);
+ }
+
+ public Spec getFromUriAndKey(String envId, String uriAndKey) {
+ return registry(envId).getFromUriAndKey(uriAndKey);
+ }
+
+ public Spec getFromID(String envId, String id) {
+ return registry(envId).getFromID(id);
+ }
+
+ public Spec fromSpec(String envId, Spec query) {
+ return registry(envId).fromSpec(query);
+ }
+
+ public Spec fromRef(String envId, Ref query) {
+ return registry(envId).fromRef(query);
+ }
+
+ private SpecRegistry registry(String envId) {
+ return registries.computeIfAbsent(envId, ignore -> new SpecRegistry());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
new file mode 100644
index 000000000..0eb4bfe87
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
@@ -0,0 +1,91 @@
+package com.graviteesource.services.runtimesecrets.spec.registry;
+
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+class SpecRegistry {
+
+ private final Map byName = new HashMap<>();
+ private final Map byUri = new HashMap<>();
+ private final Map byUriAndKey = new HashMap<>();
+ private final Map byID = new HashMap<>();
+
+ void register(Spec spec) {
+ if (spec.id() != null) {
+ byID.put(spec.id(), spec);
+ }
+ if (spec.name() != null) {
+ byName.put(spec.name(), spec);
+ }
+ if (spec.uri() != null) {
+ byUri.put(spec.uri(), spec);
+ if (spec.key() != null) {
+ byUriAndKey.put(spec.uriAndKey(), spec);
+ }
+ }
+ }
+
+ void unregister(Spec spec) {
+ if (spec.id() != null) {
+ byID.remove(spec.id());
+ }
+ if (spec.uri() != null) {
+ byUri.remove(spec.uri());
+ if (spec.key() != null) {
+ byUriAndKey.remove(spec.uriAndKey(), spec);
+ }
+ }
+ if (spec.name() != null) {
+ byName.remove(spec.name());
+ }
+ }
+
+ Spec getFromName(String name) {
+ return byName.get(name);
+ }
+
+ Spec getFromUri(String uri) {
+ return byUri.get(uri);
+ }
+
+ Spec getFromUriAndKey(String uriAndKey) {
+ return byUriAndKey.get(uriAndKey);
+ }
+
+ Spec getFromID(String id) {
+ return byID.get(id);
+ }
+
+ Spec fromRef(Ref query) {
+ if (query.mainType() == Ref.MainType.NAME) {
+ return byName.get(query.mainExpression().value());
+ }
+ if (query.mainType() == Ref.MainType.URI) {
+ if (query.secondaryType() == Ref.SecondaryType.KEY) {
+ return byUriAndKey.get(query.mainExpression().value());
+ }
+ return byUri.get(query.mainExpression().value());
+ }
+ return null;
+ }
+
+ Spec fromSpec(Spec query) {
+ if (query.id() != null) {
+ return byID.get(query.id());
+ } else if (query.name() != null) {
+ return byName.get(query.name());
+ } else if (query.uri() != null) {
+ if (query.key() != null) {
+ return byUriAndKey.get(query.uriAndKey());
+ }
+ return byUri.get(query.uri());
+ }
+ return null;
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
new file mode 100644
index 000000000..63ed829ee
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
@@ -0,0 +1,116 @@
+package com.graviteesource.services.runtimesecrets.spring;
+
+import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_EMPTY_ACL_SPECS;
+import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_ON_THE_FLY_SPECS;
+
+import com.graviteesource.services.runtimesecrets.RuntimeSecretProcessingService;
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.discovery.ContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
+import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
+import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
+import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
+import com.graviteesource.services.runtimesecrets.providers.DefaultRuntimeResolver;
+import com.graviteesource.services.runtimesecrets.providers.FromConfigurationSecretProviderDeployer;
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import com.graviteesource.services.runtimesecrets.spec.DefaultSpecLifecycleService;
+import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
+import io.gravitee.node.api.secrets.runtime.discovery.DefinitionBrowser;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import java.util.List;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.*;
+import org.springframework.core.env.Environment;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class BeanFactory {
+
+ @Bean
+ Config config(
+ @Value("${" + ALLOW_ON_THE_FLY_SPECS + ":true}") boolean allowRuntimeSpecs,
+ @Value("${" + ALLOW_EMPTY_ACL_SPECS + ":true}") boolean allowEmptyACLSpecs
+ ) {
+ return new Config(allowRuntimeSpecs, allowEmptyACLSpecs);
+ }
+
+ @Bean
+ RuntimeSecretProcessingService runtimeSecretProcessingService(
+ DefinitionBrowserRegistry definitionBrowserRegistry,
+ SpecLifecycleService specLifecycleService,
+ GrantService grantService,
+ EnvAwareSpecRegistry specRegistry
+ ) {
+ return new RuntimeSecretProcessingService(
+ definitionBrowserRegistry,
+ new ContextRegistry(),
+ grantService,
+ specLifecycleService,
+ specRegistry
+ );
+ }
+
+ @Bean
+ DefinitionBrowserRegistry definitionBrowserRegistry(List browsers) {
+ return new DefinitionBrowserRegistry(browsers);
+ }
+
+ @Bean
+ SpecLifecycleService secretSpecService(Cache cache, ResolverService resolverService, Config config) {
+ return new DefaultSpecLifecycleService(new EnvAwareSpecRegistry(), cache, resolverService, config);
+ }
+
+ @Bean
+ Cache secretCache() {
+ return new SimpleOffHeapCache();
+ }
+
+ @Bean
+ GrantService grantService(Config config) {
+ return new DefaultGrantService(new GrantRegistry(), config);
+ }
+
+ @Bean
+ EnvAwareSpecRegistry envAwareSpecRegistry() {
+ return new EnvAwareSpecRegistry();
+ }
+
+ @Bean
+ @Conditional(EnvironmentCondition.class)
+ SecretProviderDeployer runtimeSecretProviderDeployer(Environment environment) {
+ return new FromConfigurationSecretProviderDeployer(environment);
+ }
+
+ @Bean
+ ResolverService runtimeSecretResolver() {
+ SecretProviderRegistry secretProviderRegistry = new SecretProviderRegistry();
+ return new DefaultRuntimeResolver(secretProviderRegistry);
+ }
+
+ @Bean
+ ContextUpdater elContextUpdater(
+ Cache cache,
+ GrantService grantService,
+ SpecLifecycleService specLifecycleService,
+ EnvAwareSpecRegistry specRegistry
+ ) {
+ return new ContextUpdater(cache, grantService, specLifecycleService, specRegistry);
+ }
+
+ static class EnvironmentCondition implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata ignore) {
+ return context.getEnvironment().getProperty("api.secrets.allowProvidersFromConfiguration", Boolean.class, true);
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
new file mode 100644
index 000000000..970c85700
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
@@ -0,0 +1,92 @@
+package com.graviteesource.services.runtimesecrets.storage;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import io.gravitee.node.api.secrets.model.Secret;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SimpleOffHeapCache implements Cache {
+
+ private final Kryo kryo;
+
+ public SimpleOffHeapCache() {
+ this.kryo = new Kryo();
+ kryo.register(Secret.class);
+ kryo.register(Entry.class);
+ kryo.register(Entry.Type.class);
+ kryo.register(HashMap.class);
+ }
+
+ private final ConcurrentMap data = new ConcurrentHashMap<>();
+
+ @Override
+ public CacheKey put(String envId, String naturalId, Entry value) {
+ final CacheKey cacheKey = new CacheKey(envId, naturalId);
+ var bytes = serialize(value);
+ final ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bytes.length);
+ data.put(cacheKey, byteBuffer);
+ byteBuffer.put(bytes);
+ return cacheKey;
+ }
+
+ @Override
+ public Optional get(String envId, String naturalId) {
+ ByteBuffer byteBuffer = data.get(new CacheKey(envId, naturalId));
+ if (byteBuffer != null) {
+ byte[] buf = new byte[byteBuffer.limit()];
+ byteBuffer.position(0);
+ byteBuffer.get(buf, 0, buf.length);
+ return Optional.of(deserialize(buf));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public void computeIfAbsent(String envId, String naturalId, Supplier supplier) {
+ data.computeIfAbsent(
+ new CacheKey(envId, naturalId),
+ key -> {
+ Entry value = supplier.get();
+ byte[] stringAsBytes = serialize(value);
+ ByteBuffer byteBuffer = ByteBuffer.allocateDirect(stringAsBytes.length);
+ byteBuffer.put(stringAsBytes);
+ return byteBuffer;
+ }
+ );
+ }
+
+ @Override
+ public void evict(String envId, String naturalId) {
+ data.remove(new CacheKey(envId, naturalId));
+ }
+
+ public byte[] serialize(Entry value) {
+ try (ByteArrayOutputStream bytes = new ByteArrayOutputStream(); Output out = new Output(bytes)) {
+ this.kryo.writeObject(out, value);
+ return out.toBytes();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ public Entry deserialize(byte[] bytes) {
+ try (Input in = new Input(bytes)) {
+ return this.kryo.readObject(in, Entry.class);
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java
new file mode 100644
index 000000000..ee67ec284
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java
@@ -0,0 +1,128 @@
+package com.graviteesource.services.runtimesecrets.api.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.graviteesource.services.runtimesecrets.discovery.RefParser;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class RefTest {
+
+ public static Stream okRefs() {
+ return Stream.of(
+ arguments(
+ "static uri short",
+ "<< /vault/secrets/partners/apikeys:tesco >>",
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/vault/secrets/partners/apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("tesco", false),
+ "<< /vault/secrets/partners/apikeys:tesco >>"
+ )
+ ),
+ arguments(
+ "static uri prefix",
+ "<< uri /vault/secrets/partners/apikeys:tesco >>",
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/vault/secrets/partners/apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("tesco", false),
+ "<< uri /vault/secrets/partners/apikeys:tesco >>"
+ )
+ ),
+ arguments(
+ "static uri long",
+ "<< uri /vault/secrets/partners/apikeys key tesco >>",
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/vault/secrets/partners/apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("tesco", false),
+ "<< uri /vault/secrets/partners/apikeys key tesco >>"
+ )
+ ),
+ arguments(
+ "static name short",
+ "<< partners-apikeys >>",
+ new Ref(Ref.MainType.NAME, new Ref.Expression("partners-apikeys", false), null, null, "<< partners-apikeys >>")
+ ),
+ arguments(
+ "static name prefix",
+ "<< name partners-apikeys >>",
+ new Ref(Ref.MainType.NAME, new Ref.Expression("partners-apikeys", false), null, null, "<< name partners-apikeys >>")
+ ),
+ arguments(
+ "dyn EL key",
+ "<< uri /vault/secrets/partners/apikeys key {#context.attributes['secret-key']} >>",
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/vault/secrets/partners/apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("#context.attributes['secret-key']", true),
+ "<< uri /vault/secrets/partners/apikeys key {#context.attributes['secret-key']} >>"
+ )
+ ),
+ arguments(
+ "dyn EL key short",
+ "<< /vault/secrets/partners/apikeys:{#context.attributes['secret-key']} >>",
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/vault/secrets/partners/apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("#context.attributes['secret-key']", true),
+ "<< /vault/secrets/partners/apikeys:{#context.attributes['secret-key']} >>"
+ )
+ ),
+ arguments(
+ "name and dyn EL key short",
+ "<< partners-apikeys:{#context.attributes['secret-key']} >>",
+ new Ref(
+ Ref.MainType.NAME,
+ new Ref.Expression("partners-apikeys", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression("#context.attributes['secret-key']", true),
+ "<< partners-apikeys:{#context.attributes['secret-key']} >>"
+ )
+ ),
+ arguments(
+ "dynamic name",
+ "<< name #context.attributes['secret-uri'] >>",
+ new Ref(
+ Ref.MainType.NAME,
+ new Ref.Expression("#context.attributes['secret-uri']", true),
+ null,
+ null,
+ "<< name #context.attributes['secret-uri'] >>"
+ )
+ )
+ );
+ }
+
+ @MethodSource("okRefs")
+ @ParameterizedTest(name = "{0}")
+ void should_parse(String name, String given, Ref expected) {
+ assertThat(RefParser.parse(given)).usingRecursiveComparison().isEqualTo(expected);
+ }
+
+ @Test
+ void should_parse_uri_and_key() {
+ String expression = "/provider/secret:password";
+ assertThat(RefParser.parseUriAndKey(expression, expression.length()))
+ .usingRecursiveComparison()
+ .isEqualTo(new RefParser.UriAndKey("/provider/secret", "password"));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
new file mode 100644
index 000000000..bed5a9e37
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
@@ -0,0 +1,88 @@
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class RefDiscovererTest {
+
+ public static Stream payloads() {
+ return Stream.of(
+ arguments(
+ "one smaller",
+ """
+ {
+ "username": "admin",
+ "password": "<< uri /vault/secrets/redis:password >>"
+ }
+ """,
+ List.of("<< uri /vault/secrets/redis:password >>"),
+ List.of("secret"),
+ List.of("\"password\": \"secret\"")
+ ),
+ arguments(
+ "one bigger",
+ """
+ {
+ "username": "admin",
+ "password": "<< uri /vault/secrets/redis:password >>"
+ }
+ """,
+ List.of("<< uri /vault/secrets/redis:password >>"),
+ List.of(
+ "{#secret.fromGrant('262fc907-ef40-47e0-b076-001ca79282da', 'f49ea02d-cd44-44dd-aa6d-04bb2a57a6ac-/vault/secrets/redis', 'password')}"
+ ),
+ List.of(
+ "\"password\": \"{#secret.fromGrant('262fc907-ef40-47e0-b076-001ca79282da', 'f49ea02d-cd44-44dd-aa6d-04bb2a57a6ac-/vault/secrets/redis', 'password')}\""
+ )
+ ),
+ arguments(
+ "mixed bigger",
+ """
+ {
+ "username": "admin",
+ "password": "<< uri /vault/secrets/redis:password >>"
+ "token": "<< name redis-token >>"
+ }
+ """,
+ List.of("<< uri /vault/secrets/redis:password >>", "<< name redis-token >>"),
+ List.of(
+ "{#secret.fromGrant('ce06fe04-3cd4-4513-bf15-5aa446ab2c27', 'f49ea02d-cd44-44dd-aa6d-04bb2a57a6ac-/vault/secrets/redis', 'password')}",
+ "ABCDEFGH"
+ ),
+ List.of(
+ "\"password\": \"{#secret.fromGrant('ce06fe04-3cd4-4513-bf15-5aa446ab2c27', 'f49ea02d-cd44-44dd-aa6d-04bb2a57a6ac-/vault/secrets/redis', 'password')}\"",
+ "\"token\": \"ABCDEFGH\""
+ )
+ )
+ );
+ }
+
+ @MethodSource("payloads")
+ @ParameterizedTest(name = "{0}")
+ void should_replace_refs_in_payloads(
+ String name,
+ String payload,
+ List refs,
+ List replacements,
+ List testString
+ ) {
+ PayloadRefParser disco = new PayloadRefParser(payload);
+ disco.runDiscovery();
+ assertThat(disco.getRawRefs().stream().map(PayloadRefParser.RawSecretRef::ref)).containsAnyElementsOf(refs);
+ String result = disco.replaceRefs(replacements);
+ assertThat(result).contains(testString);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
new file mode 100644
index 000000000..a996f4a40
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
@@ -0,0 +1,134 @@
+package com.graviteesource.services.runtimesecrets.grant;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryLocation;
+import io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.ACLs;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.List;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class DefaultGrantServiceTest {
+
+ private DefaultGrantService cut;
+
+ @BeforeEach
+ void setup() {
+ Config config = new Config(true, true);
+ this.cut = new DefaultGrantService(new GrantRegistry(), config);
+ }
+
+ public static Stream grants() {
+ return Stream.of(
+ arguments("null spec", context("dev", "api", "123"), null, true, "no spec found"),
+ arguments("no acl same env", context("dev", "api", "123"), spec("dev", null), true, null),
+ arguments("empty acl same env", context("dev", "api", "123"), spec("dev", new ACLs(List.of(), List.of())), true, null),
+ arguments("no acl diff env", context("dev", "api", "123"), spec("test", null), false, null),
+ arguments(
+ "def acl ok",
+ context("dev", "api", "123"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null)),
+ true,
+ null
+ ),
+ arguments(
+ "def acl wrong id",
+ context("dev", "api", "123"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("456"))), null)),
+ false,
+ null
+ ),
+ arguments(
+ "def acl wrong kind",
+ context("dev", "api", "123"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("dict", List.of("123"))), null)),
+ false,
+ null
+ ),
+ arguments(
+ "def acl many",
+ context("dev", "api", "123"),
+ spec(
+ "dev",
+ new ACLs(
+ List.of(new ACLs.DefinitionACL("dict", List.of("123")), new ACLs.DefinitionACL("api", List.of("123", "456"))),
+ null
+ )
+ ),
+ true,
+ null
+ ),
+ arguments(
+ "plugin acl ok",
+ context("dev", "api", "123", plugin("foo")),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("foo", null)))),
+ true,
+ null
+ ),
+ arguments(
+ "plugin acl ko",
+ context("dev", "api", "123", plugin("foo")),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("bar", null)))),
+ false,
+ null
+ ),
+ arguments(
+ "plugin acl ok many",
+ context("dev", "api", "123", plugin("foo")),
+ spec(
+ "dev",
+ new ACLs(
+ List.of(new ACLs.DefinitionACL("api", List.of("123"))),
+ List.of(new ACLs.PluginACL("bar", null), new ACLs.PluginACL("foo", null))
+ )
+ ),
+ true,
+ null
+ )
+ );
+ }
+
+ @MethodSource("grants")
+ @ParameterizedTest(name = "{0}")
+ void should_authorize(String name, DiscoveryContext context, Spec spec, boolean granted, String error) {
+ if (error != null) {
+ assertThatCode(() -> cut.authorize(context, spec)).hasMessageContaining(error);
+ } else {
+ assertThat(cut.authorize(context, spec)).isEqualTo(granted);
+ }
+ }
+
+ static DiscoveryContext context(String env, String kind, String id, PayloadLocation... payloads) {
+ return new DiscoveryContext(
+ null,
+ env,
+ new Ref(Ref.MainType.NAME, new Ref.Expression("secret", false), null, null, "<< secret >>"),
+ new DiscoveryLocation(new DiscoveryLocation.Definition(kind, id), payloads)
+ );
+ }
+
+ static Spec spec(String env, ACLs acls) {
+ return new Spec(null, "secret", null, null, null, false, false, null, acls, env);
+ }
+
+ static PayloadLocation plugin(String id) {
+ return new PayloadLocation(PayloadLocation.PLUGIN_KIND, id);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
new file mode 100644
index 000000000..5b5deeba8
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
@@ -0,0 +1,104 @@
+package com.graviteesource.services.runtimesecrets.storage;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.gravitee.node.api.secrets.model.Secret;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import java.util.Map;
+import java.util.Optional;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class SimpleOffHeapCacheTest {
+
+ private final Cache cut = new SimpleOffHeapCache();
+
+ @Test
+ void should_store_error_entries() {
+ cut.put("dev", "secret_error", new Entry(Entry.Type.ERROR, null, "500"));
+ cut.put("test", "secret_empty", new Entry(Entry.Type.EMPTY, null, "204"));
+ cut.put("prod", "secret_not_found", new Entry(Entry.Type.NOT_FOUND, null, "404"));
+
+ assertThat(cut.get("dev", "secret_error")).get().extracting("type", "error").containsExactly(Entry.Type.ERROR, "500");
+ assertThat(cut.get("test", "secret_empty")).get().extracting("type", "error").containsExactly(Entry.Type.EMPTY, "204");
+ assertThat(cut.get("prod", "secret_not_found"))
+ .isPresent()
+ .get()
+ .extracting("type", "error")
+ .containsExactly(Entry.Type.NOT_FOUND, "404");
+ }
+
+ @Test
+ void should_store_segmented_data() {
+ cut.put("dev", "secret", new Entry(Entry.Type.VALUE, Map.of("foo", new Secret("bar")), null));
+ cut.put("test", "secret", new Entry(Entry.Type.VALUE, Map.of("buz", new Secret("puk")), null));
+ assertThat(cut.get("dev", "secret"))
+ .get()
+ .usingRecursiveAssertion()
+ .isEqualTo(new Entry(Entry.Type.VALUE, Map.of("foo", new Secret("bar")), null));
+ assertThat(cut.get("test", "secret"))
+ .get()
+ .usingRecursiveAssertion()
+ .isEqualTo(new Entry(Entry.Type.VALUE, Map.of("buz", new Secret("puk")), null));
+ }
+
+ @Test
+ void should_perform_crud_ops() {
+ cut.put(
+ "dev",
+ "secret",
+ new Entry(Entry.Type.VALUE, Map.of("redis-password", new Secret("123456"), "ldap-password", new Secret("azerty")), null)
+ );
+ assertThat(cut.get("dev", "secret"))
+ .get()
+ .extracting(entry -> entry.value().values().stream().map(Secret::asString).toList())
+ .asInstanceOf(InstanceOfAssertFactories.LIST)
+ .containsExactlyInAnyOrder("123456", "azerty");
+ assertThat(cut.get("dev", "secret"))
+ .get()
+ .extracting(entry -> entry.value().keySet())
+ .asInstanceOf(InstanceOfAssertFactories.COLLECTION)
+ .containsExactlyInAnyOrder("redis-password", "ldap-password");
+
+ // override and test it was really done
+ Entry dbPasswords = new Entry(
+ Entry.Type.VALUE,
+ Map.of("mongodb-password", new Secret("778899"), "mysql-password", new Secret("qwerty")),
+ null
+ );
+ cut.put("dev", "secret", dbPasswords);
+ dbPasswordsAssert(cut.get("dev", "secret"));
+
+ // no override as does not exists
+ cut.computeIfAbsent("dev", "secret", () -> new Entry(Entry.Type.VALUE, Map.of(), null));
+ dbPasswordsAssert(cut.get("dev", "secret"));
+
+ // eviction
+ cut.evict("dev", "secret");
+ assertThat(cut.get("dev", "secret")).isNotPresent();
+
+ cut.computeIfAbsent("dev", "secret", () -> dbPasswords);
+ dbPasswordsAssert(cut.get("dev", "secret"));
+ }
+
+ private void dbPasswordsAssert(Optional optEntry) {
+ assertThat(optEntry)
+ .get()
+ .extracting(entry -> entry.value().values().stream().map(Secret::asString).toList())
+ .asInstanceOf(InstanceOfAssertFactories.LIST)
+ .containsExactlyInAnyOrder("778899", "qwerty");
+ assertThat(optEntry)
+ .get()
+ .extracting(entry -> entry.value().keySet())
+ .asInstanceOf(InstanceOfAssertFactories.COLLECTION)
+ .containsExactlyInAnyOrder("mongodb-password", "mysql-password");
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcher.java b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcher.java
index 4bff56c9c..a89502f72 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcher.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcher.java
@@ -156,7 +156,7 @@ public boolean canResolveSingleValue(String location) {
* @throws IllegalArgumentException if the URL is well formatted
*/
public SecretMount toSecretMount(String location) {
- SecretURL url = SecretURL.from(location);
+ SecretURL url = SecretURL.from(location, true);
return this.findSecretProvider(url.provider())
.map(secretProvider -> {
try {
diff --git a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/resolver/GraviteeConfigurationSecretPropertyResolver.java b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/resolver/GraviteeConfigurationSecretPropertyResolver.java
index ea5adf11e..2fd5f381a 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/resolver/GraviteeConfigurationSecretPropertyResolver.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/resolver/GraviteeConfigurationSecretPropertyResolver.java
@@ -53,7 +53,7 @@ public Maybe resolve(String location) {
@Override
public boolean isWatchable(String value) {
- return SecretURL.from(value).isWatchable();
+ return SecretURL.from(value, true).isWatchable();
}
@Override
diff --git a/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcherTest.java b/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcherTest.java
index f72760096..5a973981f 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcherTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/conf/GraviteeConfigurationSecretResolverDispatcherTest.java
@@ -99,22 +99,22 @@ void should_create_secret_provider_and_watch_filtered() {
SecretMap first = cut.watch(secretMount).blockingFirst();
SecretMap last = cut.watch(secretMount).blockingLast();
assertThat(first).isNotEqualTo(last).isNotNull();
- assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null))).isPresent();
- assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null))).isNotPresent();
- assertThat(last.getSecret(new SecretMount(null, null, "created_flag", null))).isNotPresent();
- assertThat(last.getSecret(new SecretMount(null, null, "updated_flag", null))).isPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null, true))).isPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null, true))).isNotPresent();
+ assertThat(last.getSecret(new SecretMount(null, null, "created_flag", null, true))).isNotPresent();
+ assertThat(last.getSecret(new SecretMount(null, null, "updated_flag", null, true))).isPresent();
first = cut.watch(secretMount, SecretEvent.Type.UPDATED).blockingFirst();
last = cut.watch(secretMount, SecretEvent.Type.UPDATED).blockingLast();
assertThat(first).isEqualTo(last).isNotNull();
- assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null))).isNotPresent();
- assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null))).isPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null, true))).isNotPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null, true))).isPresent();
first = cut.watch(secretMount, SecretEvent.Type.CREATED).blockingFirst();
last = cut.watch(secretMount, SecretEvent.Type.CREATED).blockingLast();
assertThat(first).isEqualTo(last).isNotNull();
- assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null))).isPresent();
- assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null))).isNotPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "created_flag", null, true))).isPresent();
+ assertThat(first.getSecret(new SecretMount(null, null, "updated_flag", null, true))).isNotPresent();
Iterable all = cut.watch(secretMount, SecretEvent.Type.DELETED).blockingIterable();
assertThat(all).isEmpty();
diff --git a/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/test/TestSecretProvider.java b/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/test/TestSecretProvider.java
index f533cdf79..f1fdb7afc 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/test/TestSecretProvider.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-service/src/test/java/io/gravitee/node/secrets/service/test/TestSecretProvider.java
@@ -47,6 +47,6 @@ public SecretMount fromURL(SecretURL url) {
if (!url.path().equals("test")) {
throw new IllegalArgumentException();
}
- return new SecretMount(url.provider(), new SecretLocation(Map.of("path", url.path())), url.key(), url);
+ return new SecretMount(url.provider(), new SecretLocation(Map.of("path", url.path())), url.key(), url, true);
}
}
diff --git a/gravitee-node-secrets/pom.xml b/gravitee-node-secrets/pom.xml
index 1094b833d..954bf70f2 100644
--- a/gravitee-node-secrets/pom.xml
+++ b/gravitee-node-secrets/pom.xml
@@ -34,6 +34,7 @@
gravitee-node-secrets-plugin-handler
gravitee-node-secrets-service
+ gravitee-node-secrets-runtime
@@ -51,4 +52,12 @@
+
+
+ com.esotericsoftware
+ kryo
+ 5.6.0
+
+
+
From 4a7d67d51997b383483fb124e618a6fbee224c69 Mon Sep 17 00:00:00 2001
From: Benoit Bordigoni
Date: Tue, 17 Sep 2024 16:24:24 +0200
Subject: [PATCH 02/15] feat: add mock secret provider plugin
---
.../gravitee-secret-provider-mock/pom.xml | 136 ++++++++++++++++++
.../src/main/assembly/plugin-assembly.xml | 48 +++++++
.../plugin/mock/MockSecretLocation.java | 23 +++
.../plugin/mock/MockSecretProvider.java | 44 ++++++
.../mock/MockSecretProviderConfiguration.java | 31 ++++
.../mock/MockSecretProviderFactory.java | 16 +++
.../src/main/resources/plugin.properties | 7 +
.../plugin/mock/MockSecretProviderTest.java | 52 +++++++
gravitee-node-secrets/pom.xml | 1 +
9 files changed, 358 insertions(+)
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/assembly/plugin-assembly.xml
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretLocation.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/resources/plugin.properties
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml b/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
new file mode 100644
index 000000000..1862aee66
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ 4.0.0
+
+ io.gravitee.node
+ gravitee-node-secrets
+ 6.4.2
+
+
+ gravitee-secret-provider-mock
+ Gravitee.io - Node - Secrets - Mock Provider
+
+
+ 3.7.1
+ 3.2.6
+ 1.7.0
+ 1.2.1
+
+ plugins/secret-providers
+
+
+
+
+ io.gravitee.node
+ gravitee-node-api
+
+
+ io.gravitee.node
+ gravitee-node-secrets-plugin-handler
+
+
+ io.reactivex.rxjava3
+ rxjava
+
+
+
+ org.assertj
+ assertj-core
+
+
+
+
+
+ src/main/resources
+ true
+
+
+
+
+
+ com.mycila
+ license-maven-plugin
+
+
+ */**
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ ${maven-plugin-nexus-staging.version}
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-plugin-gpg.version}
+
+ true
+
+
+
+ org.codehaus.mojo
+ properties-maven-plugin
+ ${maven-plugin-properties.version}
+
+
+ initialize
+ load-plugin-properties
+
+ read-project-properties
+
+
+
+ ${project.basedir}/src/main/resources/plugin.properties
+
+ false
+
+
+
+
+
+ maven-assembly-plugin
+ ${maven-plugin-assembly.version}
+
+ false
+
+ src/main/assembly/plugin-assembly.xml
+
+
+
+
+ make-resource-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/assembly/plugin-assembly.xml b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/assembly/plugin-assembly.xml
new file mode 100644
index 000000000..d1cee7e11
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/assembly/plugin-assembly.xml
@@ -0,0 +1,48 @@
+
+
+
+ plugin
+
+ zip
+
+ false
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}.jar
+
+
+
+
+
+
+ src/main/resources/schemas
+ schemas
+
+
+
+
+
+
+ lib
+ false
+
+
+
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretLocation.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretLocation.java
new file mode 100644
index 000000000..25caaf49c
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretLocation.java
@@ -0,0 +1,23 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+import io.gravitee.node.api.secrets.model.SecretLocation;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record MockSecretLocation(String secret) {
+ static SecretLocation fromUrl(SecretURL url) {
+ return new MockSecretLocation(url.path()).toLocation();
+ }
+
+ static MockSecretLocation fromLocation(SecretLocation location) {
+ return new MockSecretLocation(location.get("secret"));
+ }
+
+ SecretLocation toLocation() {
+ return new SecretLocation(Map.of("secret", secret));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
new file mode 100644
index 000000000..d5cebfe08
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
@@ -0,0 +1,44 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.api.secrets.model.SecretEvent;
+import io.gravitee.node.api.secrets.model.SecretMap;
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.util.ConfigHelper;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.Maybe;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+public class MockSecretProvider implements SecretProvider {
+
+ public static final String PLUGIN_ID = "mock";
+
+ private final MockSecretProviderConfiguration configuration;
+
+ @Override
+ public Maybe resolve(SecretMount secretMount) {
+ MockSecretLocation location = MockSecretLocation.fromLocation(secretMount.location());
+ Map secretMap = ConfigHelper.removePrefix(configuration.getSecrets(), location.secret());
+ if (secretMap.isEmpty()) {
+ return Maybe.empty();
+ }
+ return Maybe.just(SecretMap.of(secretMap));
+ }
+
+ @Override
+ public Flowable watch(SecretMount secretMount) {
+ return Flowable.empty();
+ }
+
+ @Override
+ public SecretMount fromURL(SecretURL url) {
+ return new SecretMount(PLUGIN_ID, MockSecretLocation.fromUrl(url), url.key(), url);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
new file mode 100644
index 000000000..d70ae4109
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
@@ -0,0 +1,31 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+import io.gravitee.node.api.secrets.SecretManagerConfiguration;
+import io.gravitee.node.api.secrets.util.ConfigHelper;
+import java.util.Map;
+import lombok.Getter;
+import lombok.experimental.FieldNameConstants;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Getter
+@FieldNameConstants
+public class MockSecretProviderConfiguration implements SecretManagerConfiguration {
+
+ private final boolean enabled;
+
+ @Getter
+ private final Map secrets;
+
+ public MockSecretProviderConfiguration(Map config) {
+ this.enabled = ConfigHelper.getProperty(config, Fields.enabled, Boolean.class, false);
+ this.secrets = ConfigHelper.removePrefix(config, "secrets");
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
new file mode 100644
index 000000000..3db78ed87
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
@@ -0,0 +1,16 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.api.secrets.SecretProviderFactory;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class MockSecretProviderFactory implements SecretProviderFactory {
+
+ @Override
+ public SecretProvider create(MockSecretProviderConfiguration configuration) {
+ return new MockSecretProvider(configuration);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/resources/plugin.properties b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/resources/plugin.properties
new file mode 100644
index 000000000..ed1ba0e83
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/resources/plugin.properties
@@ -0,0 +1,7 @@
+id=mock
+name=Mock Secret Provider
+version=${project.version}
+description=Mock secret provider allow to get secrets from configuration for testing purposes
+class=io.gravitee.node.secrets.plugin.mock.MockSecretProviderFactory
+type=secret-provider
+
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
new file mode 100644
index 000000000..57d465ea0
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
@@ -0,0 +1,52 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.api.secrets.model.SecretMap;
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import java.util.Map;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class MockSecretProviderTest {
+
+ private SecretProvider cut;
+
+ @BeforeEach
+ void setup() {
+ Map conf = Map.of("enabled", true, "secrets.redis.password", "r3d1s", "secrets.ldap.password", "1da9");
+ this.cut = new MockSecretProviderFactory().create(new MockSecretProviderConfiguration(conf));
+ }
+
+ @Test
+ void should_create_mount() {
+ SecretMount secretMountRedis = cut.fromURL(SecretURL.from("secret://mock/redis:password"));
+ assertThat(secretMountRedis.provider()).isEqualTo("mock");
+ assertThat((String) secretMountRedis.location().get("secret")).isEqualTo("redis");
+ assertThat(secretMountRedis.key()).isEqualTo("password");
+ }
+
+ @Test
+ void should_resolve() {
+ SecretMount secretMountRedis = cut.fromURL(SecretURL.from("secret://mock/redis:password"));
+ cut.resolve(secretMountRedis).test().assertValue(SecretMap.of(Map.of("password", "r3d1s")));
+
+ SecretMount secretMountLdap = cut.fromURL(SecretURL.from("secret://mock/ldap"));
+ cut.resolve(secretMountLdap).test().assertValue(SecretMap.of(Map.of("password", "1da9")));
+ }
+
+ @Test
+ void should_return_empty() {
+ SecretMount secretMountEmpty = cut.fromURL(SecretURL.from("secret://mock/empty:password"));
+ cut.resolve(secretMountEmpty).test().assertNoErrors().assertComplete();
+ }
+}
diff --git a/gravitee-node-secrets/pom.xml b/gravitee-node-secrets/pom.xml
index 954bf70f2..2fca06b06 100644
--- a/gravitee-node-secrets/pom.xml
+++ b/gravitee-node-secrets/pom.xml
@@ -35,6 +35,7 @@
gravitee-node-secrets-plugin-handler
gravitee-node-secrets-service
gravitee-node-secrets-runtime
+ gravitee-secret-provider-mock
From 900f908c616256b1c0455ce01227e1ecb0c54721 Mon Sep 17 00:00:00 2001
From: Benoit Bordigoni
Date: Thu, 3 Oct 2024 15:11:43 +0200
Subject: [PATCH 03/15] feat: prep for poc demo
---
.../node/api/secrets/model/SecretMount.java | 2 +-
.../node/api/secrets/model/SecretURL.java | 8 +-
.../runtime/discovery/ContextRegistry.java | 14 +
.../runtime/discovery/DefinitionBrowser.java | 4 +-
.../discovery/DefinitionPayloadNotifier.java | 4 +-
.../api/secrets/runtime/discovery/Ref.java | 15 +-
.../node/api/secrets/runtime/grant/Grant.java | 7 +
.../secrets/runtime/grant/GrantService.java | 7 +-
.../runtime/providers/ResolverService.java | 2 +-
.../providers/SecretProviderDeployer.java | 4 +-
.../node/api/secrets/runtime/spec/Spec.java | 6 +-
gravitee-node-container/pom.xml | 6 +
.../gravitee/node/container/AbstractNode.java | 2 +
.../spring/SpringBasedContainer.java | 2 +
.../gravitee-node-secrets-runtime/pom.xml | 36 +-
...a => RuntimeSecretsProcessingService.java} | 50 +-
.../runtimesecrets/RuntimeSecretsService.java | 39 ++
.../runtimesecrets/config/Config.java | 24 +-
.../discovery/ContextRegistry.java | 53 --
.../discovery/DefaultContextRegistry.java | 92 ++++
.../discovery/DefinitionBrowserRegistry.java | 15 +
.../discovery/PayloadRefParser.java | 19 +
.../runtimesecrets/discovery/RefParser.java | 23 +-
.../runtimesecrets/el/ContextUpdater.java | 19 +-
.../services/runtimesecrets/el/Formatter.java | 52 +-
.../services/runtimesecrets/el/Result.java | 15 +
.../services/runtimesecrets/el/Service.java | 51 +-
.../SecretSpelSecuredEvaluationContext.java | 39 ++
.../SecretSpelSecuredMethodResolver.java | 42 ++
.../el/engine/SecretSpelTemplateContext.java | 29 ++
.../el/engine/SecretSpelTemplateEngine.java | 30 ++
.../errors/SecretAccessDeniedException.java | 15 +
.../errors/SecretEmptyException.java | 15 +
.../errors/SecretKeyNotFoundException.java | 15 +
.../errors/SecretNotFoundException.java | 15 +
.../errors/SecretProviderException.java | 15 +
.../SecretProviderNotFoundException.java | 15 +
.../errors/SecretRefParsingException.java | 15 +
.../errors/SecretSpecNotFoundException.java | 15 +
.../grant/DefaultGrantService.java | 66 ++-
.../runtimesecrets/grant/GrantRegistry.java | 26 +-
.../providers/DefaultResolverService.java | 50 ++
.../providers/DefaultRuntimeResolver.java | 35 --
...omConfigurationSecretProviderDeployer.java | 35 --
.../providers/SecretProviderRegistry.java | 42 +-
...omConfigurationSecretProviderDeployer.java | 122 +++++
.../spec/DefaultSpecLifecycleService.java | 155 ++++--
.../runtimesecrets/spec/SpecRegistry.java | 159 ++++++
.../spec/registry/EnvAwareSpecRegistry.java | 51 --
.../spec/registry/SpecRegistry.java | 91 ----
.../runtimesecrets/spring/BeanFactory.java | 116 -----
.../spring/RuntimeSecretsBeanFactory.java | 182 +++++++
.../spring/SecretTemplateEngineFactory.java | 33 ++
.../storage/SimpleOffHeapCache.java | 15 +
.../main/resources/META-INF/spring.factories | 1 +
.../RuntimeSecretsProcessingServiceTest.java | 493 ++++++++++++++++++
.../discovery/RefDiscovererTest.java | 15 +
.../RefParserTest.java} | 20 +-
.../runtimesecrets/el/ServiceTest.java | 173 ++++++
.../grant/DefaultGrantServiceTest.java | 182 +++++--
...nfigurationSecretProviderDeployerTest.java | 106 ++++
.../spec/DefaultSpecLifecycleServiceTest.java | 135 +++++
.../storage/SimpleOffHeapCacheTest.java | 15 +
.../testsupport/PluginManagerHelper.java | 137 +++++
.../testsupport/SpecFixtures.java | 70 +++
.../src/test/resources/v4-api.json | 269 ++++++++++
.../AbstractSecretProviderDispatcher.java | 9 +-
.../gravitee-secret-provider-mock/pom.xml | 11 +
.../plugin/mock/MockSecretProvider.java | 66 ++-
.../mock/MockSecretProviderConfiguration.java | 31 --
.../mock/MockSecretProviderException.java | 12 +
.../mock/MockSecretProviderFactory.java | 1 +
.../plugin/mock/conf/ConfiguredError.java | 3 +
.../plugin/mock/conf/ConfiguredEvent.java | 6 +
.../conf/MockSecretProviderConfiguration.java | 83 +++
.../plugin/mock/MockSecretProviderTest.java | 116 ++++-
76 files changed, 3367 insertions(+), 596 deletions(-)
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/ContextRegistry.java
create mode 100644 gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/Grant.java
rename gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/{RuntimeSecretProcessingService.java => RuntimeSecretsProcessingService.java} (72%)
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefaultContextRegistry.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredEvaluationContext.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredMethodResolver.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateContext.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateEngine.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultResolverService.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
delete mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/SecretTemplateEngineFactory.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java
rename gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/{api/model/RefTest.java => discovery/RefParserTest.java} (88%)
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleServiceTest.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/PluginManagerHelper.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/SpecFixtures.java
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/v4-api.json
delete mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderException.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredError.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredEvent.java
create mode 100644 gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/MockSecretProviderConfiguration.java
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
index d1d2fd2c4..ea33014d5 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretMount.java
@@ -10,7 +10,7 @@
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
-public record SecretMount(String provider, SecretLocation location, String key, SecretURL secretURL, boolean withRetries) {
+public record SecretMount(String provider, SecretLocation location, String key, SecretURL secretURL, boolean retryOnError) {
/**
* Test the presence of a key
*
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
index 4173d7c7e..cee788735 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretURL.java
@@ -17,7 +17,7 @@
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
-public record SecretURL(String provider, String path, String key, Multimap query) {
+public record SecretURL(String provider, String path, String key, Multimap query, boolean pluginIdMatchURLProvider) {
public static final char URL_SEPARATOR = '/';
private static final Splitter urlPathSplitter = Splitter.on(URL_SEPARATOR);
private static final Splitter queryParamSplitter = Splitter.on('&');
@@ -25,6 +25,10 @@ public record SecretURL(String provider, String path, String key, Multimap
@@ -93,7 +97,7 @@ public static SecretURL from(String url, boolean includesSchema) {
throwFormatError(url);
}
- return new SecretURL(provider, path, key, query);
+ return new SecretURL(provider, path, key, query, includesSchema);
}
private static void throwFormatError(String url) {
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/ContextRegistry.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/ContextRegistry.java
new file mode 100644
index 000000000..6df3d92d1
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/ContextRegistry.java
@@ -0,0 +1,14 @@
+package io.gravitee.node.api.secrets.runtime.discovery;
+
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.List;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface ContextRegistry {
+ void register(DiscoveryContext context, Definition definition);
+ List findBySpec(Spec spec);
+ List getByDefinition(String envId, Definition definition);
+}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
index 1a7e374ef..592916375 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionBrowser.java
@@ -7,9 +7,9 @@
* @author GraviteeSource Team
*/
public interface DefinitionBrowser {
- boolean canHandle(T definition);
+ boolean canHandle(Object definition);
Definition getDefinitionKindLocation(T definition, Map metadata);
- void findPayloads(DefinitionPayloadNotifier notifier);
+ void findPayloads(T definition, DefinitionPayloadNotifier notifier);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
index 3a4ac4646..ddf3f3ea1 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/DefinitionPayloadNotifier.java
@@ -1,9 +1,11 @@
package io.gravitee.node.api.secrets.runtime.discovery;
+import java.util.function.Consumer;
+
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
public interface DefinitionPayloadNotifier {
- void onPayload(String payload, PayloadLocation location);
+ void onPayload(String payload, PayloadLocation location, Consumer updatedPayload);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
index 064913f48..dbfd9141e 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
@@ -33,8 +33,19 @@ public enum SecondaryType {
public static final String URI_KEY_SEPARATOR = ":";
- public Spec toRuntimeSpec(String envId) {
- return new Spec(null, null, mainExpression.value(), secondaryExpression().value(), null, false, true, null, null, envId);
+ public Spec asOnTheFlySpec(String envId) {
+ return new Spec(
+ null,
+ null,
+ mainExpression().value(),
+ secondaryExpression().value(),
+ null,
+ mainType() == MainType.URI && mainExpression.isLiteral() && secondaryType() == null,
+ true,
+ null,
+ null,
+ envId
+ );
}
/**
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/Grant.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/Grant.java
new file mode 100644
index 000000000..9d02b8cc8
--- /dev/null
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/Grant.java
@@ -0,0 +1,7 @@
+package io.gravitee.node.api.secrets.runtime.grant;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public record Grant(String naturalId, String key) {}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
index 6dcba6514..2c6da1d9f 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
@@ -2,17 +2,18 @@
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.Optional;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
public interface GrantService {
- boolean isGranted(String token);
+ Optional getGrant(String contextId);
- boolean authorize(DiscoveryContext context, Spec spec);
+ boolean isGranted(DiscoveryContext context, Spec spec);
- void grant(DiscoveryContext context);
+ void grant(DiscoveryContext context, Spec spec);
void revoke(DiscoveryContext context);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
index 56305d41c..888a08950 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/ResolverService.java
@@ -12,5 +12,5 @@
public interface ResolverService {
Single resolve(String envId, SecretMount secretMount);
- SecretMount toSecretMount(String envId, SecretURL secretURL);
+ Single toSecretMount(String envId, SecretURL secretURL);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
index 8da82de32..136f59c3a 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/providers/SecretProviderDeployer.java
@@ -7,5 +7,7 @@
* @author GraviteeSource Team
*/
public interface SecretProviderDeployer {
- void deploy(String id, String pluginId, Map config, String envId);
+ default void init() {}
+
+ void deploy(String pluginId, Map config, String providerId, String envId);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
index 5f84bf879..59956422e 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
@@ -1,6 +1,6 @@
package io.gravitee.node.api.secrets.runtime.spec;
-import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
+import static io.gravitee.node.api.secrets.runtime.discovery.Ref.formatUriAndKey;
import io.gravitee.node.api.secrets.model.SecretURL;
import java.util.List;
@@ -18,7 +18,7 @@ public record Spec(
String key,
List children,
boolean usesDynamicKey,
- boolean isRuntime,
+ boolean isOnTheFly,
RenewalPolicy renewalPolicy,
ACLs acls,
String envId
@@ -42,7 +42,7 @@ public Optional findChildrenFromUri(String query) {
}
public String uriAndKey() {
- return uri + URI_KEY_SEPARATOR + key;
+ return formatUriAndKey(uri, key);
}
public SecretURL toSecretURL() {
diff --git a/gravitee-node-container/pom.xml b/gravitee-node-container/pom.xml
index 5b218238f..c1635d0b9 100644
--- a/gravitee-node-container/pom.xml
+++ b/gravitee-node-container/pom.xml
@@ -169,5 +169,11 @@
mockito-core
test
+
+ io.gravitee.node
+ gravitee-node-secrets-runtime
+ 6.4.2
+ compile
+
diff --git a/gravitee-node-container/src/main/java/io/gravitee/node/container/AbstractNode.java b/gravitee-node-container/src/main/java/io/gravitee/node/container/AbstractNode.java
index abf9a47e1..efd1e0308 100644
--- a/gravitee-node-container/src/main/java/io/gravitee/node/container/AbstractNode.java
+++ b/gravitee-node-container/src/main/java/io/gravitee/node/container/AbstractNode.java
@@ -15,6 +15,7 @@
*/
package io.gravitee.node.container;
+import com.graviteesource.services.runtimesecrets.RuntimeSecretsService;
import io.gravitee.common.component.Lifecycle;
import io.gravitee.common.component.LifecycleComponent;
import io.gravitee.common.service.AbstractService;
@@ -145,6 +146,7 @@ public List> components() {
components.add(NodeHealthCheckService.class);
components.add(NodeMonitorService.class);
components.add(ReporterManager.class);
+ components.add(RuntimeSecretsService.class);
return components;
}
diff --git a/gravitee-node-container/src/main/java/io/gravitee/node/container/spring/SpringBasedContainer.java b/gravitee-node-container/src/main/java/io/gravitee/node/container/spring/SpringBasedContainer.java
index 46eba8507..9acb0969f 100644
--- a/gravitee-node-container/src/main/java/io/gravitee/node/container/spring/SpringBasedContainer.java
+++ b/gravitee-node-container/src/main/java/io/gravitee/node/container/spring/SpringBasedContainer.java
@@ -15,6 +15,7 @@
*/
package io.gravitee.node.container.spring;
+import com.graviteesource.services.runtimesecrets.spring.RuntimeSecretsBeanFactory;
import io.gravitee.common.component.LifecycleComponent;
import io.gravitee.kubernetes.client.spring.KubernetesClientConfiguration;
import io.gravitee.node.api.Node;
@@ -160,6 +161,7 @@ protected List> annotatedClasses() {
classes.add(PluginConfiguration.class);
classes.add(ManagementConfiguration.class);
classes.add(NodeMonitoringConfiguration.class);
+ classes.add(RuntimeSecretsBeanFactory.class);
// Bean registry post processor needs to be manually registered as it MUST be taken in account before spring context is refreshed.
classes.add(PluginHandlerBeanRegistryPostProcessor.class);
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
index c411edeef..a7126e39a 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
@@ -45,7 +45,7 @@
io.gravitee.el
gravitee-expression-language
- 3.1.0
+ 3.2.3
compile
@@ -54,6 +54,38 @@
awaitility
test
-
+
+ io.gravitee.node
+ gravitee-secret-provider-mock
+ 6.4.2
+
+
+ org.springframework
+ spring-test
+ test
+
+
+ org.yaml
+ snakeyaml
+ 2.2
+ test
+
+
+ org.springframework.security
+ spring-security-core
+ test
+
+
+ io.gravitee.apim.definition
+ gravitee-apim-definition-model
+ 4.5.0-SNAPSHOT
+ test
+
+
+ io.gravitee.apim.definition
+ gravitee-apim-definition-jackson
+ 4.5.0-SNAPSHOT
+ test
+
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java
similarity index 72%
rename from gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java
rename to gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java
index bc86cce51..1aa01ad1b 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretProcessingService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java
@@ -1,16 +1,31 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets;
-import com.graviteesource.services.runtimesecrets.discovery.ContextRegistry;
import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
import com.graviteesource.services.runtimesecrets.discovery.PayloadRefParser;
import com.graviteesource.services.runtimesecrets.discovery.RefParser;
import com.graviteesource.services.runtimesecrets.el.Formatter;
-import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
import io.gravitee.node.api.secrets.runtime.discovery.*;
import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
import java.util.*;
+import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.Getter;
@@ -23,25 +38,25 @@
*/
@Slf4j
@RequiredArgsConstructor
-public class RuntimeSecretProcessingService {
+public class RuntimeSecretsProcessingService {
private final DefinitionBrowserRegistry definitionBrowserRegistry;
private final ContextRegistry contextRegistry;
+ private final SpecRegistry specRegistry;
private final GrantService grantService;
private final SpecLifecycleService specLifecycleService;
- private final EnvAwareSpecRegistry specRegistry;
/**
* finds a {@link DefinitionBrowser}
* Run it to get {@link DiscoveryContext}
* Inject EL {@link PayloadRefParser}
- * Find {@link Spec}
+ * Find {@link Spec} or create on the fly
* Grant {@link DiscoveryContext}
* @param definition the secret naturalId container
* @param metadata some optional metadata
* @param the kind of subject
*/
- void processSecrets(String envId, @Nonnull T definition, @Nullable Map metadata) {
+ public void onDefinitionDeploy(String envId, @Nonnull T definition, @Nullable Map metadata) {
Optional> browser = definitionBrowserRegistry.findBrowser(definition);
if (browser.isEmpty()) {
log.info("No definition browser found for kind [{}]", definition.getClass());
@@ -50,8 +65,11 @@ void processSecrets(String envId, @Nonnull T definition, @Nullable Map definitionBrowser = browser.get();
Definition rootDefinition = definitionBrowser.getDefinitionKindLocation(definition, metadata);
- DefaultPayloadNotifier notifier = new DefaultPayloadNotifier(rootDefinition, envId);
- definitionBrowser.findPayloads(notifier);
+
+ log.info("Finding secret in definition: {}", rootDefinition);
+
+ DefaultPayloadNotifier notifier = new DefaultPayloadNotifier(rootDefinition, envId, specRegistry);
+ definitionBrowser.findPayloads(definition, notifier);
// register contexts by naturalId and definition
for (DiscoveryContext context : notifier.getContextList()) {
@@ -61,9 +79,12 @@ void processSecrets(String envId, @Nonnull T definition, @Nullable Map updatedPayload) {
PayloadRefParser payloadRefParser = new PayloadRefParser(payload);
List discoveryContexts = payloadRefParser
.runDiscovery()
@@ -112,6 +135,7 @@ public void onPayload(String payload, PayloadLocation payloadLocation) {
})
.toList();
payloadRefParser.replaceRefs(ELs);
+ updatedPayload.accept(payloadRefParser.getUpdatePayload());
}
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
new file mode 100644
index 000000000..5f831ab04
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
@@ -0,0 +1,39 @@
+package com.graviteesource.services.runtimesecrets;
+
+import io.gravitee.common.service.AbstractService;
+import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import java.util.Map;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RequiredArgsConstructor
+public class RuntimeSecretsService extends AbstractService {
+
+ private final RuntimeSecretsProcessingService runtimeSecretsProcessingService;
+ private final SpecLifecycleService specLifecycleService;
+ private final SecretProviderDeployer secretProviderDeployer;
+
+ @Override
+ protected void doStart() throws Exception {
+ secretProviderDeployer.init();
+ }
+
+ public void deploy(Spec spec) {
+ specLifecycleService.deploy(spec);
+ }
+
+ public void undeploy(Spec spec) {
+ specLifecycleService.undeploy(spec);
+ }
+
+ public void onDefinitionDeploy(String envId, @Nonnull T definition, @Nullable Map metadata) {
+ runtimeSecretsProcessingService.onDefinitionDeploy(envId, definition, metadata);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
index c0a87ac24..b7622c648 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/config/Config.java
@@ -1,10 +1,28 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.config;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
-public record Config(boolean allowOnTheFlySpecs, boolean allowEmptyACLSpecs) {
- public static final String ALLOW_EMPTY_ACL_SPECS = "api.secrets.allowEmptyNoACLsSpecs";
- public static final String ALLOW_ON_THE_FLY_SPECS = "api.secrets.allowOnTheFlySpecs";
+public record Config(boolean onTheFlySpecsEnabled, long onTheFlySpecsDelayBeforeRetryMs, boolean allowEmptyACLSpecs) {
+ public static final String CONFIG_PREFIX = "api.secrets";
+ public static final String ALLOW_EMPTY_NO_ACL_SPECS = CONFIG_PREFIX + ".allowNoACLsSpecs";
+ public static final String ON_THE_FLY_SPECS_ENABLED = CONFIG_PREFIX + ".onTheFlySpecs.enabled";
+ public static final String ON_THE_FLY_SPECS_DELAY_BEFORE_RETRY_MS = CONFIG_PREFIX + ".onTheFlySpecs.delayBeforeRetryMs";
+ public static final String API_SECRETS_ALLOW_PROVIDERS_FROM_CONFIGURATION = CONFIG_PREFIX + ".allowProvidersFromConfiguration";
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
deleted file mode 100644
index 8ec071864..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/ContextRegistry.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.graviteesource.services.runtimesecrets.discovery;
-
-import com.google.common.collect.Multimap;
-import com.google.common.collect.MultimapBuilder;
-import io.gravitee.node.api.secrets.runtime.discovery.Definition;
-import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
-import io.gravitee.node.api.secrets.runtime.discovery.Ref;
-import io.gravitee.node.api.secrets.runtime.spec.Spec;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class ContextRegistry {
-
- private final Multimap byName = MultimapBuilder.hashKeys().arrayListValues().build();
- private final Multimap byUri = MultimapBuilder.hashKeys().arrayListValues().build();
- private final Multimap byUriAndKey = MultimapBuilder.hashKeys().arrayListValues().build();
- private final Multimap byDefinitionSpec = MultimapBuilder.hashKeys().arrayListValues().build();
-
- public void register(DiscoveryContext context, Definition definition) {
- if (context.ref().mainType() == Ref.MainType.NAME && context.ref().mainExpression().isLiteral()) {
- byName.put(context.ref().mainExpression().value(), context);
- }
- if (context.ref().mainType() == Ref.MainType.URI && context.ref().mainExpression().isLiteral()) {
- byUri.put(context.ref().mainExpression().value(), context);
- if (context.ref().secondaryType() == Ref.SecondaryType.KEY && context.ref().secondaryExpression().isLiteral()) {
- byUriAndKey.put(context.ref().uriAndKey(), context);
- }
- }
- byDefinitionSpec.put(definition, context);
- }
-
- public List findBySpec(Spec spec) {
- List result = new ArrayList<>();
- if (spec.name() != null && !spec.name().isEmpty()) {
- result.addAll(byName.get(spec.name()));
- }
- if (spec.uri() != null && !spec.uri().isEmpty()) {
- result.addAll(byUri.get(spec.name()));
- }
- if (spec.key() != null && !spec.key().isEmpty()) {
- result.addAll(byUriAndKey.get(spec.uriAndKey()));
- }
- return result;
- }
-
- public List getByDefinition(Definition definition) {
- return (List) byDefinitionSpec.get(definition);
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefaultContextRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefaultContextRegistry.java
new file mode 100644
index 000000000..9e620df12
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefaultContextRegistry.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.discovery;
+
+import com.google.common.collect.Multimap;
+import com.google.common.collect.MultimapBuilder;
+import io.gravitee.node.api.secrets.runtime.discovery.ContextRegistry;
+import io.gravitee.node.api.secrets.runtime.discovery.Definition;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class DefaultContextRegistry implements ContextRegistry {
+
+ Map registry = new HashMap<>();
+
+ public void register(DiscoveryContext context, Definition definition) {
+ registry(context.envId()).register(context, definition);
+ }
+
+ public List findBySpec(Spec spec) {
+ return registry(spec.envId()).findBySpec(spec);
+ }
+
+ public List getByDefinition(String envId, Definition definition) {
+ return registry(envId).getByDefinition(null, definition);
+ }
+
+ ContextRegistry registry(String envId) {
+ return registry.computeIfAbsent(envId, ignore -> new InternalRegistry());
+ }
+
+ static class InternalRegistry implements ContextRegistry {
+
+ private final Multimap byName = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byUri = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byUriAndKey = MultimapBuilder.hashKeys().arrayListValues().build();
+ private final Multimap byDefinitionSpec = MultimapBuilder.hashKeys().arrayListValues().build();
+
+ public void register(DiscoveryContext context, Definition definition) {
+ if (context.ref().mainType() == Ref.MainType.NAME && context.ref().mainExpression().isLiteral()) {
+ byName.put(context.ref().mainExpression().value(), context);
+ }
+ if (context.ref().mainType() == Ref.MainType.URI && context.ref().mainExpression().isLiteral()) {
+ byUri.put(context.ref().mainExpression().value(), context);
+ if (context.ref().secondaryType() == Ref.SecondaryType.KEY && context.ref().secondaryExpression().isLiteral()) {
+ byUriAndKey.put(context.ref().uriAndKey(), context);
+ }
+ }
+ byDefinitionSpec.put(definition, context);
+ }
+
+ public List findBySpec(Spec spec) {
+ List result = new ArrayList<>();
+ if (spec.name() != null && !spec.name().isEmpty()) {
+ result.addAll(byName.get(spec.name()));
+ }
+ if (spec.uri() != null && !spec.uri().isEmpty()) {
+ result.addAll(byUri.get(spec.name()));
+ }
+ if (spec.key() != null && !spec.key().isEmpty()) {
+ result.addAll(byUriAndKey.get(spec.uriAndKey()));
+ }
+ return result;
+ }
+
+ public List getByDefinition(String envId, Definition definition) {
+ return (List) byDefinitionSpec.get(definition);
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
index d66827147..c7073977b 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/DefinitionBrowserRegistry.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.discovery;
import io.gravitee.node.api.secrets.runtime.discovery.DefinitionBrowser;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
index b8c6c6de3..985f1d4f4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/PayloadRefParser.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.discovery;
import java.util.ArrayList;
@@ -77,4 +92,8 @@ public String replaceRefs(List expressions) {
return payload.toString();
}
+
+ public String getUpdatePayload() {
+ return payload.toString();
+ }
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
index cc945c56d..abf53a089 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.discovery;
import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
@@ -57,7 +72,7 @@ public static Ref parse(String ref) {
} catch (IllegalArgumentException e) {
throw enumError("unknown kind: '%s' for secret reference '%s'".formatted(typeString, ref));
}
- final RefParsing refParsing = mainExpression(buffer);
+ final RefParsing refParsing = mainExpression(buffer, ref);
Ref.SecondaryType secondaryType = null;
if (refParsing.secondaryType() != null) {
@@ -107,7 +122,7 @@ private static String mainType(StringBuilder buffer) {
}
if (isEL(buffer.toString())) {
throw new SecretRefParsingException(
- "EL expression must be preceded by '%s' or '%s' when starting the secret reference".formatted(
+ "EL expression must be preceded by '%s' or '%s' when located at the beginning of secret reference".formatted(
NAME_TYPE.stripLeading(),
URI_TYPE.stripLeading()
)
@@ -122,7 +137,7 @@ private static String mainType(StringBuilder buffer) {
record RefParsing(String mainExpression, String secondaryType, String secondaryExpression) {}
- private static RefParsing mainExpression(StringBuilder buffer) {
+ private static RefParsing mainExpression(StringBuilder buffer, String ref) {
String foundToken = null;
String expression = null;
int end = 0;
@@ -137,7 +152,7 @@ private static RefParsing mainExpression(StringBuilder buffer) {
}
if (foundToken == null) {
- throw new SecretRefParsingException("reference %s syntax is incorrect looks like nothing is specified");
+ throw new SecretRefParsingException("reference %s syntax is incorrect".formatted(ref));
}
String uriOrName = expression.trim();
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
index cee1d535d..c74b612b0 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
@@ -1,6 +1,21 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.el;
-import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
import io.gravitee.el.TemplateContext;
import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
@@ -17,7 +32,7 @@ public class ContextUpdater {
private final Cache cache;
private final GrantService grantService;
private final SpecLifecycleService specLifecycleService;
- private final EnvAwareSpecRegistry specRegistry;
+ private final SpecRegistry specRegistry;
public void addRuntimeSecretsService(TemplateContext context) {
context.setVariable("secrets", new Service(cache, grantService, specLifecycleService, specRegistry));
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
index dea549c3b..a5473f64b 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Formatter.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.el;
import io.gravitee.node.api.secrets.runtime.discovery.Definition;
@@ -12,11 +27,12 @@
*/
public class Formatter {
- public static final String FROM_GRANT_TEMPLATE = "{#secrets.fromGrant('%s', '%s', '%s', %s)}";
+ public static final String FROM_GRANT_TEMPLATE = "{#secrets.fromGrant('%s', '%s')}";
+ public static final String FROM_GRANT_EL_KEY_TEMPLATE = "{#secrets.fromGrant('%s', '%s', %s)}";
public static final String METHOD_NAME_SUFFIX = "WithName";
public static final String METHOD_URI_SUFFIX = "WithUri";
public static final String FROM_GRANT_WITH_TEMPLATE = "{#secrets.fromGrant%s('%s', '%s', '%s', %s)}";
- public static final String FROM_EL_WITH_TEMPLATE = "{#secrets.fromEL%s('%s', '%s', '%s', '%s'%s)}";
+ public static final String FROM_EL_WITH_TEMPLATE = "{#secrets.fromEL%s('%s', %s, '%s', '%s'%s)}";
public static String computeELFromStatic(DiscoveryContext context, String envId) {
if (context.ref().mainExpression().isEL()) {
@@ -24,13 +40,25 @@ public static String computeELFromStatic(DiscoveryContext context, String envId)
}
final String mainSpec = context.ref().mainExpression().value();
String el;
- switch (context.ref().secondaryType()) {
- case KEY -> el = fromGrant(context.id(), envId, mainSpec, context.ref().secondaryExpression());
- case NAME -> el = fromGrantWithTemplate(METHOD_NAME_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
- case URI -> el = fromGrantWithTemplate(METHOD_URI_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
- default -> {
- throw new IllegalArgumentException("secondary type unknown: %s".formatted(context.ref().secondaryType()));
+ if (context.ref().secondaryType() != null) {
+ switch (context.ref().secondaryType()) {
+ case KEY -> {
+ if (context.ref().secondaryExpression().isLiteral()) {
+ el = fromGrant(context.id(), envId);
+ } else {
+ el = fromGrant(context.id(), envId, context.ref().secondaryExpression().value());
+ }
+ }
+ case NAME -> el =
+ fromGrantWithTemplate(METHOD_NAME_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
+ case URI -> el =
+ fromGrantWithTemplate(METHOD_URI_SUFFIX, context.id(), envId, mainSpec, context.ref().secondaryExpression());
+ default -> {
+ throw new IllegalArgumentException("secondary type unknown: %s".formatted(context.ref().secondaryType()));
+ }
}
+ } else {
+ el = fromGrant(context.id(), envId);
}
return el;
}
@@ -80,8 +108,12 @@ private static String fromGrantWithTemplate(
return FROM_GRANT_WITH_TEMPLATE.formatted(methodSuffix, id, envId, literalExpression, quoteLiteral(secondaryExpression));
}
- private static String fromGrant(UUID id, String envId, String expression, Ref.Expression keySpec) {
- return FROM_GRANT_TEMPLATE.formatted(id, envId, expression, quoteLiteral(keySpec));
+ private static String fromGrant(UUID id, String envId) {
+ return FROM_GRANT_TEMPLATE.formatted(id, envId);
+ }
+
+ private static String fromGrant(UUID id, String envId, String key) {
+ return FROM_GRANT_EL_KEY_TEMPLATE.formatted(id, envId, key);
}
private static String quoteLiteral(Ref.Expression expression) {
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
index be7c59635..89c1f0512 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Result.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.el;
/**
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
index 161427246..93d330afe 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
@@ -1,20 +1,37 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.el;
import static io.gravitee.node.api.secrets.runtime.discovery.Ref.URI_KEY_SEPARATOR;
import com.graviteesource.services.runtimesecrets.discovery.RefParser;
import com.graviteesource.services.runtimesecrets.errors.*;
-import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
import io.gravitee.node.api.secrets.model.Secret;
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryLocation;
import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.grant.Grant;
import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
import io.gravitee.node.api.secrets.runtime.storage.Cache;
import io.gravitee.node.api.secrets.runtime.storage.Entry;
import java.util.Map;
+import java.util.Optional;
import lombok.RequiredArgsConstructor;
/**
@@ -27,19 +44,35 @@ public class Service {
private final Cache cache;
private final GrantService grantService;
private final SpecLifecycleService specLifecycleService;
- private final EnvAwareSpecRegistry specRegistry;
+ private final SpecRegistry specRegistry;
- public String fromGrant(String token, String envId, String expression, String key) {
- boolean granted = grantService.isGranted(token);
- if (!granted) {
- return resultToValue(new Result(Result.Type.DENIED, "secret [%s] is denied in environment [%s]".formatted(expression, envId)));
+ public String fromGrant(String contextId, String envId) {
+ Optional grantOptional = grantService.getGrant(contextId);
+ if (grantOptional.isEmpty()) {
+ return resultToValue(new Result(Result.Type.DENIED, "secret was denied ahead of traffic"));
}
+ return getFromCache(envId, grantOptional.get(), grantOptional.get().key());
+ }
+
+ public String fromGrant(String contextId, String envId, String key) {
+ Optional grantOptional = grantService.getGrant(contextId);
+ if (grantOptional.isEmpty()) {
+ return resultToValue(new Result(Result.Type.DENIED, "secret was denied ahead of traffic"));
+ }
+ return getFromCache(envId, grantOptional.get(), key);
+ }
+
+ private String getFromCache(String envId, Grant grant, String key) {
return resultToValue(
toResult(
cache
- .get(envId, expression)
+ .get(envId, grant.naturalId())
.orElse(
- new Entry(Entry.Type.EMPTY, null, "no value in cache for [%s] in environment [%s]".formatted(expression, envId))
+ new Entry(
+ Entry.Type.EMPTY,
+ null,
+ "no value in cache for [%s] in environment [%s]".formatted(grant.naturalId(), envId)
+ )
),
key
)
@@ -75,7 +108,7 @@ public String fromELWithName(String envId, String name, String definitionKind, S
}
private String grantAndGet(String envId, String definitionKind, String definitionId, Spec spec, Ref ref, String naturalId, String key) {
- boolean granted = grantService.authorize(
+ boolean granted = grantService.isGranted(
new DiscoveryContext(null, envId, ref, new DiscoveryLocation(new DiscoveryLocation.Definition(definitionKind, definitionId))),
spec
);
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredEvaluationContext.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredEvaluationContext.java
new file mode 100644
index 000000000..77b76b4d5
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredEvaluationContext.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.el.engine;
+
+import io.gravitee.el.spel.context.SecuredEvaluationContext;
+import java.util.Collections;
+import java.util.List;
+import org.springframework.expression.MethodResolver;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretSpelSecuredEvaluationContext extends SecuredEvaluationContext {
+
+ private static final List methodResolvers = Collections.singletonList(new SecretSpelSecuredMethodResolver());
+
+ public SecretSpelSecuredEvaluationContext() {
+ super();
+ }
+
+ @Override
+ public List getMethodResolvers() {
+ return methodResolvers;
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredMethodResolver.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredMethodResolver.java
new file mode 100644
index 000000000..91aacfc05
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelSecuredMethodResolver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.el.engine;
+
+import com.graviteesource.services.runtimesecrets.el.Service;
+import io.gravitee.el.spel.context.SecuredMethodResolver;
+import io.reactivex.rxjava3.annotations.NonNull;
+import java.lang.reflect.Method;
+import javax.annotation.Nonnull;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretSpelSecuredMethodResolver extends SecuredMethodResolver {
+
+ public SecretSpelSecuredMethodResolver() {
+ super();
+ }
+
+ @Nonnull
+ @Override
+ public @NonNull Method[] getMethods(@NonNull Class> type) {
+ if (type.equals(Service.class)) {
+ return Service.class.getMethods();
+ }
+ return super.getMethods(type);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateContext.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateContext.java
new file mode 100644
index 000000000..fe355b71f
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateContext.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.el.engine;
+
+import io.gravitee.el.spel.context.SpelTemplateContext;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretSpelTemplateContext extends SpelTemplateContext {
+
+ public SecretSpelTemplateContext() {
+ super(new SecretSpelSecuredEvaluationContext());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateEngine.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateEngine.java
new file mode 100644
index 000000000..61316d9a4
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretSpelTemplateEngine.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.el.engine;
+
+import io.gravitee.el.spel.SpelExpressionParser;
+import io.gravitee.el.spel.SpelTemplateEngine;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretSpelTemplateEngine extends SpelTemplateEngine {
+
+ public SecretSpelTemplateEngine(SpelExpressionParser spelExpressionParser) {
+ super(spelExpressionParser, new SecretSpelTemplateContext());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
index 1141a5dad..9521f2f68 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretAccessDeniedException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
index a33bb6d63..25a275606 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretEmptyException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
index f2fc6f4e8..b7316ff95 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretKeyNotFoundException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
index 27cd15442..b85486dc9 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretNotFoundException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
index ad9af9a76..dd9b5ade4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
index 2f060cd7a..db0d99d6c 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretProviderNotFoundException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
index 12d0aacde..d4de949c2 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretRefParsingException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
index 7370d3886..ad51b4fb5 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/errors/SecretSpecNotFoundException.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.errors;
import io.gravitee.node.api.secrets.runtime.RuntimeSecretException;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
index 84c8833c0..96c41703a 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
@@ -1,18 +1,36 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.grant;
-import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_EMPTY_ACL_SPECS;
-import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_ON_THE_FLY_SPECS;
+import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_EMPTY_NO_ACL_SPECS;
+import static com.graviteesource.services.runtimesecrets.config.Config.ON_THE_FLY_SPECS_ENABLED;
import static io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation.PLUGIN_KIND;
import com.graviteesource.services.runtimesecrets.config.Config;
import com.graviteesource.services.runtimesecrets.errors.SecretSpecNotFoundException;
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
import io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.grant.Grant;
import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.spec.ACLs;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
import java.util.Arrays;
import java.util.Objects;
+import java.util.Optional;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
@@ -30,14 +48,14 @@ public class DefaultGrantService implements GrantService {
private final Config config;
@Override
- public boolean authorize(@Nonnull DiscoveryContext context, Spec spec) {
+ public boolean isGranted(@Nonnull DiscoveryContext context, Spec spec) {
if (spec == null) {
throw new SecretSpecNotFoundException(
"no spec found or created on-the-fly for ref [%s] in envId [%s], %s=%s".formatted(
context.ref().rawRef(),
context.envId(),
- ALLOW_ON_THE_FLY_SPECS,
- config.allowOnTheFlySpecs()
+ ON_THE_FLY_SPECS_ENABLED,
+ config.onTheFlySpecsEnabled()
)
);
}
@@ -46,24 +64,25 @@ public boolean authorize(@Nonnull DiscoveryContext context, Spec spec) {
log.warn(
"secret spec for ref [{}] is not granted because is does not contains ACLs and this is not allowed. see: {}",
context.ref().rawRef(),
- ALLOW_EMPTY_ACL_SPECS
+ ALLOW_EMPTY_NO_ACL_SPECS
);
return false;
} else {
- return Objects.equals(context.envId(), spec.envId());
+ return checkSpec(context).test(spec);
}
}
- return checkACLs(spec, context);
+ return checkSpec(context).test(spec) && checkACLs(context).test(spec.acls());
}
- public boolean isGranted(@Nonnull String token) {
- return grantRegistry.exists(token);
+ @Override
+ public void grant(@Nonnull DiscoveryContext context, Spec spec) {
+ grantRegistry.register(context.id().toString(), new Grant(spec.naturalId(), spec.key()));
}
@Override
- public void grant(@Nonnull DiscoveryContext context) {
- grantRegistry.register(context);
+ public Optional getGrant(String contextId) {
+ return Optional.ofNullable(grantRegistry.get(contextId));
}
@Override
@@ -71,13 +90,26 @@ public void revoke(@Nonnull DiscoveryContext context) {
grantRegistry.unregister(context);
}
- private boolean checkACLs(Spec spec, DiscoveryContext context) {
+ private Predicate checkSpec(DiscoveryContext context) {
+ Predicate envMatch = spec -> Objects.equals(context.envId(), spec.envId());
+
+ Predicate dynOrNoKey = spec -> spec.usesDynamicKey() || context.ref().secondaryType() == null;
+
+ Predicate keyMatch = spec ->
+ context.ref().secondaryType() == Ref.SecondaryType.KEY &&
+ context.ref().secondaryExpression().isLiteral() &&
+ spec.key().equals(context.ref().secondaryExpression().value());
+
+ return envMatch.and(keyMatch.or(dynOrNoKey));
+ }
+
+ private Predicate checkACLs(DiscoveryContext context) {
Predicate noDefKind = acls ->
acls.definitions() == null ||
acls.definitions().isEmpty() ||
acls.definitions().stream().allMatch(def -> def.kind() == null || def.kind().isEmpty());
- Predicate defKind = acls ->
+ Predicate defKindMatch = acls ->
acls.definitions().stream().anyMatch(defACLs -> defACLs.kind().contains(context.location().definition().kind()));
Predicate noDefId = acls ->
@@ -85,12 +117,12 @@ private boolean checkACLs(Spec spec, DiscoveryContext context) {
acls.definitions().isEmpty() ||
acls.definitions().stream().allMatch(def -> def.ids() == null || def.ids().isEmpty());
- Predicate defId = acls ->
+ Predicate defIdMatch = acls ->
acls.definitions().stream().anyMatch(defACLs -> defACLs.ids().contains(context.location().definition().id()));
Predicate noPlugin = acls -> acls.plugins() == null || acls.plugins().isEmpty();
- Predicate plugin = acls ->
+ Predicate pluginMatch = acls ->
acls
.plugins()
.stream()
@@ -106,6 +138,6 @@ private boolean checkACLs(Spec spec, DiscoveryContext context) {
)
);
- return noDefKind.or(defKind).and(noDefId.or(defId)).and(noPlugin.or(plugin)).test(spec.acls());
+ return noDefKind.or(defKindMatch).and(noDefId.or(defIdMatch)).and(noPlugin.or(pluginMatch));
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
index fcab420a7..b16018241 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/GrantRegistry.java
@@ -1,6 +1,22 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.grant;
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.grant.Grant;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
@@ -12,10 +28,10 @@
*/
public class GrantRegistry {
- private final Map grants = new ConcurrentHashMap<>();
+ private final Map grants = new ConcurrentHashMap<>();
- public void register(DiscoveryContext context) {
- grants.put(context.id().toString(), null);
+ public void register(String id, Grant grant) {
+ grants.put(id, grant);
}
public void unregister(DiscoveryContext... contexts) {
@@ -24,7 +40,7 @@ public void unregister(DiscoveryContext... contexts) {
}
}
- public boolean exists(String token) {
- return grants.containsKey(token);
+ public Grant get(String token) {
+ return grants.get(token);
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultResolverService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultResolverService.java
new file mode 100644
index 000000000..fc8b407f8
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultResolverService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.providers;
+
+import io.gravitee.node.api.secrets.model.SecretMount;
+import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.reactivex.rxjava3.core.Single;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class DefaultResolverService implements ResolverService {
+
+ private final SecretProviderRegistry secretProviderRegistry;
+
+ public DefaultResolverService(SecretProviderRegistry secretProviderRegistry) {
+ this.secretProviderRegistry = secretProviderRegistry;
+ }
+
+ @Override
+ public Single resolve(String envId, SecretMount secretMount) {
+ return secretProviderRegistry
+ .get(envId, secretMount.provider())
+ .flatMapMaybe(secretProvider -> secretProvider.resolve(secretMount))
+ .map(secretMap -> new Entry(Entry.Type.VALUE, secretMap.asMap(), null))
+ .defaultIfEmpty(new Entry(Entry.Type.NOT_FOUND, null, null))
+ .onErrorResumeNext(t -> Single.just(new Entry(Entry.Type.ERROR, null, t.getMessage())));
+ }
+
+ @Override
+ public Single toSecretMount(String envId, SecretURL secretURL) {
+ return secretProviderRegistry.get(envId, secretURL.provider()).map(provider -> provider.fromURL(secretURL));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
deleted file mode 100644
index db3ba255a..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/DefaultRuntimeResolver.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.graviteesource.services.runtimesecrets.providers;
-
-import io.gravitee.node.api.secrets.SecretProvider;
-import io.gravitee.node.api.secrets.model.SecretMount;
-import io.gravitee.node.api.secrets.model.SecretURL;
-import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
-import io.gravitee.node.api.secrets.runtime.storage.Entry;
-import io.reactivex.rxjava3.core.Single;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class DefaultRuntimeResolver implements ResolverService {
-
- private final SecretProviderRegistry secretProviderRegistry;
-
- public DefaultRuntimeResolver(SecretProviderRegistry secretProviderRegistry) {
- this.secretProviderRegistry = secretProviderRegistry;
- }
-
- @Override
- public Single resolve(String envId, SecretMount secretMount) {
- SecretProvider secretProvider = secretProviderRegistry.get(envId, secretMount.provider());
- return secretProvider
- .resolve(secretMount)
- .map(secretMap -> new Entry(Entry.Type.VALUE, secretMap.asMap(), null))
- .defaultIfEmpty(new Entry(Entry.Type.NOT_FOUND, null, null));
- }
-
- @Override
- public SecretMount toSecretMount(String envId, SecretURL secretURL) {
- return secretProviderRegistry.get(envId, secretURL.provider()).fromURL(secretURL);
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
deleted file mode 100644
index b08d34155..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployer.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.graviteesource.services.runtimesecrets.providers;
-
-import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
-import java.util.Map;
-import org.springframework.core.env.Environment;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class FromConfigurationSecretProviderDeployer implements SecretProviderDeployer {
-
- private boolean init;
- private final Environment environment;
-
- public FromConfigurationSecretProviderDeployer(Environment environment) {
- this.environment = environment;
- }
-
- public void init() {
- if (!init) {
- doInit();
- init = true;
- }
- }
-
- private void doInit() {
- // TODO
- }
-
- @Override
- public void deploy(String id, String pluginId, Map config, String envId) {
- // TODO
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
index a309aa278..95c0923c9 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
@@ -1,9 +1,26 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.providers;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.graviteesource.services.runtimesecrets.errors.SecretProviderNotFoundException;
import io.gravitee.node.api.secrets.SecretProvider;
+import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Single;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
@@ -32,16 +49,21 @@ public void register(String id, SecretProvider provider, String envId) {
* @return a secret provider
* @throws SecretProviderNotFoundException if the provider is not found
*/
- public SecretProvider get(String envId, String id) {
- return perEnv
- .get(envId)
- .stream()
- .filter(entry -> entry.id().equals(id))
- .map(SecretProviderEntry::provider)
- .findFirst()
- .or(() -> Optional.ofNullable(allEnvs.get(id)))
- .orElseThrow(() ->
- new SecretProviderNotFoundException("Cannot find secret provider with id [%s] for environmentID [%s]".formatted(id, envId))
+ public Single get(String envId, String id) {
+ return Maybe
+ .fromOptional(
+ perEnv
+ .get(envId)
+ .stream()
+ .filter(entry -> entry.id().equals(id))
+ .map(SecretProviderEntry::provider)
+ .findFirst()
+ .or(() -> Optional.ofNullable(allEnvs.get(id)))
+ )
+ .switchIfEmpty(
+ Single.error(
+ new SecretProviderNotFoundException("Cannot find secret provider with id [%s] for envId [%s]".formatted(id, envId))
+ )
);
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
new file mode 100644
index 000000000..39dba85ad
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.providers.config;
+
+import static com.graviteesource.services.runtimesecrets.config.Config.CONFIG_PREFIX;
+
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import io.gravitee.common.util.EnvironmentUtils;
+import io.gravitee.node.api.secrets.SecretManagerConfiguration;
+import io.gravitee.node.api.secrets.SecretProviderFactory;
+import io.gravitee.node.api.secrets.errors.SecretManagerConfigurationException;
+import io.gravitee.node.api.secrets.errors.SecretProviderNotFoundException;
+import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import io.gravitee.node.api.secrets.util.ConfigHelper;
+import io.gravitee.node.secrets.plugins.SecretProviderPlugin;
+import io.gravitee.node.secrets.plugins.SecretProviderPluginManager;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+
+@RequiredArgsConstructor
+@Slf4j
+public class FromConfigurationSecretProviderDeployer implements SecretProviderDeployer {
+
+ private final ConfigurableEnvironment environment;
+ private final SecretProviderRegistry registry;
+ private final SecretProviderPluginManager secretProviderPluginManager;
+
+ public void init() {
+ log.info("loading runtime secret providers from configuration");
+ Map allProperties = EnvironmentUtils.getAllProperties(environment);
+ Map apiSecrets = ConfigHelper.removePrefix(allProperties, CONFIG_PREFIX);
+ int i = 0;
+ String provider = provider(i);
+ while (apiSecrets.containsKey(provider + ".plugin")) {
+ Map providerConfig = ConfigHelper.removePrefix(apiSecrets, provider);
+ if (!ConfigHelper.getProperty(providerConfig, "enabled", Boolean.class, true)) {
+ return;
+ }
+ String plugin = ConfigHelper.getProperty(providerConfig, "plugin", String.class);
+ String id = ConfigHelper.getProperty(providerConfig, "id", String.class, plugin);
+ int e = 0;
+ String environment = environment(e);
+ while (providerConfig.containsKey(environment)) {
+ String envId = providerConfig.get(environment).toString();
+ deploy(plugin, ConfigHelper.removePrefix(providerConfig, provider + ".configuration"), id, envId);
+ environment = environment(++e);
+ }
+ // no env
+ if (e == 0) {
+ deploy(plugin, ConfigHelper.removePrefix(providerConfig, provider + ".configuration"), id, null);
+ }
+ provider = provider(++i);
+ }
+ }
+
+ @Override
+ public void deploy(String pluginId, Map configurationProperties, String providerId, String envId) {
+ try {
+ log.info("Deploying secret provider [{}] of type [{}] for environment [{}]...", providerId, pluginId, formatEnv(envId));
+ final SecretProviderPlugin, ?> secretProviderPlugin = secretProviderPluginManager.get(pluginId);
+ final Class extends SecretManagerConfiguration> configurationClass = secretProviderPlugin.configuration();
+ final SecretProviderFactory factory = secretProviderPluginManager.getFactoryById(pluginId);
+ if (configurationClass != null && factory != null) {
+ // read the config using the plugin class loader
+ SecretManagerConfiguration config;
+ Class> configurationClass1 = factory.getClass().getClassLoader().loadClass(configurationClass.getName());
+ try {
+ @SuppressWarnings("unchecked")
+ Constructor constructor =
+ (Constructor) configurationClass1.getDeclaredConstructor(Map.class);
+ config = constructor.newInstance(configurationProperties);
+ } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ throw new SecretManagerConfigurationException(
+ "Could not create configuration class for secret manager: %s".formatted(providerId),
+ e
+ );
+ }
+ log.info("Secret provider [{}] of type [{}] for environment [{}]: DEPLOYED", providerId, pluginId, formatEnv(envId));
+ this.registry.register(providerId, factory.create(config).start(), envId);
+ } else {
+ log.info("Secret provider [{}] of type [{}] for environment [{}]: FAILED", providerId, pluginId, formatEnv(envId));
+ throw new SecretProviderNotFoundException("Cannot find secret provider [%s] plugin".formatted(pluginId));
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("cannot load plugin %s properly: ".formatted(pluginId));
+ }
+ }
+
+ private static String formatEnv(String envId) {
+ return envId == null ? "*" : envId;
+ }
+
+ private static String provider(int i) {
+ return "providers[%d]".formatted(i);
+ }
+
+ private static String environment(int e) {
+ return "environments[%d]".formatted(e);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
index 3ea641241..920ee1df8 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
@@ -1,55 +1,83 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.spec;
import com.graviteesource.services.runtimesecrets.config.Config;
-import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
import io.gravitee.node.api.secrets.model.SecretMount;
import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.runtime.discovery.ContextRegistry;
import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
import io.gravitee.node.api.secrets.runtime.storage.Cache;
import io.gravitee.node.api.secrets.runtime.storage.Entry;
-import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.core.SingleObserver;
import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.schedulers.Schedulers;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
+@Slf4j
@RequiredArgsConstructor
public class DefaultSpecLifecycleService implements SpecLifecycleService {
- private final EnvAwareSpecRegistry specRegistry;
+ private final SpecRegistry specRegistry;
+ private final ContextRegistry contextRegistry;
private final Cache cache;
private final ResolverService resolverService;
+ private final GrantService grantService;
private final Config config;
@Override
public boolean shouldDeployOnTheFly(Ref ref) {
- return (ref.mainType() == Ref.MainType.URI && ref.mainExpression().isLiteral() && config.allowOnTheFlySpecs());
+ return (ref.mainType() == Ref.MainType.URI && ref.mainExpression().isLiteral() && config.onTheFlySpecsEnabled());
}
@Override
public Spec deployOnTheFly(String envId, Ref ref) {
- Spec runtimeSpec = ref.toRuntimeSpec(envId);
+ Spec runtimeSpec = ref.asOnTheFlySpec(envId);
cache.computeIfAbsent(
envId,
runtimeSpec.naturalId(),
() -> {
- specRegistry.register(envId, runtimeSpec);
+ specRegistry.register(runtimeSpec);
SecretURL secretURL = runtimeSpec.toSecretURL();
- SecretMount mount = resolverService.toSecretMount(envId, secretURL).withoutRetries();
return resolverService
- .resolve(envId, mount)
- .subscribeOn(Schedulers.io())
- .onErrorResumeNext(t -> {
- Entry entry = new Entry(Entry.Type.ERROR, null, t.getMessage());
- asyncResolution(runtimeSpec);
- return Single.just(entry);
- })
+ .toSecretMount(envId, secretURL)
+ .map(SecretMount::withoutRetries)
+ .flatMap(mount ->
+ resolverService
+ .resolve(envId, mount)
+ .doOnSuccess(entry -> {
+ if (entry.type() == Entry.Type.ERROR) {
+ asyncResolution(runtimeSpec, config.onTheFlySpecsDelayBeforeRetryMs(), () -> {});
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ )
.blockingGet();
}
);
@@ -57,28 +85,95 @@ public Spec deployOnTheFly(String envId, Ref ref) {
return runtimeSpec;
}
- private Disposable asyncResolution(Spec spec) {
- SecretURL secretURL = spec.toSecretURL();
- String envId = spec.envId();
- SecretMount mount = resolverService.toSecretMount(envId, secretURL);
- return resolverService
- .resolve(envId, mount)
- .subscribeOn(Schedulers.io())
- .onErrorResumeNext(t -> Single.just(new Entry(Entry.Type.ERROR, null, t.getMessage())))
- .subscribe(entry -> cache.put(envId, spec.naturalId(), entry));
+ @Override
+ public void deploy(Spec newSpec) {
+ Spec currentSpec = specRegistry.fromSpec(newSpec.envId(), newSpec);
+ log.info("Deploying Secret Spec: {}", newSpec);
+ Action afterResolve = () -> {
+ specRegistry.register(newSpec);
+ };
+ boolean shouldResolve = true;
+ if (currentSpec != null) {
+ if (isNameOrLocationChanged(currentSpec, newSpec)) {
+ afterResolve =
+ () -> {
+ renewGrant(currentSpec, newSpec);
+ specRegistry.replace(currentSpec, newSpec);
+ if (!currentSpec.naturalId().equals(newSpec.naturalId())) {
+ cache.evict(newSpec.envId(), currentSpec.naturalId());
+ }
+ };
+ } else if (isACLsChange(newSpec, currentSpec)) {
+ renewGrant(currentSpec, newSpec);
+ shouldResolve = false;
+ }
+ }
+
+ if (shouldResolve) {
+ asyncResolution(newSpec, 0, afterResolve);
+ }
}
- @Override
- public void deploy(Spec spec) {
- specRegistry.register(spec.envId(), spec);
- // TODO check diff
- // TODO if change clean by old name or uri
- Disposable disposable = asyncResolution(spec/*, cleanupLambda*/);
+ private void renewGrant(Spec oldSpec, Spec newSpec) {
+ contextRegistry
+ .findBySpec(oldSpec)
+ .forEach(context -> {
+ if (grantService.isGranted(context, newSpec)) {
+ grantService.grant(context, newSpec);
+ } else {
+ grantService.revoke(context);
+ }
+ });
+ specRegistry.replace(oldSpec, newSpec);
+ }
+
+ private static boolean isACLsChange(Spec spec, Spec previousSpec) {
+ return !Objects.equals(previousSpec.acls(), spec.acls());
+ }
+
+ private boolean isNameOrLocationChanged(Spec oldSpec, Spec newSpec) {
+ record LiteSpec(String name, String uriAndKey) {}
+ return !Objects.equals(new LiteSpec(oldSpec.name(), oldSpec.uriAndKey()), new LiteSpec(newSpec.name(), newSpec.uriAndKey()));
}
@Override
public void undeploy(Spec spec) {
- specRegistry.unregister(spec.envId(), spec);
+ contextRegistry.findBySpec(spec).forEach(grantService::revoke);
cache.evict(spec.envId(), spec.naturalId());
+ specRegistry.unregister(spec);
+ }
+
+ private void asyncResolution(Spec spec, long delayMs, @NonNull Action postResolution) {
+ SecretURL secretURL = spec.toSecretURL();
+ String envId = spec.envId();
+ resolverService
+ .toSecretMount(envId, secretURL)
+ .delay(delayMs, TimeUnit.MILLISECONDS)
+ .doOnSuccess(mount -> {
+ log.info("Resolving secret: {}", mount);
+ })
+ .flatMap(mount -> resolverService.resolve(envId, mount).subscribeOn(Schedulers.io()))
+ .doOnTerminate(postResolution)
+ .subscribe(
+ new SimpleSingleObserver<>() {
+ @Override
+ public void onSuccess(@NonNull Entry entry) {
+ cache.put(spec.envId(), spec.naturalId(), entry);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable err) {
+ log.error("Async resolution failed", err);
+ }
+ }
+ );
+ }
+
+ private abstract static class SimpleSingleObserver implements SingleObserver {
+
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ // no op
+ }
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
new file mode 100644
index 000000000..fd32e0039
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.spec;
+
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SpecRegistry {
+
+ private final Map registries = new HashMap<>();
+
+ public void register(Spec spec) {
+ registry(spec.envId()).register(spec);
+ }
+
+ public void unregister(Spec spec) {
+ registry(spec.envId()).unregister(spec);
+ }
+
+ public void replace(Spec oldSpec, Spec newSpec) {
+ String envId = oldSpec.envId();
+ synchronized (registry(envId)) {
+ registry(envId).unregister(oldSpec);
+ registry(envId).register(newSpec);
+ }
+ }
+
+ public Spec getFromName(String envId, String name) {
+ return registry(envId).getFromName(name);
+ }
+
+ public Spec getFromUri(String envId, String uri) {
+ return registry(envId).getFromUri(uri);
+ }
+
+ public Spec getFromUriAndKey(String envId, String uriAndKey) {
+ return registry(envId).getFromUriAndKey(uriAndKey);
+ }
+
+ public Spec getFromID(String envId, String id) {
+ return registry(envId).getFromID(id);
+ }
+
+ public Spec fromSpec(String envId, Spec query) {
+ return registry(envId).fromSpec(query);
+ }
+
+ public Spec fromRef(String envId, Ref query) {
+ return registry(envId).fromRef(query);
+ }
+
+ private Registry registry(String envId) {
+ return registries.computeIfAbsent(envId, ignore -> new Registry());
+ }
+
+ private static class Registry {
+
+ private final Map byName = new HashMap<>();
+ private final Map byUri = new HashMap<>();
+ private final Map byUriAndKey = new HashMap<>();
+ private final Map byID = new HashMap<>();
+
+ void register(Spec spec) {
+ if (spec.id() != null) {
+ byID.put(spec.id(), spec);
+ }
+ if (spec.name() != null) {
+ byName.put(spec.name(), spec);
+ }
+ if (spec.uri() != null) {
+ byUri.put(spec.uri(), spec);
+ if (spec.key() != null) {
+ byUriAndKey.put(spec.uriAndKey(), spec);
+ }
+ }
+ }
+
+ void unregister(Spec spec) {
+ if (spec.id() != null) {
+ byID.remove(spec.id());
+ }
+ if (spec.uri() != null) {
+ byUri.remove(spec.uri());
+ if (spec.key() != null) {
+ byUriAndKey.remove(spec.uriAndKey(), spec);
+ }
+ }
+ if (spec.name() != null) {
+ byName.remove(spec.name());
+ }
+ }
+
+ Spec getFromName(String name) {
+ return byName.get(name);
+ }
+
+ Spec getFromUri(String uri) {
+ return byUri.get(uri);
+ }
+
+ Spec getFromUriAndKey(String uriAndKey) {
+ return byUriAndKey.get(uriAndKey);
+ }
+
+ Spec getFromID(String id) {
+ return byID.get(id);
+ }
+
+ Spec fromRef(Ref query) {
+ if (query.mainType() == Ref.MainType.NAME) {
+ return byName.get(query.mainExpression().value());
+ }
+ if (query.mainType() == Ref.MainType.URI) {
+ if (query.secondaryType() == Ref.SecondaryType.KEY) {
+ return byUriAndKey.get(query.uriAndKey());
+ }
+ return byUri.get(query.mainExpression().value());
+ }
+ return null;
+ }
+
+ Spec fromSpec(Spec query) {
+ Spec result = null;
+ if (query.id() != null) {
+ result = byID.get(query.id());
+ }
+ if (result == null && query.name() != null) {
+ result = byName.get(query.name());
+ }
+ if (result == null && query.uri() != null) {
+ if (query.key() != null) {
+ result = byUriAndKey.get(query.uriAndKey());
+ } else {
+ result = byUri.get(query.uri());
+ }
+ }
+ return result;
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
deleted file mode 100644
index d56004af9..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/EnvAwareSpecRegistry.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.graviteesource.services.runtimesecrets.spec.registry;
-
-import io.gravitee.node.api.secrets.runtime.discovery.Ref;
-import io.gravitee.node.api.secrets.runtime.spec.Spec;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-public class EnvAwareSpecRegistry {
-
- private final Map registries = new HashMap<>();
-
- public void register(String envId, Spec spec) {
- registry(envId).register(spec);
- }
-
- public void unregister(String envId, Spec spec) {
- registry(envId).unregister(spec);
- }
-
- public Spec getFromName(String envId, String name) {
- return registry(envId).getFromName(name);
- }
-
- public Spec getFromUri(String envId, String uri) {
- return registry(envId).getFromUri(uri);
- }
-
- public Spec getFromUriAndKey(String envId, String uriAndKey) {
- return registry(envId).getFromUriAndKey(uriAndKey);
- }
-
- public Spec getFromID(String envId, String id) {
- return registry(envId).getFromID(id);
- }
-
- public Spec fromSpec(String envId, Spec query) {
- return registry(envId).fromSpec(query);
- }
-
- public Spec fromRef(String envId, Ref query) {
- return registry(envId).fromRef(query);
- }
-
- private SpecRegistry registry(String envId) {
- return registries.computeIfAbsent(envId, ignore -> new SpecRegistry());
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
deleted file mode 100644
index 0eb4bfe87..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/registry/SpecRegistry.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package com.graviteesource.services.runtimesecrets.spec.registry;
-
-import io.gravitee.node.api.secrets.runtime.discovery.Ref;
-import io.gravitee.node.api.secrets.runtime.spec.Spec;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-class SpecRegistry {
-
- private final Map byName = new HashMap<>();
- private final Map byUri = new HashMap<>();
- private final Map byUriAndKey = new HashMap<>();
- private final Map byID = new HashMap<>();
-
- void register(Spec spec) {
- if (spec.id() != null) {
- byID.put(spec.id(), spec);
- }
- if (spec.name() != null) {
- byName.put(spec.name(), spec);
- }
- if (spec.uri() != null) {
- byUri.put(spec.uri(), spec);
- if (spec.key() != null) {
- byUriAndKey.put(spec.uriAndKey(), spec);
- }
- }
- }
-
- void unregister(Spec spec) {
- if (spec.id() != null) {
- byID.remove(spec.id());
- }
- if (spec.uri() != null) {
- byUri.remove(spec.uri());
- if (spec.key() != null) {
- byUriAndKey.remove(spec.uriAndKey(), spec);
- }
- }
- if (spec.name() != null) {
- byName.remove(spec.name());
- }
- }
-
- Spec getFromName(String name) {
- return byName.get(name);
- }
-
- Spec getFromUri(String uri) {
- return byUri.get(uri);
- }
-
- Spec getFromUriAndKey(String uriAndKey) {
- return byUriAndKey.get(uriAndKey);
- }
-
- Spec getFromID(String id) {
- return byID.get(id);
- }
-
- Spec fromRef(Ref query) {
- if (query.mainType() == Ref.MainType.NAME) {
- return byName.get(query.mainExpression().value());
- }
- if (query.mainType() == Ref.MainType.URI) {
- if (query.secondaryType() == Ref.SecondaryType.KEY) {
- return byUriAndKey.get(query.mainExpression().value());
- }
- return byUri.get(query.mainExpression().value());
- }
- return null;
- }
-
- Spec fromSpec(Spec query) {
- if (query.id() != null) {
- return byID.get(query.id());
- } else if (query.name() != null) {
- return byName.get(query.name());
- } else if (query.uri() != null) {
- if (query.key() != null) {
- return byUriAndKey.get(query.uriAndKey());
- }
- return byUri.get(query.uri());
- }
- return null;
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
deleted file mode 100644
index 63ed829ee..000000000
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/BeanFactory.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package com.graviteesource.services.runtimesecrets.spring;
-
-import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_EMPTY_ACL_SPECS;
-import static com.graviteesource.services.runtimesecrets.config.Config.ALLOW_ON_THE_FLY_SPECS;
-
-import com.graviteesource.services.runtimesecrets.RuntimeSecretProcessingService;
-import com.graviteesource.services.runtimesecrets.config.Config;
-import com.graviteesource.services.runtimesecrets.discovery.ContextRegistry;
-import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
-import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
-import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
-import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
-import com.graviteesource.services.runtimesecrets.providers.DefaultRuntimeResolver;
-import com.graviteesource.services.runtimesecrets.providers.FromConfigurationSecretProviderDeployer;
-import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
-import com.graviteesource.services.runtimesecrets.spec.DefaultSpecLifecycleService;
-import com.graviteesource.services.runtimesecrets.spec.registry.EnvAwareSpecRegistry;
-import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
-import io.gravitee.node.api.secrets.runtime.discovery.DefinitionBrowser;
-import io.gravitee.node.api.secrets.runtime.grant.GrantService;
-import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
-import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
-import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
-import io.gravitee.node.api.secrets.runtime.storage.Cache;
-import java.util.List;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.*;
-import org.springframework.core.env.Environment;
-import org.springframework.core.type.AnnotatedTypeMetadata;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-@Configuration
-public class BeanFactory {
-
- @Bean
- Config config(
- @Value("${" + ALLOW_ON_THE_FLY_SPECS + ":true}") boolean allowRuntimeSpecs,
- @Value("${" + ALLOW_EMPTY_ACL_SPECS + ":true}") boolean allowEmptyACLSpecs
- ) {
- return new Config(allowRuntimeSpecs, allowEmptyACLSpecs);
- }
-
- @Bean
- RuntimeSecretProcessingService runtimeSecretProcessingService(
- DefinitionBrowserRegistry definitionBrowserRegistry,
- SpecLifecycleService specLifecycleService,
- GrantService grantService,
- EnvAwareSpecRegistry specRegistry
- ) {
- return new RuntimeSecretProcessingService(
- definitionBrowserRegistry,
- new ContextRegistry(),
- grantService,
- specLifecycleService,
- specRegistry
- );
- }
-
- @Bean
- DefinitionBrowserRegistry definitionBrowserRegistry(List browsers) {
- return new DefinitionBrowserRegistry(browsers);
- }
-
- @Bean
- SpecLifecycleService secretSpecService(Cache cache, ResolverService resolverService, Config config) {
- return new DefaultSpecLifecycleService(new EnvAwareSpecRegistry(), cache, resolverService, config);
- }
-
- @Bean
- Cache secretCache() {
- return new SimpleOffHeapCache();
- }
-
- @Bean
- GrantService grantService(Config config) {
- return new DefaultGrantService(new GrantRegistry(), config);
- }
-
- @Bean
- EnvAwareSpecRegistry envAwareSpecRegistry() {
- return new EnvAwareSpecRegistry();
- }
-
- @Bean
- @Conditional(EnvironmentCondition.class)
- SecretProviderDeployer runtimeSecretProviderDeployer(Environment environment) {
- return new FromConfigurationSecretProviderDeployer(environment);
- }
-
- @Bean
- ResolverService runtimeSecretResolver() {
- SecretProviderRegistry secretProviderRegistry = new SecretProviderRegistry();
- return new DefaultRuntimeResolver(secretProviderRegistry);
- }
-
- @Bean
- ContextUpdater elContextUpdater(
- Cache cache,
- GrantService grantService,
- SpecLifecycleService specLifecycleService,
- EnvAwareSpecRegistry specRegistry
- ) {
- return new ContextUpdater(cache, grantService, specLifecycleService, specRegistry);
- }
-
- static class EnvironmentCondition implements Condition {
-
- @Override
- public boolean matches(ConditionContext context, AnnotatedTypeMetadata ignore) {
- return context.getEnvironment().getProperty("api.secrets.allowProvidersFromConfiguration", Boolean.class, true);
- }
- }
-}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
new file mode 100644
index 000000000..8a012be29
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.spring;
+
+import static com.graviteesource.services.runtimesecrets.config.Config.*;
+
+import com.graviteesource.services.runtimesecrets.RuntimeSecretsProcessingService;
+import com.graviteesource.services.runtimesecrets.RuntimeSecretsService;
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
+import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
+import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
+import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
+import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import com.graviteesource.services.runtimesecrets.providers.config.FromConfigurationSecretProviderDeployer;
+import com.graviteesource.services.runtimesecrets.spec.DefaultSpecLifecycleService;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
+import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
+import io.gravitee.node.api.secrets.runtime.discovery.ContextRegistry;
+import io.gravitee.node.api.secrets.runtime.discovery.DefinitionBrowser;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.secrets.plugins.SecretProviderPluginManager;
+import java.util.List;
+import java.util.function.Predicate;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.*;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.type.AnnotatedTypeMetadata;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Configuration
+public class RuntimeSecretsBeanFactory {
+
+ @Bean
+ Config config(
+ @Value("${" + ON_THE_FLY_SPECS_ENABLED + ":true}") boolean onTheFlySpecsEnabled,
+ @Value("${" + ALLOW_EMPTY_NO_ACL_SPECS + ":true}") boolean allowEmptyACLSpecs,
+ @Value("${" + ON_THE_FLY_SPECS_DELAY_BEFORE_RETRY_MS + ":500}") long onTheFlySpecsDelayBeforeRetryMs
+ ) {
+ return new Config(onTheFlySpecsEnabled, onTheFlySpecsDelayBeforeRetryMs, allowEmptyACLSpecs);
+ }
+
+ @Bean
+ RuntimeSecretsService runtimeSecretsService(
+ RuntimeSecretsProcessingService runtimeSecretsProcessingService,
+ SpecLifecycleService specLifecycleService,
+ SecretProviderDeployer secretProviderDeployer
+ ) {
+ return new RuntimeSecretsService(runtimeSecretsProcessingService, specLifecycleService, secretProviderDeployer);
+ }
+
+ @Bean
+ RuntimeSecretsProcessingService runtimeSecretsProcessingService(
+ DefinitionBrowserRegistry definitionBrowserRegistry,
+ ContextRegistry contextRegistry,
+ SpecRegistry specRegistry,
+ SpecLifecycleService specLifecycleService,
+ GrantService grantService
+ ) {
+ return new RuntimeSecretsProcessingService(
+ definitionBrowserRegistry,
+ contextRegistry,
+ specRegistry,
+ grantService,
+ specLifecycleService
+ );
+ }
+
+ @Bean
+ ContextRegistry contextRegistry() {
+ return new DefaultContextRegistry();
+ }
+
+ @Bean
+ DefinitionBrowserRegistry definitionBrowserRegistry(List browsers) {
+ return new DefinitionBrowserRegistry(browsers);
+ }
+
+ @Bean
+ SpecLifecycleService specLifecycleService(
+ ContextRegistry contextRegistry,
+ Cache cache,
+ ResolverService resolverService,
+ GrantService grantService,
+ Config config
+ ) {
+ return new DefaultSpecLifecycleService(new SpecRegistry(), contextRegistry, cache, resolverService, grantService, config);
+ }
+
+ @Bean
+ Cache secretCache() {
+ return new SimpleOffHeapCache();
+ }
+
+ @Bean
+ GrantService grantService(Config config) {
+ return new DefaultGrantService(new GrantRegistry(), config);
+ }
+
+ @Bean
+ SpecRegistry envAwareSpecRegistry() {
+ return new SpecRegistry();
+ }
+
+ @Bean
+ SecretProviderRegistry secretProviderRegistry() {
+ return new SecretProviderRegistry();
+ }
+
+ @Bean
+ @Conditional(AllowGraviteeYmlProviders.class)
+ SecretProviderDeployer runtimeSecretProviderDeployer(
+ ConfigurableEnvironment environment,
+ SecretProviderRegistry secretProviderRegistry,
+ SecretProviderPluginManager pluginManager
+ ) {
+ return new FromConfigurationSecretProviderDeployer(environment, secretProviderRegistry, pluginManager);
+ }
+
+ @Bean
+ @Conditional({ AllowGraviteeYmlProviders.class })
+ ResolverService resolverService(SecretProviderRegistry secretProviderRegistry) {
+ return new DefaultResolverService(secretProviderRegistry);
+ }
+
+ @Bean
+ @Conditional({ DenyConfigProviders.class })
+ ResolverService runtimeSecretResolver() {
+ return null;
+ }
+
+ @Bean
+ ContextUpdater elContextUpdater(
+ Cache cache,
+ GrantService grantService,
+ SpecLifecycleService specLifecycleService,
+ SpecRegistry specRegistry
+ ) {
+ return new ContextUpdater(cache, grantService, specLifecycleService, specRegistry);
+ }
+
+ private static final Predicate ALLOW_PROVIDERS_FROM_CONFIG = context ->
+ context.getEnvironment().getProperty(API_SECRETS_ALLOW_PROVIDERS_FROM_CONFIGURATION, Boolean.class, true);
+
+ static class AllowGraviteeYmlProviders implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata ignore) {
+ return ALLOW_PROVIDERS_FROM_CONFIG.test(context);
+ }
+ }
+
+ static class DenyConfigProviders implements Condition {
+
+ @Override
+ public boolean matches(ConditionContext context, AnnotatedTypeMetadata ignore) {
+ return ALLOW_PROVIDERS_FROM_CONFIG.negate().test(context);
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/SecretTemplateEngineFactory.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/SecretTemplateEngineFactory.java
new file mode 100644
index 000000000..7dea8e20f
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/SecretTemplateEngineFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.spring;
+
+import com.graviteesource.services.runtimesecrets.el.engine.SecretSpelTemplateEngine;
+import io.gravitee.el.TemplateEngine;
+import io.gravitee.el.TemplateEngineFactory;
+import io.gravitee.el.spel.SpelExpressionParser;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SecretTemplateEngineFactory implements TemplateEngineFactory {
+
+ @Override
+ public TemplateEngine templateEngine() {
+ return new SecretSpelTemplateEngine(new SpelExpressionParser());
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
index 970c85700..be36ebe20 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCache.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.storage;
import com.esotericsoftware.kryo.Kryo;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
new file mode 100644
index 000000000..f87ed7e72
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+io.gravitee.el.TemplateEngineFactory=com.graviteesource.services.runtimesecrets.spring.SecretTemplateEngineFactory
\ No newline at end of file
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java
new file mode 100644
index 000000000..f63685c3e
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.awaitility.Awaitility.await;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
+import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
+import com.graviteesource.services.runtimesecrets.el.engine.SecretSpelTemplateEngine;
+import com.graviteesource.services.runtimesecrets.errors.SecretAccessDeniedException;
+import com.graviteesource.services.runtimesecrets.errors.SecretProviderException;
+import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
+import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
+import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import com.graviteesource.services.runtimesecrets.spec.DefaultSpecLifecycleService;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
+import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
+import io.gravitee.el.spel.SpelExpressionParser;
+import io.gravitee.el.spel.SpelTemplateEngine;
+import io.gravitee.node.api.secrets.runtime.discovery.*;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.spec.ACLs;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.gravitee.node.secrets.plugin.mock.MockSecretProvider;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.security.util.InMemoryResource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class RuntimeSecretsProcessingServiceTest {
+
+ public static final String FOO_ENV_ID = "foo";
+ public static final String BAR_ENV_ID = "bar";
+ InMemoryResource providerAllEnv = new InMemoryResource(
+ """
+ secrets:
+ mySecret:
+ redisPassword: "fighters"
+ ldapPassword: "dog"
+ flaky:
+ password: iamflaky
+ errors:
+ - secret: flaky
+ message: huge error!!!
+ repeat: 1
+ - secret: error
+ message: I am not in the mood
+
+ """
+ );
+ InMemoryResource providerBarEnv = new InMemoryResource(
+ """
+ secrets:
+ mySecret:
+ redisPassword: "tender"
+ ldapPassword: "regular"
+
+ """
+ );
+ private SpecLifecycleService specLifeCycleService;
+ private Cache cache;
+ private SpelTemplateEngine spelTemplateEngine;
+ private RuntimeSecretsProcessingService cut;
+
+ @BeforeEach
+ void before() {
+ SecretProviderRegistry secretProviderRegistry = new SecretProviderRegistry();
+
+ final YamlPropertiesFactoryBean allEnvSPConfig = new YamlPropertiesFactoryBean();
+ allEnvSPConfig.setResources(providerAllEnv);
+ secretProviderRegistry.register(
+ "mock",
+ new MockSecretProvider(new MockSecretProviderConfiguration((Map) new LinkedHashMap<>(allEnvSPConfig.getObject()))),
+ null
+ );
+
+ final YamlPropertiesFactoryBean barEnvSPConfig = new YamlPropertiesFactoryBean();
+ barEnvSPConfig.setResources(providerBarEnv);
+ secretProviderRegistry.register(
+ "mock-bar",
+ new MockSecretProvider(new MockSecretProviderConfiguration((Map) new LinkedHashMap<>(barEnvSPConfig.getObject()))),
+ BAR_ENV_ID
+ );
+
+ cache = new SimpleOffHeapCache();
+ Config config = new Config(true, 200, true);
+ GrantService grantService = new DefaultGrantService(new GrantRegistry(), config);
+ SpecRegistry specRegistry = new SpecRegistry();
+ ContextRegistry contextRegistry = new DefaultContextRegistry();
+ ResolverService resolverService = new DefaultResolverService(secretProviderRegistry);
+ specLifeCycleService = new DefaultSpecLifecycleService(specRegistry, contextRegistry, cache, resolverService, grantService, config);
+ ContextUpdater contextUpdater = new ContextUpdater(cache, grantService, specLifeCycleService, specRegistry);
+ spelTemplateEngine = new SecretSpelTemplateEngine(new SpelExpressionParser());
+ // set up EL variables
+ contextUpdater.addRuntimeSecretsService(spelTemplateEngine.getTemplateContext());
+ spelTemplateEngine.getTemplateContext().setVariable("uris", Map.of("redis", "/mock/mySecret:redisPassword"));
+
+ DefinitionBrowserRegistry browserRegistry = new DefinitionBrowserRegistry(List.of(new TestDefinitionBrowser()));
+ cut = new RuntimeSecretsProcessingService(browserRegistry, contextRegistry, specRegistry, grantService, specLifeCycleService);
+ }
+
+ @Test
+ void should_discover_and_resolve_secret_on_the_fly() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<>", "");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ }
+
+ @Test
+ void should_discover_and_resolve_secret_on_the_fly_with_mixed_string() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "Redis password is: <>!", "");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("Redis password is: fighters!");
+ }
+
+ @Test
+ void should_discover_and_resolve_secret_on_the_fly_from_el() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "Redis password is: << uri {#uris['redis']} >>!", "");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("Redis password is: fighters!");
+ }
+
+ @Test
+ void should_discover_and_get_secret() {
+ final String name = "redis-password";
+ Spec spec = new Spec(null, name, "/mock/mySecret", "redisPassword", null, false, false, null, null, FOO_ENV_ID);
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<<" + name + ">>", "");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ }
+
+ @Test
+ void should_get_a_different_secret_in_two_different_env() {
+ final String name = "redis-password";
+ Spec fooSpec = new Spec(null, name, "/mock/mySecret", "redisPassword", null, false, false, null, null, FOO_ENV_ID);
+ Spec barSpec = new Spec(null, name, "/mock-bar/mySecret", "redisPassword", null, false, false, null, null, BAR_ENV_ID);
+ specLifeCycleService.deploy(fooSpec);
+ specLifeCycleService.deploy(barSpec);
+
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(BAR_ENV_ID, name)).isPresent());
+
+ FakeDefinition fooDefinition = new FakeDefinition("123", "<<" + name + ">>", "");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fooDefinition, Map.of("revision", "1"));
+ assertThat(spelTemplateEngine.getValue(fooDefinition.getFirst(), String.class)).isEqualTo("fighters");
+
+ FakeDefinition barDefinition = new FakeDefinition("123", "<<" + name + ">>", "");
+ cut.onDefinitionDeploy(BAR_ENV_ID, barDefinition, Map.of("revision", "1"));
+ assertThat(spelTemplateEngine.getValue(barDefinition.getFirst(), String.class)).isEqualTo("tender");
+ }
+
+ @Test
+ void should_discover_secrets_in_two_locations() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<>", "<>");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getSecond(), String.class)).isEqualTo("dog");
+ }
+
+ @Test
+ void should_discover_deny_access_to_second_secrets_due_to_ACLs() {
+ Spec spec = new Spec(
+ null,
+ null,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, "/mock/mySecret")).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<>", "<>");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getSecond(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class);
+ }
+
+ @Test
+ void should_fail_getting_secret_secrets_after_spec_undeployed() {
+ String name = "redis-password";
+ Spec spec = new Spec(null, name, "/mock/mySecret", "redisPassword", null, false, false, null, null, FOO_ENV_ID);
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<<" + name + ">>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ specLifeCycleService.undeploy(spec);
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class);
+ }
+
+ @Test
+ void should_fail_to_resolve_secret_on_the_fly_then_succeeds_after_secret_it_is_present() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<< /mock/flaky:password>>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(cache.get(FOO_ENV_ID, "/mock/flaky")).isPresent().get().extracting(Entry::type).isEqualTo(Entry.Type.ERROR);
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class))
+ .isInstanceOf(SecretProviderException.class)
+ .hasMessageContaining("huge error!!!");
+
+ // it should be there any milliseconds
+ await()
+ .atMost(500, TimeUnit.MILLISECONDS)
+ .untilAsserted(() ->
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).doesNotThrowAnyException()
+ );
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("iamflaky");
+ }
+
+ @Test
+ void should_get_a_different_secret_after_spec_update() {
+ String name = "password";
+ String specID = UUID.randomUUID().toString();
+ Spec spec = new Spec(
+ specID,
+ name,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<<" + name + ">>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+
+ Spec specV2 = new Spec(
+ specID,
+ name,
+ "/mock/mySecret",
+ "ldapPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(specV2);
+ awaitShortly()
+ .untilAsserted(() -> assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("dog"));
+ }
+
+ @Test
+ void should_fail_getting_secret_when_acls_changes() {
+ String name = "password";
+ String specID = UUID.randomUUID().toString();
+ Spec spec = new Spec(
+ specID,
+ name,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<<" + name + ">>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+
+ Spec specV2 = new Spec(
+ specID,
+ name,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("second", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(specV2);
+ awaitShortly()
+ .untilAsserted(() ->
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class)
+ );
+ }
+
+ @Test
+ void should_fail_to_get_error_when_secret_provider_returns_error() {
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class))
+ .isInstanceOf(SecretProviderException.class)
+ .hasMessageContaining("I am not in the mood");
+ }
+
+ @Test
+ void should_fail_to_get_secret_after_undeploy() {
+ String name = "password";
+ String specID = UUID.randomUUID().toString();
+ Spec spec = new Spec(
+ specID,
+ name,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+ awaitShortly().untilAsserted(() -> assertThat(cache.get(FOO_ENV_ID, name)).isPresent());
+
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<<" + name + ">>", null);
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+
+ specLifeCycleService.undeploy(spec);
+
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class);
+ }
+
+ @Test
+ void should_go_from_on_the_fly_to_named_user_flow() {
+ // on the fly
+ FakeDefinition fakeDefinition = new FakeDefinition("123", "<>", "<>");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition, Map.of("revision", "1"));
+
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getSecond(), String.class)).isEqualTo("fighters");
+
+ // create spec to limit sage
+ String specID = UUID.randomUUID().toString();
+ Spec spec = new Spec(
+ specID,
+ null,
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+ awaitShortly()
+ .untilAsserted(() -> {
+ assertThat(spelTemplateEngine.getValue(fakeDefinition.getFirst(), String.class)).isEqualTo("fighters");
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition.getSecond(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class);
+ });
+
+ // create spec to limit sage
+ spec =
+ new Spec(
+ specID,
+ "redis-password",
+ "/mock/mySecret",
+ "redisPassword",
+ null,
+ false,
+ false,
+ null,
+ new ACLs(null, List.of(new ACLs.PluginACL("first", null))),
+ FOO_ENV_ID
+ );
+ specLifeCycleService.deploy(spec);
+
+ FakeDefinition fakeDefinition2 = new FakeDefinition("123", "<>", "<< redis-password>>");
+ cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition2, Map.of("revision", "2"));
+
+ awaitShortly()
+ .untilAsserted(() -> {
+ assertThat(cache.get(FOO_ENV_ID, "redis-password")).isPresent().get().extracting(Entry::type).isEqualTo(Entry.Type.VALUE);
+ // TODO assert old secret is still there
+ assertThat(spelTemplateEngine.getValue(fakeDefinition2.getFirst(), String.class)).isEqualTo("fighters");
+ assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition2.getSecond(), String.class))
+ .isInstanceOf(SecretAccessDeniedException.class);
+ });
+ // TODO assert old secret is evict after undeploy of revision 1
+ }
+
+ @Test
+ void should_continue_getting_secret_when_previous_revision_removed_unused_are_evicted() {
+ // PARAMTERIZED => secret are [on the fly, named, uri]
+ // simulate fake definition deploy
+ // - deploy rev1 => secret 1 + secret 2
+ // - deploy rev2 => secret 1
+ // - undeploy rev1
+ // => secret 1 still available
+ }
+
+ static class TestDefinitionBrowser implements DefinitionBrowser {
+
+ @Override
+ public boolean canHandle(Object definition) {
+ return definition instanceof FakeDefinition;
+ }
+
+ @Override
+ public Definition getDefinitionKindLocation(FakeDefinition definition, Map metadata) {
+ return new Definition("test", definition.getId(), Optional.of(metadata.get("revision")));
+ }
+
+ @Override
+ public void findPayloads(FakeDefinition definition, DefinitionPayloadNotifier notifier) {
+ if (definition.getFirst() != null) {
+ notifier.onPayload(definition.getFirst(), new PayloadLocation(PayloadLocation.PLUGIN_KIND, "first"), definition::setFirst);
+ }
+ if (definition.getSecond() != null) {
+ notifier.onPayload(
+ definition.getSecond(),
+ new PayloadLocation(PayloadLocation.PLUGIN_KIND, "second"),
+ definition::setSecond
+ );
+ }
+ }
+ }
+
+ ConditionFactory awaitShortly() {
+ return await().pollDelay(0, TimeUnit.MILLISECONDS).pollInterval(20, TimeUnit.MILLISECONDS).atMost(100, TimeUnit.MILLISECONDS);
+ }
+}
+
+@Data
+@AllArgsConstructor
+class FakeDefinition {
+
+ private String id, first, second;
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
index bed5a9e37..36507e4e4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefDiscovererTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.discovery;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
similarity index 88%
rename from gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java
rename to gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
index ee67ec284..0f6d5eae2 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/api/model/RefTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
@@ -1,9 +1,23 @@
-package com.graviteesource.services.runtimesecrets.api.model;
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.discovery;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.params.provider.Arguments.arguments;
-import com.graviteesource.services.runtimesecrets.discovery.RefParser;
import io.gravitee.node.api.secrets.runtime.discovery.Ref;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayNameGeneration;
@@ -18,7 +32,7 @@
* @author GraviteeSource Team
*/
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
-class RefTest {
+class RefParserTest {
public static Stream okRefs() {
return Stream.of(
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
new file mode 100644
index 000000000..a33b68466
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.el;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.RefParser;
+import com.graviteesource.services.runtimesecrets.el.engine.SecretSpelTemplateEngine;
+import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
+import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
+import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import com.graviteesource.services.runtimesecrets.spec.DefaultSpecLifecycleService;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
+import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
+import io.gravitee.el.spel.SpelExpressionParser;
+import io.gravitee.el.spel.SpelTemplateEngine;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
+import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryLocation;
+import io.gravitee.node.api.secrets.runtime.grant.GrantService;
+import io.gravitee.node.api.secrets.runtime.providers.ResolverService;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Cache;
+import io.gravitee.node.secrets.plugin.mock.MockSecretProvider;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import org.awaitility.core.ConditionFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.security.util.InMemoryResource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class ServiceTest {
+
+ public static final String ENV_ID = "foo";
+ InMemoryResource inMemoryResource = new InMemoryResource(
+ """
+ secrets:
+ mySecret:
+ redisPassword: "redisadmin"
+ ldapPassword: "ldapadmin"
+
+ """
+ );
+ private GrantService grantService;
+ private SpecLifecycleService specLifeCycleService;
+ private Cache cache;
+ private SpelTemplateEngine spelTemplateEngine;
+
+ @BeforeEach
+ void before() {
+ final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
+ yaml.setResources(inMemoryResource);
+ SecretProviderRegistry secretProviderRegistry = new SecretProviderRegistry();
+ secretProviderRegistry.register(
+ "mock",
+ new MockSecretProvider(new MockSecretProviderConfiguration((Map) new LinkedHashMap<>(yaml.getObject()))),
+ null
+ );
+ cache = new SimpleOffHeapCache();
+ Config config = new Config(true, 0, true);
+ this.grantService = new DefaultGrantService(new GrantRegistry(), config);
+ SpecRegistry specRegistry = new SpecRegistry();
+ ResolverService resolverService = new DefaultResolverService(secretProviderRegistry);
+ specLifeCycleService =
+ new DefaultSpecLifecycleService(specRegistry, new DefaultContextRegistry(), cache, resolverService, grantService, config);
+ ContextUpdater contextUpdater = new ContextUpdater(cache, grantService, specLifeCycleService, specRegistry);
+ spelTemplateEngine = new SecretSpelTemplateEngine(new SpelExpressionParser());
+ // set up EL variables
+ contextUpdater.addRuntimeSecretsService(spelTemplateEngine.getTemplateContext());
+ spelTemplateEngine.getTemplateContext().setVariable("keys", Map.of("redis", "redisPassword"));
+ spelTemplateEngine.getTemplateContext().setVariable("names", Map.of("redis", "redis-password"));
+ spelTemplateEngine.getTemplateContext().setVariable("uris", Map.of("redis", "/mock/mySecret:redisPassword"));
+ }
+
+ @CsvSource(
+ value = {
+ "by name, redis-password, redis-password, redisPassword, false, <>",
+ "by uri, null, /mock/mySecret, redisPassword, false, <>",
+ "by name with EL, redis-password, redis-password, null, true, <>",
+ "by uri with EL, null, /mock/mySecret, null, true, <>",
+ },
+ nullValues = "null"
+ )
+ @ParameterizedTest(name = "{0}")
+ void should_call_service_using_fromGrant(
+ String test,
+ String specName,
+ String naturalId,
+ String key,
+ boolean dynKeys,
+ String refAsString
+ ) {
+ Spec spec = new Spec(null, specName, "/mock/mySecret", key, null, dynKeys, false, null, null, ENV_ID);
+ specLifeCycleService.deploy(spec);
+ shortAwait().untilAsserted(() -> assertThat(cache.get(ENV_ID, naturalId)).isPresent());
+
+ DiscoveryContext context = new DiscoveryContext(
+ UUID.randomUUID(),
+ ENV_ID,
+ RefParser.parse(refAsString),
+ new DiscoveryLocation(new DiscoveryLocation.Definition("test", "123"))
+ );
+ boolean authorized = grantService.isGranted(context, spec);
+ assertThat(authorized).isTrue();
+ grantService.grant(context, spec);
+
+ String el = Formatter.computeELFromStatic(context, ENV_ID);
+ assertThat(spelTemplateEngine.getValue(el, String.class)).isEqualTo("redisadmin");
+ }
+
+ @CsvSource(
+ value = {
+ "by name, redis-password, redis-password, <>, true",
+ "by uri, null, /mock/mySecret, <>, true",
+ "by uri on the fly, null, null, <>, false",
+ },
+ nullValues = "null"
+ )
+ @ParameterizedTest(name = "{0}")
+ void should_call_service_using_fromELWith(String test, String specName, String naturalId, String refAsString, boolean createSpec) {
+ DiscoveryContext context = new DiscoveryContext(
+ UUID.randomUUID(),
+ ENV_ID,
+ RefParser.parse(refAsString),
+ new DiscoveryLocation(new DiscoveryLocation.Definition("test", "123"))
+ );
+
+ if (createSpec) {
+ Spec spec = new Spec(null, specName, "/mock/mySecret", "redisPassword", null, false, false, null, null, ENV_ID);
+ specLifeCycleService.deploy(spec);
+ shortAwait().untilAsserted(() -> assertThat(cache.get(ENV_ID, naturalId)).isPresent());
+ boolean authorized = grantService.isGranted(context, spec);
+ assertThat(authorized).isTrue();
+ grantService.grant(context, spec);
+ }
+
+ String el = Formatter.computeELFromEL(context, ENV_ID);
+ assertThat(spelTemplateEngine.getValue(el, String.class)).isEqualTo("redisadmin");
+ }
+
+ ConditionFactory shortAwait() {
+ return await().pollDelay(0, TimeUnit.MILLISECONDS).pollInterval(20, TimeUnit.MILLISECONDS).atMost(100, TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
index a996f4a40..466e4020f 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.grant;
import static org.assertj.core.api.Assertions.assertThat;
@@ -16,6 +31,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -31,101 +47,169 @@ class DefaultGrantServiceTest {
@BeforeEach
void setup() {
- Config config = new Config(true, true);
+ Config config = new Config(true, 0, true);
this.cut = new DefaultGrantService(new GrantRegistry(), config);
}
public static Stream grants() {
return Stream.of(
- arguments("null spec", context("dev", "api", "123"), null, true, "no spec found"),
- arguments("no acl same env", context("dev", "api", "123"), spec("dev", null), true, null),
- arguments("empty acl same env", context("dev", "api", "123"), spec("dev", new ACLs(List.of(), List.of())), true, null),
- arguments("no acl diff env", context("dev", "api", "123"), spec("test", null), false, null),
+ arguments("no acl same env", context("dev", "api", "123"), spec("dev", null, null)),
+ arguments("no acl same key", context("dev", "api", "123", "pwd"), spec("dev", null, "pwd")),
+ arguments("empty acl same env", context("dev", "api", "123"), spec("dev", new ACLs(List.of(), List.of()), null)),
arguments(
"def acl ok",
context("dev", "api", "123"),
- spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null)),
- true,
- null
- ),
- arguments(
- "def acl wrong id",
- context("dev", "api", "123"),
- spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("456"))), null)),
- false,
- null
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null), null)
),
arguments(
- "def acl wrong kind",
- context("dev", "api", "123"),
- spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("dict", List.of("123"))), null)),
- false,
- null
+ "def acl ok same key",
+ context("dev", "api", "123", "pwd"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null), "pwd")
),
arguments(
- "def acl many",
+ "def acl ok many",
context("dev", "api", "123"),
spec(
"dev",
new ACLs(
List.of(new ACLs.DefinitionACL("dict", List.of("123")), new ACLs.DefinitionACL("api", List.of("123", "456"))),
null
- )
- ),
- true,
- null
+ ),
+ null
+ )
),
arguments(
"plugin acl ok",
context("dev", "api", "123", plugin("foo")),
- spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("foo", null)))),
- true,
- null
+ spec(
+ "dev",
+ new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("foo", null))),
+ null
+ )
),
arguments(
- "plugin acl ko",
+ "plugin acl ok many",
context("dev", "api", "123", plugin("foo")),
- spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("bar", null)))),
- false,
- null
+ spec(
+ "dev",
+ new ACLs(
+ List.of(new ACLs.DefinitionACL("api", List.of("123"))),
+ List.of(new ACLs.PluginACL("bar", null), new ACLs.PluginACL("foo", null))
+ ),
+ null
+ )
),
arguments(
- "plugin acl ok many",
+ "plugin acl only",
+ context("dev", "api", "123", plugin("foo")),
+ spec("dev", new ACLs(null, List.of(new ACLs.PluginACL("foo", null))), null)
+ ),
+ arguments(
+ "plugin acl only many",
context("dev", "api", "123", plugin("foo")),
spec(
"dev",
new ACLs(
- List.of(new ACLs.DefinitionACL("api", List.of("123"))),
+ List.of(), // setting an empty list for the sake of testing empty list
List.of(new ACLs.PluginACL("bar", null), new ACLs.PluginACL("foo", null))
- )
- ),
- true,
- null
+ ),
+ null
+ )
+ )
+ );
+ }
+
+ public static Stream denials() {
+ return Stream.of(
+ arguments("no acl diff env", context("dev", "api", "123"), spec("test", null, null)),
+ arguments("no acl diff key", context("dev", "api", "123", "pwd"), spec("dev", null, "pass")),
+ arguments(
+ "def acl ok wrong env",
+ context("dev", "api", "123"),
+ spec("test", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null), null)
+ ),
+ arguments(
+ "def acl ok wrong key",
+ context("dev", "api", "123", "pwd"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), null), "pass")
+ ),
+ arguments(
+ "def acl wrong id",
+ context("dev", "api", "123"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("456"))), null), null)
+ ),
+ arguments(
+ "def acl wrong kind",
+ context("dev", "api", "123"),
+ spec("dev", new ACLs(List.of(new ACLs.DefinitionACL("dict", List.of("123"))), null), null)
+ ),
+ arguments(
+ "plugin acl ko",
+ context("dev", "api", "123", plugin("foo")),
+ spec(
+ "dev",
+ new ACLs(List.of(new ACLs.DefinitionACL("api", List.of("123"))), List.of(new ACLs.PluginACL("bar", null))),
+ null
+ )
+ ),
+ arguments(
+ "plugin acl only ko",
+ context("dev", "api", "123", plugin("bar")),
+ spec("dev", new ACLs(null, List.of(new ACLs.PluginACL("foo", null))), null)
)
);
}
@MethodSource("grants")
@ParameterizedTest(name = "{0}")
- void should_authorize(String name, DiscoveryContext context, Spec spec, boolean granted, String error) {
- if (error != null) {
- assertThatCode(() -> cut.authorize(context, spec)).hasMessageContaining(error);
- } else {
- assertThat(cut.authorize(context, spec)).isEqualTo(granted);
- }
+ void should_grant(String name, DiscoveryContext context, Spec spec) {
+ assertThat(cut.isGranted(context, spec)).isTrue();
+ }
+
+ @MethodSource("denials")
+ @ParameterizedTest(name = "{0}")
+ void should_deny(String name, DiscoveryContext context, Spec spec) {
+ assertThat(cut.isGranted(context, spec)).isFalse();
+ }
+
+ @Test
+ void should_raise_error() {
+ DiscoveryContext context = context("dev", "api", "123");
+ assertThatCode(() -> cut.isGranted(context, null)).hasMessageContaining("no spec found");
+ }
+
+ static DiscoveryContext context(String env, String kind, String id, String key, PayloadLocation... payloads) {
+ return context(
+ env,
+ kind,
+ id,
+ new Ref(
+ Ref.MainType.URI,
+ new Ref.Expression("/mock/secret", false),
+ Ref.SecondaryType.KEY,
+ new Ref.Expression(key, false),
+ "<< /mock/secret:%s >>".formatted(key)
+ ),
+ payloads
+ );
}
static DiscoveryContext context(String env, String kind, String id, PayloadLocation... payloads) {
- return new DiscoveryContext(
- null,
+ return context(
env,
+ kind,
+ id,
new Ref(Ref.MainType.NAME, new Ref.Expression("secret", false), null, null, "<< secret >>"),
- new DiscoveryLocation(new DiscoveryLocation.Definition(kind, id), payloads)
+ payloads
);
}
- static Spec spec(String env, ACLs acls) {
- return new Spec(null, "secret", null, null, null, false, false, null, acls, env);
+ static DiscoveryContext context(String env, String kind, String id, Ref ref, PayloadLocation... payloads) {
+ return new DiscoveryContext(null, env, ref, new DiscoveryLocation(new DiscoveryLocation.Definition(kind, id), payloads));
+ }
+
+ static Spec spec(String env, ACLs acls, String key) {
+ return new Spec(null, "secret", null, key, null, key == null, false, null, acls, env);
}
static PayloadLocation plugin(String id) {
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
new file mode 100644
index 000000000..1e7e4091f
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.providers;
+
+import com.graviteesource.services.runtimesecrets.providers.config.FromConfigurationSecretProviderDeployer;
+import com.graviteesource.services.runtimesecrets.testsupport.PluginManagerHelper;
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.secrets.plugin.mock.MockSecretProvider;
+import io.gravitee.node.secrets.plugins.SecretProviderPluginManager;
+import java.util.LinkedHashMap;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.mock.env.MockEnvironment;
+import org.springframework.security.util.InMemoryResource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class FromConfigurationSecretProviderDeployerTest {
+
+ InMemoryResource inMemoryResource = new InMemoryResource(
+ """
+ api:
+ secrets:
+ providers:
+ - enabled: true
+ plugin: "mock"
+ environments:
+ - "dev"
+ configuration:
+ secrets:
+ mySecret:
+ redisPassword: "foo"
+ ldapPassword: "bar"
+ - enabled: true
+ id: "all-env-secret-manager"
+ plugin: "mock"
+ configuration:
+ secrets:
+ my_secret:
+ redisPassword: "very-long-password"
+ ldapPassword: "also-quite-not-short-password"
+ - enabled: false
+ id: "disabled"
+ plugin: "mock"
+ configuration: {}
+
+ """
+ );
+ private SecretProviderRegistry registry;
+ private SecretProviderPluginManager pluginManager;
+ private FromConfigurationSecretProviderDeployer cut;
+
+ @BeforeEach
+ void before() {
+ final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
+ yaml.setResources(inMemoryResource);
+ MockEnvironment mockEnvironment = new MockEnvironment();
+ mockEnvironment.getPropertySources().addFirst(new MapPropertySource("test", new LinkedHashMap(yaml.getObject())));
+ registry = new SecretProviderRegistry();
+ pluginManager = PluginManagerHelper.newPluginManagerWithMockPlugin();
+ cut = new FromConfigurationSecretProviderDeployer(mockEnvironment, registry, pluginManager);
+ }
+
+ @Test
+ void should_load_providers() {
+ cut.init();
+ AtomicReference last = new AtomicReference<>();
+ registry
+ .get("bar", "all-env-secret-manager")
+ .test()
+ .awaitCount(1)
+ .assertValue(sp -> {
+ last.set(sp);
+ return sp instanceof MockSecretProvider;
+ });
+ registry
+ .get("foo", "all-env-secret-manager")
+ .test()
+ .awaitCount(1)
+ .assertValue(sp -> sp instanceof MockSecretProvider && sp == last.get());
+ registry.get("dev", "mock").test().awaitCount(1).assertValue(sp -> sp instanceof MockSecretProvider && last.get() != sp);
+ registry.get("test", "mock").test().assertError(err -> err.getMessage().contains("[mock] for envId [test]"));
+ registry.get("any", "disabled").test().assertError(err -> err.getMessage().contains("[disabled] for envId [any]"));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleServiceTest.java
new file mode 100644
index 000000000..2b48b363f
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleServiceTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.spec;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
+import com.graviteesource.services.runtimesecrets.discovery.RefParser;
+import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
+import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
+import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
+import com.graviteesource.services.runtimesecrets.providers.SecretProviderRegistry;
+import com.graviteesource.services.runtimesecrets.storage.SimpleOffHeapCache;
+import io.gravitee.node.api.secrets.model.Secret;
+import io.gravitee.node.api.secrets.runtime.discovery.Ref;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.gravitee.node.api.secrets.runtime.storage.Entry;
+import io.gravitee.node.secrets.plugin.mock.MockSecretProvider;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import org.assertj.core.api.InstanceOfAssertFactories;
+import org.awaitility.Awaitility;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayNameGeneration;
+import org.junit.jupiter.api.DisplayNameGenerator;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.security.util.InMemoryResource;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
+class DefaultSpecLifecycleServiceTest {
+
+ public static final String ENV_ID = "foo";
+ InMemoryResource inMemoryResource = new InMemoryResource(
+ """
+ secrets:
+ mySecret:
+ redisPassword: "redisadmin"
+ ldapPassword: "ldapadmin"
+
+ """
+ );
+
+ SpecLifecycleService cut;
+ private SimpleOffHeapCache cache;
+
+ @BeforeEach
+ void before() {
+ final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
+ yaml.setResources(inMemoryResource);
+ SecretProviderRegistry registry = new SecretProviderRegistry();
+ registry.register(
+ "mock",
+ new MockSecretProvider(new MockSecretProviderConfiguration((Map) new LinkedHashMap<>(yaml.getObject()))),
+ null
+ );
+ cache = new SimpleOffHeapCache();
+ cut =
+ new DefaultSpecLifecycleService(
+ new SpecRegistry(),
+ new DefaultContextRegistry(),
+ cache,
+ new DefaultResolverService(registry),
+ new DefaultGrantService(new GrantRegistry(), new Config(true, 0, true)),
+ new Config(true, 0, true)
+ );
+ }
+
+ @Test
+ void should_deploy_spec_and_get_secret_map_from_cache() {
+ Spec spec = new Spec(null, "redis-password", "/mock/mySecret", "redisPassword", null, false, false, null, null, ENV_ID);
+ cut.deploy(spec);
+ Awaitility.await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> checkInCache("redis-password"));
+ }
+
+ @Test
+ void should_deploy_spec_on_the_fly_then_get_secret_map() {
+ Ref ref = RefParser.parse("<>");
+ assertThat(cut.shouldDeployOnTheFly(ref)).isTrue();
+ Spec spec = cut.deployOnTheFly(ENV_ID, ref);
+ assertThat(spec.uri()).isEqualTo("/mock/mySecret");
+ assertThat(spec.key()).isEqualTo("redisPassword");
+ Awaitility.await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> checkInCache("/mock/mySecret"));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "<>", "<>" })
+ void should_not_deploy_on_the_fly(String s) {
+ assertThat(cut.shouldDeployOnTheFly(RefParser.parse(s))).isFalse();
+ }
+
+ @Test
+ void should_deploy_spec_and_get_secret_map_from_cache_un_deploy_check_cache_empty() {
+ Spec spec = new Spec(null, "redis-password", "/mock/mySecret", "redisPassword", null, false, false, null, null, ENV_ID);
+ cut.deploy(spec);
+ Awaitility.await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> checkInCache("redis-password"));
+ cut.undeploy(spec);
+ assertThat(cache.get(ENV_ID, "redis-password")).isNotPresent();
+ }
+
+ private void checkInCache(String natualId) {
+ Optional foo = cache.get(ENV_ID, natualId);
+ assertThat(foo).get().extracting(Entry::type).asString().isEqualTo("VALUE");
+ assertThat(foo)
+ .get()
+ .extracting(Entry::value)
+ .asInstanceOf(InstanceOfAssertFactories.MAP)
+ .containsEntry("redisPassword", new Secret("redisadmin"))
+ .containsEntry("ldapPassword", new Secret("ldapadmin"));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
index 5b5deeba8..d440f0aff 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/storage/SimpleOffHeapCacheTest.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets.storage;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/PluginManagerHelper.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/PluginManagerHelper.java
new file mode 100644
index 000000000..5da101875
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/PluginManagerHelper.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.testsupport;
+
+import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.secrets.plugin.mock.MockSecretProviderFactory;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
+import io.gravitee.node.secrets.plugins.SecretProviderPlugin;
+import io.gravitee.node.secrets.plugins.SecretProviderPluginManager;
+import io.gravitee.node.secrets.plugins.internal.DefaultSecretProviderClassLoaderFactory;
+import io.gravitee.node.secrets.plugins.internal.DefaultSecretProviderPlugin;
+import io.gravitee.node.secrets.plugins.internal.DefaultSecretProviderPluginManager;
+import io.gravitee.plugin.core.api.PluginManifest;
+import java.net.URL;
+import java.nio.file.Path;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class PluginManagerHelper {
+
+ public static SecretProviderPluginManager newPluginManagerWithMockPlugin() {
+ SecretProviderPluginManager pluginManager = new DefaultSecretProviderPluginManager(new DefaultSecretProviderClassLoaderFactory());
+ pluginManager.register(
+ new DefaultSecretProviderPlugin<>(
+ new MockSecretProviderPlugin(true),
+ MockSecretProviderFactory.class,
+ MockSecretProviderConfiguration.class
+ )
+ );
+ return pluginManager;
+ }
+
+ public static class MockSecretProviderPlugin
+ implements SecretProviderPlugin {
+
+ private final boolean deployed;
+
+ public MockSecretProviderPlugin(boolean deployed) {
+ this.deployed = deployed;
+ }
+
+ @Override
+ public String id() {
+ return "mock";
+ }
+
+ @Override
+ public String clazz() {
+ return MockSecretProviderFactory.class.getName();
+ }
+
+ @Override
+ public Class secretProviderFactory() {
+ return MockSecretProviderFactory.class;
+ }
+
+ @Override
+ public Path path() {
+ return Path.of("src/test/resources");
+ }
+
+ @Override
+ public PluginManifest manifest() {
+ return new PluginManifest() {
+ @Override
+ public String id() {
+ return "mock";
+ }
+
+ @Override
+ public String name() {
+ return "Mock Secret Provider";
+ }
+
+ @Override
+ public String description() {
+ return "Mock Secret Provider";
+ }
+
+ @Override
+ public String category() {
+ return "secret providers";
+ }
+
+ @Override
+ public String version() {
+ return "0.0.0";
+ }
+
+ @Override
+ public String plugin() {
+ return MockSecretProviderFactory.class.getName();
+ }
+
+ @Override
+ public String type() {
+ return SecretProvider.PLUGIN_TYPE;
+ }
+
+ @Override
+ public String feature() {
+ return null;
+ }
+ };
+ }
+
+ @Override
+ public URL[] dependencies() {
+ return new URL[0];
+ }
+
+ @Override
+ public boolean deployed() {
+ return this.deployed;
+ }
+
+ @Override
+ public Class configuration() {
+ return null;
+ }
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/SpecFixtures.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/SpecFixtures.java
new file mode 100644
index 000000000..c5159e700
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/testsupport/SpecFixtures.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.graviteesource.services.runtimesecrets.testsupport;
+
+import io.gravitee.node.api.secrets.runtime.spec.ACLs;
+import io.gravitee.node.api.secrets.runtime.spec.Spec;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class SpecFixtures {
+
+ public static Spec namedWithDynKey(String envId, String name, String uri) {
+ return new Spec(UUID.randomUUID().toString(), name, uri, null, null, true, false, null, null, envId);
+ }
+
+ public static Spec fromUriDynKey(String envId, String uri) {
+ return new Spec(UUID.randomUUID().toString(), null, uri, null, null, true, false, null, null, envId);
+ }
+
+ public static Spec fromUriAndKey(String envId, String uri, String key) {
+ return new Spec(UUID.randomUUID().toString(), null, uri, key, null, false, false, null, null, envId);
+ }
+
+ public static Spec namedWithUriAndKey(String envId, String name, String uri, String key) {
+ return new Spec(UUID.randomUUID().toString(), name, uri, key, null, false, false, null, null, envId);
+ }
+
+ public static Spec fromNameUriAndKeyACLs(
+ String envId,
+ String name,
+ String uri,
+ String key,
+ ACLs.DefinitionACL definitionACL,
+ ACLs.PluginACL pluginACL
+ ) {
+ return new Spec(
+ UUID.randomUUID().toString(),
+ name,
+ uri,
+ key,
+ null,
+ false,
+ false,
+ null,
+ new ACLs(List.of(definitionACL), List.of(pluginACL)),
+ envId
+ );
+ }
+
+ public static Spec fromNameUriAndKeyPluginACL(String envId, String name, String uri, String key, ACLs.PluginACL pluginACL) {
+ return new Spec(UUID.randomUUID().toString(), name, uri, key, null, false, false, null, new ACLs(null, List.of(pluginACL)), envId);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/v4-api.json b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/v4-api.json
new file mode 100644
index 000000000..af26f5a5e
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/v4-api.json
@@ -0,0 +1,269 @@
+{
+ "id": "7023b5ac-0fff-402e-a3b5-ac0fff402efe",
+ "name": "Test V4",
+ "type": "proxy",
+ "apiVersion": "1",
+ "definitionVersion": "4.0.0",
+ "tags": [],
+ "properties": [],
+ "resources": [
+ {
+ "name": "users",
+ "type": "auth-provider-inline-resource",
+ "configuration": {
+ "users": [
+ {
+ "username": "batman",
+ "password": "<< /mock/users:batman >>",
+ "roles": []
+ },
+ {
+ "username": "robin",
+ "password": "<< /mock/users:robin >>",
+ "roles": []
+ }
+ ]
+ },
+ "enabled": true
+ }
+ ],
+ "listeners": [
+ {
+ "type": "http",
+ "entrypoints": [
+ {
+ "type": "http-proxy",
+ "configuration": {},
+ "qos": "auto"
+ }
+ ],
+ "paths": [
+ {
+ "path": "/test-benoit-secrets/"
+ }
+ ]
+ }
+ ],
+ "endpointGroups": [
+ {
+ "name": "Default HTTP proxy group",
+ "type": "http-proxy",
+ "loadBalancer": {
+ "type": "round-robin"
+ },
+ "sharedConfiguration": {
+ "headers": [
+ {
+ "name": "Authorization",
+ "value": "Bearer << backend-bearer-token >>"
+ }
+ ],
+ "proxy": {
+ "useSystemProxy": false,
+ "enabled": false
+ },
+ "http": {
+ "keepAliveTimeout": 30000,
+ "keepAlive": true,
+ "followRedirects": false,
+ "readTimeout": 10000,
+ "idleTimeout": 60000,
+ "connectTimeout": 3000,
+ "useCompression": true,
+ "maxConcurrentConnections": 20,
+ "version": "HTTP_1_1",
+ "pipelining": false
+ },
+ "ssl": {
+ "keyStore": {
+ "certContent": "<< /mock/back-end-tls:crt >>",
+ "keyContent": "<< /mock/back-end-tls:key >>",
+ "type": "PEM"
+ },
+ "hostnameVerifier": true,
+ "trustStore": {
+ "type": ""
+ },
+ "trustAll": false
+ }
+ },
+ "endpoints": [
+ {
+ "name": "Default HTTP proxy",
+ "type": "http-proxy",
+ "secondary": false,
+ "weight": 1,
+ "inheritConfiguration": true,
+ "configuration": {
+ "target": "http://localhost:7777"
+ },
+ "sharedConfigurationOverride": {},
+ "services": {
+ "healthCheck": {
+ "overrideConfiguration": true,
+ "enabled": true,
+ "type": "http-health-check",
+ "configuration": {
+ "schedule": "0 */1 * * * *",
+ "headers": [
+ {
+ "name": "Authorization",
+ "value": "ApiKey << /mock/hc:apikey >>"
+ }
+ ],
+ "overrideEndpointPath": true,
+ "method": "GET",
+ "failureThreshold": 2,
+ "assertion": "{#response.status == 200}",
+ "successThreshold": 2,
+ "target": "/"
+ }
+ }
+ }
+ }
+ ],
+ "services": {}
+ }
+ ],
+ "analytics": {
+ "enabled": true
+ },
+ "plans": [
+ {
+ "id": "02ee8113-158c-4ff4-ae81-13158c7ff4f0",
+ "name": "Default Keyless (UNSECURED)",
+ "security": {
+ "type": "key-less",
+ "configuration": {}
+ },
+ "mode": "standard",
+ "tags": [],
+ "status": "published",
+ "flows": [
+ {
+ "id": "57444b7d-2bef-4370-844b-7d2bef737020",
+ "name": "plan",
+ "enabled": true,
+ "request": [
+ {
+ "name": "Assign attributes",
+ "enabled": true,
+ "policy": "policy-assign-attributes",
+ "configuration": {
+ "scope": "REQUEST",
+ "attributes": [
+ {
+ "name": "partner-id",
+ "value": "{#request.headers['X-Partner-Id']}"
+ }
+ ]
+ }
+ },
+ {
+ "name": "Transform Headers",
+ "enabled": true,
+ "policy": "transform-headers",
+ "configuration": {
+ "whitelistHeaders": [],
+ "addHeaders": [
+ {
+ "name": "X-Request-Plan",
+ "value": "<< /mock/headers:request-plan >>"
+ }
+ ],
+ "scope": "REQUEST",
+ "removeHeaders": []
+ }
+ }
+ ],
+ "response": [
+ {
+ "name": "Transform Headers",
+ "enabled": true,
+ "policy": "transform-headers",
+ "configuration": {
+ "whitelistHeaders": [],
+ "addHeaders": [
+ {
+ "name": "X-Response-Plan",
+ "value": "<< /mock/headers:response-plan >>"
+ }
+ ],
+ "scope": "REQUEST",
+ "removeHeaders": []
+ }
+ }
+ ],
+ "subscribe": [],
+ "publish": [],
+ "selectors": [
+ {
+ "type": "http",
+ "path": "/",
+ "pathOperator": "EQUALS",
+ "methods": []
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "flowExecution": {
+ "mode": "default",
+ "matchRequired": false
+ },
+ "flows": [
+ {
+ "id": "45bdee1c-41b7-4a79-bdee-1c41b75a790d",
+ "name": "flow",
+ "enabled": true,
+ "request": [
+ {
+ "name": "Transform Headers",
+ "enabled": true,
+ "policy": "transform-headers",
+ "configuration": {
+ "whitelistHeaders": [],
+ "addHeaders": [
+ {
+ "name": "X-Flow-Request",
+ "value": "<< /mock/headers:request-flow >>"
+ }
+ ],
+ "scope": "REQUEST",
+ "removeHeaders": []
+ }
+ }
+ ],
+ "response": [
+ {
+ "name": "Transform Headers",
+ "enabled": true,
+ "policy": "transform-headers",
+ "configuration": {
+ "whitelistHeaders": [],
+ "addHeaders": [
+ {
+ "name": "X-Flow-Response",
+ "value": "<< /mock/headers:response-plan >>"
+ }
+ ],
+ "scope": "REQUEST",
+ "removeHeaders": []
+ }
+ }
+ ],
+ "subscribe": [],
+ "publish": [],
+ "selectors": [
+ {
+ "type": "http",
+ "path": "/api",
+ "pathOperator": "EQUALS",
+ "methods": []
+ }
+ ]
+ }
+ ],
+ "responseTemplates": {}
+}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/AbstractSecretProviderDispatcher.java b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/AbstractSecretProviderDispatcher.java
index 4cce523ec..86f3151c4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/AbstractSecretProviderDispatcher.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-service/src/main/java/io/gravitee/node/secrets/service/AbstractSecretProviderDispatcher.java
@@ -16,6 +16,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
/**
@@ -25,7 +26,7 @@
@Slf4j
public abstract class AbstractSecretProviderDispatcher implements SecretProviderDispatcher {
- public static final String SECRET_PROVIDER_NOT_FOUND_FOR_ID = "No secret-provider plugin found for provider id: '%s'";
+ public static final String SECRET_PROVIDER_NOT_FOUND_FOR_ID = "No secret-provider plugin found for provider: '%s'";
private final SecretProviderPluginManager secretProviderPluginManager;
private final Map secretProviders = new HashMap<>();
@@ -35,6 +36,10 @@ protected AbstractSecretProviderDispatcher(SecretProviderPluginManager secretPro
}
protected final void createAndRegister(String id) {
+ createAndRegister(id, sp -> secretProviders.put(id, sp));
+ }
+
+ protected final void createAndRegister(String id, Consumer register) {
try {
final SecretProviderPlugin, ?> secretProviderPlugin = secretProviderPluginManager.get(id);
final Class extends SecretManagerConfiguration> configurationClass = secretProviderPlugin.configuration();
@@ -44,7 +49,7 @@ protected final void createAndRegister(String id) {
SecretManagerConfiguration config =
this.readConfiguration(id, factory.getClass().getClassLoader().loadClass(configurationClass.getName()));
// register and start
- secretProviders.put(id, factory.create(config).start());
+ register.accept(factory.create(config).start());
} else {
throw new SecretProviderNotFoundException(SECRET_PROVIDER_NOT_FOUND_FOR_ID.formatted(id));
}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml b/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
index 1862aee66..dd806ce53 100644
--- a/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/pom.xml
@@ -56,6 +56,17 @@
org.assertj
assertj-core
+
+ org.springframework.security
+ spring-security-core
+ test
+
+
+ org.yaml
+ snakeyaml
+ 2.2
+ test
+
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
index d5cebfe08..bfb9ac66b 100644
--- a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProvider.java
@@ -1,44 +1,106 @@
package io.gravitee.node.secrets.plugin.mock;
+import static io.gravitee.plugin.core.internal.AbstractPluginEventListener.SECRET_PROVIDER;
+
import io.gravitee.node.api.secrets.SecretProvider;
import io.gravitee.node.api.secrets.model.SecretEvent;
import io.gravitee.node.api.secrets.model.SecretMap;
import io.gravitee.node.api.secrets.model.SecretMount;
import io.gravitee.node.api.secrets.model.SecretURL;
import io.gravitee.node.api.secrets.util.ConfigHelper;
+import io.gravitee.node.secrets.plugin.mock.conf.ConfiguredError;
+import io.gravitee.node.secrets.plugin.mock.conf.ConfiguredEvent;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Single;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
@RequiredArgsConstructor
+@Slf4j
public class MockSecretProvider implements SecretProvider {
public static final String PLUGIN_ID = "mock";
private final MockSecretProviderConfiguration configuration;
+ Map errorReturned = new ConcurrentHashMap<>();
+
@Override
public Maybe resolve(SecretMount secretMount) {
+ log.info("{}-{} resolving secret: {}", PLUGIN_ID, SECRET_PROVIDER, secretMount);
+
MockSecretLocation location = MockSecretLocation.fromLocation(secretMount.location());
+
+ // return error first
+ Optional errorOpt = configuration.getError(location.secret());
+ if (errorOpt.isPresent()) {
+ ConfiguredError error = errorOpt.get();
+ AtomicInteger errorReturned = this.errorReturned.computeIfAbsent(location.secret(), __ -> new AtomicInteger());
+ if (error.repeat() == 0 || error.repeat() > 0 && errorReturned.getAndIncrement() < error.repeat()) {
+ if (error.repeat() > 0 && secretMount.retryOnError()) {
+ log.info(
+ "{}-{} retrying secret: {} [{}/{}]",
+ PLUGIN_ID,
+ SECRET_PROVIDER,
+ secretMount,
+ error.repeat(),
+ errorReturned.get()
+ );
+ return resolve(secretMount);
+ }
+ String message = "error while getting secret [%s]: %s".formatted(location.secret(), error.message());
+ log.info("{}-{} simulating error: {}", PLUGIN_ID, SECRET_PROVIDER, message);
+ return Maybe.error(new MockSecretProviderException(message));
+ }
+ }
+
+ // normal case
Map secretMap = ConfigHelper.removePrefix(configuration.getSecrets(), location.secret());
if (secretMap.isEmpty()) {
+ log.info("{}-{} no secrets for: {}", PLUGIN_ID, SECRET_PROVIDER, secretMount);
return Maybe.empty();
}
+ log.info("{}-{} found secrets ({}) for: {}", PLUGIN_ID, SECRET_PROVIDER, secretMap.size(), secretMount);
return Maybe.just(SecretMap.of(secretMap));
}
@Override
public Flowable watch(SecretMount secretMount) {
- return Flowable.empty();
+ MockSecretLocation location = MockSecretLocation.fromLocation(secretMount.location());
+ List list = configuration
+ .getConfiguredEvents()
+ .stream()
+ .filter(e -> e.secret().equals(location.secret()))
+ .toList();
+ return Flowable
+ .fromIterable(list)
+ .delay(configuration.getWatchesDelayDuration(), configuration.getWatchesDelayUnit())
+ .flatMapSingle(event -> {
+ if (Objects.equals(event.error(), "null")) {
+ return Single.just(new SecretEvent(event.type(), SecretMap.of(event.data())));
+ } else {
+ return Single.error(new MockSecretProviderException(event.error()));
+ }
+ });
}
@Override
public SecretMount fromURL(SecretURL url) {
- return new SecretMount(PLUGIN_ID, MockSecretLocation.fromUrl(url), url.key(), url);
+ if (url.pluginIdMatchURLProvider() && !Objects.equals(PLUGIN_ID, url.provider())) {
+ throw new MockSecretProviderException("url does not start with '%s%s'".formatted(SecretURL.SCHEME, PLUGIN_ID));
+ }
+ return new SecretMount(url.provider(), MockSecretLocation.fromUrl(url), url.key(), url, true);
}
}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
deleted file mode 100644
index d70ae4109..000000000
--- a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderConfiguration.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package io.gravitee.node.secrets.plugin.mock;
-
-import io.gravitee.node.api.secrets.SecretManagerConfiguration;
-import io.gravitee.node.api.secrets.util.ConfigHelper;
-import java.util.Map;
-import lombok.Getter;
-import lombok.experimental.FieldNameConstants;
-
-/**
- * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
- * @author GraviteeSource Team
- */
-@Getter
-@FieldNameConstants
-public class MockSecretProviderConfiguration implements SecretManagerConfiguration {
-
- private final boolean enabled;
-
- @Getter
- private final Map secrets;
-
- public MockSecretProviderConfiguration(Map config) {
- this.enabled = ConfigHelper.getProperty(config, Fields.enabled, Boolean.class, false);
- this.secrets = ConfigHelper.removePrefix(config, "secrets");
- }
-
- @Override
- public boolean isEnabled() {
- return enabled;
- }
-}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderException.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderException.java
new file mode 100644
index 000000000..5ba46e024
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderException.java
@@ -0,0 +1,12 @@
+package io.gravitee.node.secrets.plugin.mock;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class MockSecretProviderException extends RuntimeException {
+
+ public MockSecretProviderException(String message) {
+ super(message);
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
index 3db78ed87..1fd45ae89 100644
--- a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderFactory.java
@@ -2,6 +2,7 @@
import io.gravitee.node.api.secrets.SecretProvider;
import io.gravitee.node.api.secrets.SecretProviderFactory;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredError.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredError.java
new file mode 100644
index 000000000..c8c2d3a9a
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredError.java
@@ -0,0 +1,3 @@
+package io.gravitee.node.secrets.plugin.mock.conf;
+
+public record ConfiguredError(String message, int repeat) {}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredEvent.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredEvent.java
new file mode 100644
index 000000000..fc17ee704
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/ConfiguredEvent.java
@@ -0,0 +1,6 @@
+package io.gravitee.node.secrets.plugin.mock.conf;
+
+import io.gravitee.node.api.secrets.model.SecretEvent;
+import java.util.Map;
+
+public record ConfiguredEvent(String secret, SecretEvent.Type type, Map data, String error) {}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/MockSecretProviderConfiguration.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/MockSecretProviderConfiguration.java
new file mode 100644
index 000000000..05fa4651e
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/main/java/io/gravitee/node/secrets/plugin/mock/conf/MockSecretProviderConfiguration.java
@@ -0,0 +1,83 @@
+package io.gravitee.node.secrets.plugin.mock.conf;
+
+import io.gravitee.node.api.secrets.SecretManagerConfiguration;
+import io.gravitee.node.api.secrets.model.SecretEvent;
+import io.gravitee.node.api.secrets.util.ConfigHelper;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import lombok.experimental.FieldNameConstants;
+
+/**
+ * @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Getter
+@FieldNameConstants
+public class MockSecretProviderConfiguration implements SecretManagerConfiguration {
+
+ private final boolean enabled;
+ public static final int NO_REPEAT = 0;
+
+ @Getter
+ private final Map secrets;
+
+ private final Map configuredErrors = new ConcurrentHashMap<>();
+ private final List configuredEvents = new ArrayList<>();
+ private final Long watchesDelayDuration;
+ private final TimeUnit watchesDelayUnit;
+
+ public MockSecretProviderConfiguration(Map config) {
+ this.enabled = ConfigHelper.getProperty(config, Fields.enabled, Boolean.class, false);
+ this.secrets = ConfigHelper.removePrefix(config, "secrets");
+ Map watches = ConfigHelper.removePrefix(config, "watches");
+ watchesDelayUnit = TimeUnit.valueOf(watches.getOrDefault("delay.unit", "SECONDS").toString());
+ watchesDelayDuration = Long.parseLong(watches.getOrDefault("delay.duration", "1").toString());
+
+ // process errors
+ int i = 0;
+ String base = error(i);
+ while (config.containsKey(base + ".secret")) {
+ String secret = config.get(base + ".secret").toString();
+ int repeat = Integer.parseInt(config.getOrDefault(base + ".repeat", String.valueOf(NO_REPEAT)).toString());
+ configuredErrors.put(secret, new ConfiguredError(config.getOrDefault(base + ".message", "").toString(), repeat));
+ base = error(++i);
+ }
+
+ // process watch
+ i = 0;
+ base = event(i);
+ while (watches.containsKey(base + ".secret")) {
+ configuredEvents.add(
+ new ConfiguredEvent(
+ watches.get(base + ".secret").toString(),
+ SecretEvent.Type.valueOf(watches.getOrDefault(base + ".type", "CREATED").toString()),
+ ConfigHelper.removePrefix(watches, base + ".data"),
+ String.valueOf(watches.get(base + ".error"))
+ )
+ );
+ base = event(++i);
+ }
+ }
+
+ private static String event(int i) {
+ return "events[%s]".formatted(i);
+ }
+
+ private static String error(int i) {
+ return "errors[%s]".formatted(i);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public Optional getError(String secret) {
+ return Optional.ofNullable(configuredErrors.get(secret));
+ }
+}
diff --git a/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java b/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
index 57d465ea0..82ad64079 100644
--- a/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
+++ b/gravitee-node-secrets/gravitee-secret-provider-mock/src/test/java/io/gravitee/node/secrets/plugin/mock/MockSecretProviderTest.java
@@ -3,14 +3,18 @@
import static org.assertj.core.api.Assertions.assertThat;
import io.gravitee.node.api.secrets.SecretProvider;
-import io.gravitee.node.api.secrets.model.SecretMap;
-import io.gravitee.node.api.secrets.model.SecretMount;
-import io.gravitee.node.api.secrets.model.SecretURL;
+import io.gravitee.node.api.secrets.model.*;
+import io.gravitee.node.secrets.plugin.mock.conf.MockSecretProviderConfiguration;
+import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.security.util.InMemoryResource;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
@@ -23,7 +27,52 @@ class MockSecretProviderTest {
@BeforeEach
void setup() {
- Map conf = Map.of("enabled", true, "secrets.redis.password", "r3d1s", "secrets.ldap.password", "1da9");
+ InMemoryResource inMemoryResource = new InMemoryResource(
+ """
+ enabled: true
+ secrets:
+ redis:
+ password: r3d1s
+ ldap:
+ password: 1da9
+ flaky:
+ value: now it works
+ retry-test:
+ value: after several retries it works
+ errors:
+ - secret: flaky
+ message: next attempt it should work
+ repeat: 1
+ - secret: kafka
+ message: that's just ain't working
+ - secret: retry-test
+ message: fatal error
+ repeat: 10
+ delayMs: 200
+ watches:
+ delay:
+ unit: SECONDS
+ duration: 2
+ events:
+ - secret: apikeys
+ data:
+ partner1: "123"
+ partner2: "456"
+ type: CREATED
+ - secret: apikeys
+ data:
+ partner1: "789"
+ partner2: "101112"
+ type: UPDATED
+ - secret: apikeys
+ data: {}
+ error: odd enough message to be unique
+
+ """
+ );
+ final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
+ yaml.setResources(inMemoryResource);
+ Map conf = new LinkedHashMap<>(yaml.getObject());
this.cut = new MockSecretProviderFactory().create(new MockSecretProviderConfiguration(conf));
}
@@ -38,15 +87,68 @@ void should_create_mount() {
@Test
void should_resolve() {
SecretMount secretMountRedis = cut.fromURL(SecretURL.from("secret://mock/redis:password"));
- cut.resolve(secretMountRedis).test().assertValue(SecretMap.of(Map.of("password", "r3d1s")));
+ cut.resolve(secretMountRedis).test().awaitDone(100, TimeUnit.MILLISECONDS).assertValue(SecretMap.of(Map.of("password", "r3d1s")));
SecretMount secretMountLdap = cut.fromURL(SecretURL.from("secret://mock/ldap"));
- cut.resolve(secretMountLdap).test().assertValue(SecretMap.of(Map.of("password", "1da9")));
+ cut.resolve(secretMountLdap).test().awaitDone(100, TimeUnit.MILLISECONDS).assertValue(SecretMap.of(Map.of("password", "1da9")));
}
@Test
void should_return_empty() {
SecretMount secretMountEmpty = cut.fromURL(SecretURL.from("secret://mock/empty:password"));
- cut.resolve(secretMountEmpty).test().assertNoErrors().assertComplete();
+ cut.resolve(secretMountEmpty).test().awaitDone(100, TimeUnit.MILLISECONDS).assertNoErrors().assertComplete();
+ }
+
+ @Test
+ void should_return_an_error() {
+ SecretMount secretMount = cut.fromURL(SecretURL.from("secret://mock/kafka"));
+ cut
+ .resolve(secretMount)
+ .test()
+ .awaitDone(100, TimeUnit.MILLISECONDS)
+ .assertError(err -> err.getMessage().contains("that's just ain't working"));
+ }
+
+ @Test
+ void should_return_an_error_then_work() {
+ SecretMount secretMount = cut.fromURL(SecretURL.from("secret://mock/flaky")).withoutRetries();
+ cut
+ .resolve(secretMount)
+ .test()
+ .awaitDone(100, TimeUnit.MILLISECONDS)
+ .assertError(err -> err.getMessage().contains("next attempt it should work"));
+ cut.resolve(secretMount).test().awaitDone(100, TimeUnit.MILLISECONDS).assertValue(SecretMap.of(Map.of("value", "now it works")));
+ }
+
+ @Test
+ void should_return_a_secret_after_several_200ms_retries() {
+ SecretMount secretMount = cut.fromURL(SecretURL.from("secret://mock/retry-test"));
+ cut
+ .resolve(secretMount)
+ .test()
+ .awaitDone(6, TimeUnit.SECONDS)
+ .assertValue(SecretMap.of(Map.of("value", "after several retries it works")));
+ }
+
+ @Test
+ void should_watch_values() {
+ SecretMount secretMount = cut.fromURL(SecretURL.from("secret://mock/apikeys"));
+ cut
+ .watch(secretMount)
+ .test()
+ .awaitDone(3, TimeUnit.SECONDS)
+ .assertValueAt(
+ 0,
+ secretEvent ->
+ secretEvent.type() == SecretEvent.Type.CREATED &&
+ secretEvent.secretMap().asMap().values().containsAll(List.of(new Secret("123", false), new Secret("456", false)))
+ )
+ .assertValueAt(
+ 1,
+ secretEvent ->
+ secretEvent.type() == SecretEvent.Type.UPDATED &&
+ secretEvent.secretMap().asMap().values().containsAll(List.of(new Secret("789", false), new Secret("101112", false)))
+ )
+ .assertError(err -> err.getMessage().contains("odd enough message to be unique"));
}
}
From a9f97da2a5d11a2d6092fca573579c05be385a5b Mon Sep 17 00:00:00 2001
From: Benoit Bordigoni
Date: Tue, 8 Oct 2024 11:30:50 +0200
Subject: [PATCH 04/15] fix: demo fixes
---
.../api/secrets/model/SecretLocation.java | 2 +
.../api/secrets/runtime/discovery/Ref.java | 2 +-
.../secrets/runtime/grant/GrantService.java | 4 +-
.../{RenewalPolicy.java => Resolution.java} | 6 +-
.../node/api/secrets/runtime/spec/Spec.java | 2 +-
gravitee-node-container/pom.xml | 12 +-
.../gravitee-node-secrets-runtime/pom.xml | 33 ++--
...sProcessingService.java => Processor.java} | 11 +-
.../runtimesecrets/RuntimeSecretsService.java | 149 +++++++++++++++++-
.../runtimesecrets/discovery/RefParser.java | 4 +
.../services/runtimesecrets/el/Service.java | 4 +-
.../SecretsTemplateVariableProvider.java} | 12 +-
.../grant/DefaultGrantService.java | 30 ++--
.../providers/SecretProviderRegistry.java | 7 +-
...omConfigurationSecretProviderDeployer.java | 6 +-
.../spec/DefaultSpecLifecycleService.java | 84 ++++------
.../runtimesecrets/spec/SpecRegistry.java | 26 +--
.../spring/RuntimeSecretsBeanFactory.java | 30 ++--
.../io.gravitee.el.TemplateEngineFactory | 1 +
.../io.gravitee.el.TemplateVariableProvider | 1 +
.../main/resources/META-INF/spring.factories | 3 +-
...ingServiceTest.java => ProcessorTest.java} | 27 ++--
.../discovery/RefParserTest.java | 2 +-
.../runtimesecrets/el/ServiceTest.java | 14 +-
.../grant/DefaultGrantServiceTest.java | 15 +-
...nfigurationSecretProviderDeployerTest.java | 17 +-
.../src/test/resources/logback.xml | 24 +++
.../gravitee-node-secrets-service/pom.xml | 3 +-
.../AbstractSecretProviderDispatcher.java | 2 +-
.../gravitee-secret-provider-mock/pom.xml | 6 +-
30 files changed, 351 insertions(+), 188 deletions(-)
rename gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/{RenewalPolicy.java => Resolution.java} (67%)
rename gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/{RuntimeSecretsProcessingService.java => Processor.java} (94%)
rename gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/{ContextUpdater.java => engine/SecretsTemplateVariableProvider.java} (73%)
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateEngineFactory
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateVariableProvider
rename gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/{RuntimeSecretsProcessingServiceTest.java => ProcessorTest.java} (95%)
create mode 100644 gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/logback.xml
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretLocation.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretLocation.java
index 94586f3ea..cd05ce283 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretLocation.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/model/SecretLocation.java
@@ -3,6 +3,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import lombok.ToString;
/**
* This class represents where the secret is from a provider perspective. It is a map internally.
@@ -10,6 +11,7 @@
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
+@ToString
public class SecretLocation {
private final Map properties;
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
index dbfd9141e..8c01d8af5 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/discovery/Ref.java
@@ -40,7 +40,7 @@ public Spec asOnTheFlySpec(String envId) {
mainExpression().value(),
secondaryExpression().value(),
null,
- mainType() == MainType.URI && mainExpression.isLiteral() && secondaryType() == null,
+ mainType() == MainType.URI && mainExpression.isLiteral() && (secondaryType() == null || secondaryExpression().isEL()),
true,
null,
null,
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
index 2c6da1d9f..36bdfaf8c 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/grant/GrantService.java
@@ -11,9 +11,7 @@
public interface GrantService {
Optional getGrant(String contextId);
- boolean isGranted(DiscoveryContext context, Spec spec);
-
- void grant(DiscoveryContext context, Spec spec);
+ boolean grant(DiscoveryContext context, Spec spec);
void revoke(DiscoveryContext context);
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Resolution.java
similarity index 67%
rename from gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java
rename to gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Resolution.java
index 319f9e035..cee4dcfe4 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/RenewalPolicy.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Resolution.java
@@ -6,10 +6,10 @@
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
* @author GraviteeSource Team
*/
-public record RenewalPolicy(Type type, Duration duration, Duration checkBeforeTTL) {
+public record Resolution(Type type, Duration interval) {
public enum Type {
- NONE,
- TTL,
+ ONCE,
POLL,
+ WATCH,
}
}
diff --git a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
index 59956422e..db1ce789a 100644
--- a/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
+++ b/gravitee-node-api/src/main/java/io/gravitee/node/api/secrets/runtime/spec/Spec.java
@@ -19,7 +19,7 @@ public record Spec(
List children,
boolean usesDynamicKey,
boolean isOnTheFly,
- RenewalPolicy renewalPolicy,
+ Resolution resolution,
ACLs acls,
String envId
) {
diff --git a/gravitee-node-container/pom.xml b/gravitee-node-container/pom.xml
index c1635d0b9..6cdb91abe 100644
--- a/gravitee-node-container/pom.xml
+++ b/gravitee-node-container/pom.xml
@@ -91,6 +91,12 @@
${project.version}
+
+ io.gravitee.node
+ gravitee-node-secrets-runtime
+ ${project.version}
+
+
io.gravitee.common
gravitee-common
@@ -169,11 +175,5 @@
mockito-core
test
-
- io.gravitee.node
- gravitee-node-secrets-runtime
- 6.4.2
- compile
-
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
index a7126e39a..31cfc1ff4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/pom.xml
@@ -23,12 +23,16 @@
io.gravitee.node
gravitee-node-secrets
- 6.4.2
+ 6.4.4
gravitee-node-secrets-runtime
Gravitee.io - Node - Secrets - Runtime
+
+ 3.2.3
+
+
org.springframework
@@ -38,6 +42,14 @@
io.gravitee.node
gravitee-node-api
+
+ io.gravitee.node
+ gravitee-node-secrets-service
+
+
+ io.gravitee.node
+ gravitee-node-secrets-plugin-handler
+
io.reactivex.rxjava3
rxjava
@@ -45,8 +57,7 @@
io.gravitee.el
gravitee-expression-language
- 3.2.3
- compile
+ ${gravitee-expression-language.version}
@@ -57,7 +68,8 @@
io.gravitee.node
gravitee-secret-provider-mock
- 6.4.2
+ ${project.version}
+ test
org.springframework
@@ -67,7 +79,7 @@
org.yaml
snakeyaml
- 2.2
+ ${snakeyaml.version}
test
@@ -76,15 +88,8 @@
test
- io.gravitee.apim.definition
- gravitee-apim-definition-model
- 4.5.0-SNAPSHOT
- test
-
-
- io.gravitee.apim.definition
- gravitee-apim-definition-jackson
- 4.5.0-SNAPSHOT
+ ch.qos.logback
+ logback-classic
test
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/Processor.java
similarity index 94%
rename from gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java
rename to gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/Processor.java
index 1aa01ad1b..dd462422e 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/Processor.java
@@ -38,7 +38,7 @@
*/
@Slf4j
@RequiredArgsConstructor
-public class RuntimeSecretsProcessingService {
+public class Processor {
private final DefinitionBrowserRegistry definitionBrowserRegistry;
private final ContextRegistry contextRegistry;
@@ -81,7 +81,7 @@ public void onDefinitionDeploy(String envId, @Nonnull T definition, @Nullabl
}
if (context.ref().mainExpression().isLiteral()) {
- boolean granted = grantService.isGranted(context, spec);
+ boolean granted = grantService.grant(context, spec);
if (granted) {
grantService.grant(context, spec);
}
@@ -89,7 +89,7 @@ public void onDefinitionDeploy(String envId, @Nonnull T definition, @Nullabl
}
}
- static class DefaultPayloadNotifier implements DefinitionPayloadNotifier {
+ static final class DefaultPayloadNotifier implements DefinitionPayloadNotifier {
@Getter
final List contextList = new ArrayList<>();
@@ -106,6 +106,11 @@ static class DefaultPayloadNotifier implements DefinitionPayloadNotifier {
@Override
public void onPayload(String payload, PayloadLocation payloadLocation, Consumer updatedPayload) {
+ // no op on empty payloads
+ if (payload == null || payload.isBlank()) {
+ updatedPayload.accept(payload);
+ return;
+ }
PayloadRefParser payloadRefParser = new PayloadRefParser(payload);
List discoveryContexts = payloadRefParser
.runDiscovery()
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
index 5f831ab04..40408f6bb 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsService.java
@@ -1,13 +1,39 @@
+/*
+ * Copyright © 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.graviteesource.services.runtimesecrets;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
import io.gravitee.common.service.AbstractService;
import io.gravitee.node.api.secrets.runtime.providers.SecretProviderDeployer;
+import io.gravitee.node.api.secrets.runtime.spec.ACLs;
import io.gravitee.node.api.secrets.runtime.spec.Spec;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.*;
+import java.util.List;
import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
+import org.springframework.core.env.Environment;
/**
* @author Benoit BORDIGONI (benoit.bordigoni at graviteesource.com)
@@ -16,13 +42,30 @@
@RequiredArgsConstructor
public class RuntimeSecretsService extends AbstractService {
- private final RuntimeSecretsProcessingService runtimeSecretsProcessingService;
+ private final Processor processor;
private final SpecLifecycleService specLifecycleService;
+ private final SpecRegistry specRegistry;
private final SecretProviderDeployer secretProviderDeployer;
+ private final Environment environment;
@Override
protected void doStart() throws Exception {
secretProviderDeployer.init();
+ startWatch(environment.getProperty("rtsecdemodir"));
+ specLifecycleService.deploy(
+ new Spec(
+ "a9c0ea5b-aac8-4064-bed5-47021082f8a2",
+ "dyn-api-keys",
+ "/mock/dynamic-key/named/apikeys",
+ null,
+ null,
+ true,
+ false,
+ null,
+ acls("transform-headers"),
+ "DEFAULT"
+ )
+ );
}
public void deploy(Spec spec) {
@@ -34,6 +77,108 @@ public void undeploy(Spec spec) {
}
public void onDefinitionDeploy(String envId, @Nonnull T definition, @Nullable Map metadata) {
- runtimeSecretsProcessingService.onDefinitionDeploy(envId, definition, metadata);
+ processor.onDefinitionDeploy(envId, definition, metadata);
+ }
+
+ private void startWatch(String directory) {
+ if (directory == null) {
+ return;
+ }
+ Path watched = Path.of(directory);
+ if (!Files.exists(watched)) {
+ return;
+ }
+
+ final WatchService watcherService;
+ try {
+ watcherService = FileSystems.getDefault().newWatchService();
+ watched.register(watcherService, StandardWatchEventKinds.ENTRY_MODIFY);
+ Schedulers
+ .newThread()
+ .scheduleDirect(() -> {
+ while (true) {
+ WatchKey watchKey = watcherService.poll();
+ if (watchKey == null) {
+ continue;
+ }
+ final Optional path = watchKey
+ .pollEvents()
+ .stream()
+ .map(watchEvent -> ((WatchEvent) watchEvent).context())
+ .filter(file -> file.toString().endsWith(".properties"))
+ .findFirst();
+
+ if (path.isPresent()) {
+ Properties properties = new Properties();
+ try {
+ properties.load(new FileReader(watched.resolve(path.get()).toFile()));
+ handleDemo(properties);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+ if (!watchKey.reset()) {
+ break;
+ }
+ }
+ });
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ private void handleDemo(Properties properties) {
+ String pluginToAdd = properties.getProperty("updateSpecACLAddPlugin", "ignore");
+ String specToUndeploy = properties.getProperty("undeploySpec", "");
+ String addSpec = properties.getProperty("newSpecWithACL", "");
+
+ if (!addSpec.isEmpty()) {
+ specLifecycleService.deploy(
+ new Spec(
+ "f9024ec8-ad20-4834-8962-9c9153218983",
+ null,
+ "/mock/static/uri",
+ "api-key",
+ null,
+ false,
+ false,
+ null,
+ acls(addSpec),
+ "DEFAULT"
+ )
+ );
+ }
+ if (!pluginToAdd.equals("ignore")) {
+ deployStaticApiKey(pluginToAdd);
+ }
+
+ if (!specToUndeploy.isEmpty()) {
+ Spec spec = specRegistry.getFromName("DEFAULT", specToUndeploy);
+ specLifecycleService.undeploy(spec);
+ }
+ }
+
+ private void deployStaticApiKey(String pluginToAdd) {
+ specLifecycleService.deploy(
+ new Spec(
+ "e69328d2-cdb0-4970-a94e-c521ff03f1d5",
+ "static-api-key",
+ "/mock/static/named",
+ "api-key",
+ null,
+ false,
+ false,
+ null,
+ acls(pluginToAdd),
+ "DEFAULT"
+ )
+ );
+ }
+
+ private ACLs acls(String pluginToAdd) {
+ if (pluginToAdd.isEmpty()) {
+ return null;
+ }
+ return new ACLs(null, List.of(new ACLs.PluginACL(pluginToAdd, null)));
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
index abf53a089..0169b91d2 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/discovery/RefParser.java
@@ -189,6 +189,10 @@ private String asString() {
}
}
+ public static UriAndKey parseUriAndKey(String expression) {
+ return parseUriAndKey(expression, expression.length());
+ }
+
public static UriAndKey parseUriAndKey(String expression, int end) {
int keyIndex = expression.indexOf(URI_KEY_SEPARATOR);
String uri = expression.substring(0, keyIndex).trim();
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
index 93d330afe..63b4aca36 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/Service.java
@@ -89,7 +89,7 @@ public String fromGrantWithUri(String token, String envId, String name, String c
public String fromELWithUri(String envId, String uriWithKey, String definitionKind, String definitionId) {
if (uriWithKey.contains(URI_KEY_SEPARATOR)) {
- RefParser.UriAndKey uriAndKey = RefParser.parseUriAndKey(uriWithKey, uriWithKey.length());
+ RefParser.UriAndKey uriAndKey = RefParser.parseUriAndKey(uriWithKey);
Ref ref = uriAndKey.asRef();
Spec spec = specRegistry.getFromUriAndKey(envId, uriWithKey);
if (spec == null && specLifecycleService.shouldDeployOnTheFly(ref)) {
@@ -108,7 +108,7 @@ public String fromELWithName(String envId, String name, String definitionKind, S
}
private String grantAndGet(String envId, String definitionKind, String definitionId, Spec spec, Ref ref, String naturalId, String key) {
- boolean granted = grantService.isGranted(
+ boolean granted = grantService.grant(
new DiscoveryContext(null, envId, ref, new DiscoveryLocation(new DiscoveryLocation.Definition(definitionKind, definitionId))),
spec
);
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretsTemplateVariableProvider.java
similarity index 73%
rename from gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
rename to gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretsTemplateVariableProvider.java
index c74b612b0..2dabe34a5 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/ContextUpdater.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/el/engine/SecretsTemplateVariableProvider.java
@@ -13,10 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.graviteesource.services.runtimesecrets.el;
+package com.graviteesource.services.runtimesecrets.el.engine;
+import com.graviteesource.services.runtimesecrets.el.Service;
import com.graviteesource.services.runtimesecrets.spec.SpecRegistry;
import io.gravitee.el.TemplateContext;
+import io.gravitee.el.TemplateVariableProvider;
+import io.gravitee.el.TemplateVariableScope;
+import io.gravitee.el.annotations.TemplateVariable;
import io.gravitee.node.api.secrets.runtime.grant.GrantService;
import io.gravitee.node.api.secrets.runtime.spec.SpecLifecycleService;
import io.gravitee.node.api.secrets.runtime.storage.Cache;
@@ -27,14 +31,16 @@
* @author GraviteeSource Team
*/
@RequiredArgsConstructor
-public class ContextUpdater {
+@TemplateVariable(scopes = { TemplateVariableScope.API, TemplateVariableScope.HEALTH_CHECK })
+public class SecretsTemplateVariableProvider implements TemplateVariableProvider {
private final Cache cache;
private final GrantService grantService;
private final SpecLifecycleService specLifecycleService;
private final SpecRegistry specRegistry;
- public void addRuntimeSecretsService(TemplateContext context) {
+ @Override
+ public void provide(TemplateContext context) {
context.setVariable("secrets", new Service(cache, grantService, specLifecycleService, specRegistry));
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
index 96c41703a..badccbb33 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantService.java
@@ -20,7 +20,6 @@
import static io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation.PLUGIN_KIND;
import com.graviteesource.services.runtimesecrets.config.Config;
-import com.graviteesource.services.runtimesecrets.errors.SecretSpecNotFoundException;
import io.gravitee.node.api.secrets.runtime.discovery.DiscoveryContext;
import io.gravitee.node.api.secrets.runtime.discovery.PayloadLocation;
import io.gravitee.node.api.secrets.runtime.discovery.Ref;
@@ -48,16 +47,24 @@ public class DefaultGrantService implements GrantService {
private final Config config;
@Override
- public boolean isGranted(@Nonnull DiscoveryContext context, Spec spec) {
+ public boolean grant(@Nonnull DiscoveryContext context, Spec spec) {
+ boolean granted = isGranted(context, spec);
+ if (granted && context.id() != null) {
+ grantRegistry.register(context.id().toString(), new Grant(spec.naturalId(), spec.key()));
+ }
+ return granted;
+ }
+
+ private boolean isGranted(DiscoveryContext context, Spec spec) {
if (spec == null) {
- throw new SecretSpecNotFoundException(
- "no spec found or created on-the-fly for ref [%s] in envId [%s], %s=%s".formatted(
- context.ref().rawRef(),
- context.envId(),
- ON_THE_FLY_SPECS_ENABLED,
- config.onTheFlySpecsEnabled()
- )
+ log.warn(
+ "no spec found for ref {} in envId [{}], {}={}",
+ context.ref().rawRef(),
+ context.envId(),
+ ON_THE_FLY_SPECS_ENABLED,
+ config.onTheFlySpecsEnabled()
);
+ return false;
}
if (spec.acls() == null) {
if (!config.allowEmptyACLSpecs()) {
@@ -75,11 +82,6 @@ public boolean isGranted(@Nonnull DiscoveryContext context, Spec spec) {
return checkSpec(context).test(spec) && checkACLs(context).test(spec.acls());
}
- @Override
- public void grant(@Nonnull DiscoveryContext context, Spec spec) {
- grantRegistry.register(context.id().toString(), new Grant(spec.naturalId(), spec.key()));
- }
-
@Override
public Optional getGrant(String contextId) {
return Optional.ofNullable(grantRegistry.get(contextId));
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
index 95c0923c9..5f1c14ac9 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/SecretProviderRegistry.java
@@ -19,6 +19,7 @@
import com.google.common.collect.MultimapBuilder;
import com.graviteesource.services.runtimesecrets.errors.SecretProviderNotFoundException;
import io.gravitee.node.api.secrets.SecretProvider;
+import io.gravitee.node.secrets.service.AbstractSecretProviderDispatcher;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import java.util.HashMap;
@@ -60,11 +61,7 @@ public Single get(String envId, String id) {
.findFirst()
.or(() -> Optional.ofNullable(allEnvs.get(id)))
)
- .switchIfEmpty(
- Single.error(
- new SecretProviderNotFoundException("Cannot find secret provider with id [%s] for envId [%s]".formatted(id, envId))
- )
- );
+ .switchIfEmpty(Single.just(new AbstractSecretProviderDispatcher.ErrorSecretProvider()));
}
public record SecretProviderEntry(String id, SecretProvider provider) {}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
index 39dba85ad..7c389d05e 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/providers/config/FromConfigurationSecretProviderDeployer.java
@@ -55,7 +55,7 @@ public void init() {
String provider = provider(i);
while (apiSecrets.containsKey(provider + ".plugin")) {
Map providerConfig = ConfigHelper.removePrefix(apiSecrets, provider);
- if (!ConfigHelper.getProperty(providerConfig, "enabled", Boolean.class, true)) {
+ if (!ConfigHelper.getProperty(providerConfig, "configuration.enabled", Boolean.class, true)) {
return;
}
String plugin = ConfigHelper.getProperty(providerConfig, "plugin", String.class);
@@ -64,12 +64,12 @@ public void init() {
String environment = environment(e);
while (providerConfig.containsKey(environment)) {
String envId = providerConfig.get(environment).toString();
- deploy(plugin, ConfigHelper.removePrefix(providerConfig, provider + ".configuration"), id, envId);
+ deploy(plugin, ConfigHelper.removePrefix(providerConfig, "configuration"), id, envId);
environment = environment(++e);
}
// no env
if (e == 0) {
- deploy(plugin, ConfigHelper.removePrefix(providerConfig, provider + ".configuration"), id, null);
+ deploy(plugin, ConfigHelper.removePrefix(providerConfig, "configuration"), id, null);
}
provider = provider(++i);
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
index 920ee1df8..8cad232d4 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/DefaultSpecLifecycleService.java
@@ -16,6 +16,7 @@
package com.graviteesource.services.runtimesecrets.spec;
import com.graviteesource.services.runtimesecrets.config.Config;
+import com.graviteesource.services.runtimesecrets.spec.SpecRegistry.SpecUpdate;
import io.gravitee.node.api.secrets.model.SecretMount;
import io.gravitee.node.api.secrets.model.SecretURL;
import io.gravitee.node.api.secrets.runtime.discovery.ContextRegistry;
@@ -27,8 +28,6 @@
import io.gravitee.node.api.secrets.runtime.storage.Cache;
import io.gravitee.node.api.secrets.runtime.storage.Entry;
import io.reactivex.rxjava3.annotations.NonNull;
-import io.reactivex.rxjava3.core.SingleObserver;
-import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.Objects;
@@ -86,54 +85,57 @@ public Spec deployOnTheFly(String envId, Ref ref) {
}
@Override
- public void deploy(Spec newSpec) {
- Spec currentSpec = specRegistry.fromSpec(newSpec.envId(), newSpec);
- log.info("Deploying Secret Spec: {}", newSpec);
- Action afterResolve = () -> {
- specRegistry.register(newSpec);
- };
+ public void deploy(Spec spec) {
+ Spec currentSpec = specRegistry.fromSpec(spec.envId(), spec);
+ log.info("Deploying Secret Spec: {}", spec);
+ Action afterResolve = () -> specRegistry.register(spec);
boolean shouldResolve = true;
if (currentSpec != null) {
- if (isNameOrLocationChanged(currentSpec, newSpec)) {
+ SpecUpdate update = new SpecUpdate(currentSpec, spec);
+ if (isNameOrLocationChanged(update)) {
afterResolve =
() -> {
- renewGrant(currentSpec, newSpec);
- specRegistry.replace(currentSpec, newSpec);
- if (!currentSpec.naturalId().equals(newSpec.naturalId())) {
- cache.evict(newSpec.envId(), currentSpec.naturalId());
+ renewGrant(update);
+ specRegistry.replace(update);
+ if (!currentSpec.naturalId().equals(spec.naturalId())) {
+ cache.evict(currentSpec.envId(), currentSpec.naturalId());
}
};
- } else if (isACLsChange(newSpec, currentSpec)) {
- renewGrant(currentSpec, newSpec);
+ } else if (isACLsChange(update)) {
+ renewGrant(update);
+ specRegistry.replace(update);
shouldResolve = false;
}
+ } else {
+ contextRegistry.findBySpec(spec).forEach(context -> grantService.grant(context, spec));
}
if (shouldResolve) {
- asyncResolution(newSpec, 0, afterResolve);
+ asyncResolution(spec, 0, afterResolve);
}
}
- private void renewGrant(Spec oldSpec, Spec newSpec) {
+ private void renewGrant(SpecUpdate update) {
contextRegistry
- .findBySpec(oldSpec)
+ .findBySpec(update.oldSpec())
.forEach(context -> {
- if (grantService.isGranted(context, newSpec)) {
- grantService.grant(context, newSpec);
- } else {
+ boolean grant = grantService.grant(context, update.newSpec());
+ if (!grant) {
grantService.revoke(context);
}
});
- specRegistry.replace(oldSpec, newSpec);
}
- private static boolean isACLsChange(Spec spec, Spec previousSpec) {
- return !Objects.equals(previousSpec.acls(), spec.acls());
+ private static boolean isACLsChange(SpecUpdate update) {
+ return !Objects.equals(update.oldSpec().acls(), update.newSpec().acls());
}
- private boolean isNameOrLocationChanged(Spec oldSpec, Spec newSpec) {
+ private boolean isNameOrLocationChanged(SpecUpdate update) {
record LiteSpec(String name, String uriAndKey) {}
- return !Objects.equals(new LiteSpec(oldSpec.name(), oldSpec.uriAndKey()), new LiteSpec(newSpec.name(), newSpec.uriAndKey()));
+ return !Objects.equals(
+ new LiteSpec(update.oldSpec().name(), update.oldSpec().uriAndKey()),
+ new LiteSpec(update.newSpec().name(), update.newSpec().uriAndKey())
+ );
}
@Override
@@ -149,31 +151,11 @@ private void asyncResolution(Spec spec, long delayMs, @NonNull Action postResolu
resolverService
.toSecretMount(envId, secretURL)
.delay(delayMs, TimeUnit.MILLISECONDS)
- .doOnSuccess(mount -> {
- log.info("Resolving secret: {}", mount);
- })
+ .doOnSuccess(mount -> log.info("Resolving secret: {}", mount))
.flatMap(mount -> resolverService.resolve(envId, mount).subscribeOn(Schedulers.io()))
- .doOnTerminate(postResolution)
- .subscribe(
- new SimpleSingleObserver<>() {
- @Override
- public void onSuccess(@NonNull Entry entry) {
- cache.put(spec.envId(), spec.naturalId(), entry);
- }
-
- @Override
- public void onError(@NonNull Throwable err) {
- log.error("Async resolution failed", err);
- }
- }
- );
- }
-
- private abstract static class SimpleSingleObserver implements SingleObserver {
-
- @Override
- public void onSubscribe(@NonNull Disposable d) {
- // no op
- }
+ .subscribeOn(Schedulers.io())
+ .doOnError(err -> log.error("Async resolution failed", err))
+ .doFinally(postResolution)
+ .subscribe(entry -> cache.put(spec.envId(), spec.naturalId(), entry));
}
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
index fd32e0039..5a0e13c2f 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spec/SpecRegistry.java
@@ -26,6 +26,8 @@
*/
public class SpecRegistry {
+ public record SpecUpdate(Spec oldSpec, Spec newSpec) {}
+
private final Map registries = new HashMap<>();
public void register(Spec spec) {
@@ -36,11 +38,11 @@ public void unregister(Spec spec) {
registry(spec.envId()).unregister(spec);
}
- public void replace(Spec oldSpec, Spec newSpec) {
- String envId = oldSpec.envId();
+ public void replace(SpecUpdate update) {
+ String envId = update.oldSpec().envId();
synchronized (registry(envId)) {
- registry(envId).unregister(oldSpec);
- registry(envId).register(newSpec);
+ registry(envId).unregister(update.oldSpec());
+ registry(envId).register(update.newSpec());
}
}
@@ -48,18 +50,10 @@ public Spec getFromName(String envId, String name) {
return registry(envId).getFromName(name);
}
- public Spec getFromUri(String envId, String uri) {
- return registry(envId).getFromUri(uri);
- }
-
public Spec getFromUriAndKey(String envId, String uriAndKey) {
return registry(envId).getFromUriAndKey(uriAndKey);
}
- public Spec getFromID(String envId, String id) {
- return registry(envId).getFromID(id);
- }
-
public Spec fromSpec(String envId, Spec query) {
return registry(envId).fromSpec(query);
}
@@ -113,18 +107,10 @@ Spec getFromName(String name) {
return byName.get(name);
}
- Spec getFromUri(String uri) {
- return byUri.get(uri);
- }
-
Spec getFromUriAndKey(String uriAndKey) {
return byUriAndKey.get(uriAndKey);
}
- Spec getFromID(String id) {
- return byID.get(id);
- }
-
Spec fromRef(Ref query) {
if (query.mainType() == Ref.MainType.NAME) {
return byName.get(query.mainExpression().value());
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
index 8a012be29..559b211e9 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/java/com/graviteesource/services/runtimesecrets/spring/RuntimeSecretsBeanFactory.java
@@ -17,12 +17,12 @@
import static com.graviteesource.services.runtimesecrets.config.Config.*;
-import com.graviteesource.services.runtimesecrets.RuntimeSecretsProcessingService;
+import com.graviteesource.services.runtimesecrets.Processor;
import com.graviteesource.services.runtimesecrets.RuntimeSecretsService;
import com.graviteesource.services.runtimesecrets.config.Config;
import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
-import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
+import com.graviteesource.services.runtimesecrets.el.engine.SecretsTemplateVariableProvider;
import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
@@ -44,6 +44,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
@@ -64,28 +65,24 @@ Config config(
@Bean
RuntimeSecretsService runtimeSecretsService(
- RuntimeSecretsProcessingService runtimeSecretsProcessingService,
+ Processor processor,
SpecLifecycleService specLifecycleService,
- SecretProviderDeployer secretProviderDeployer
+ SpecRegistry specRegistry,
+ SecretProviderDeployer secretProviderDeployer,
+ Environment environment
) {
- return new RuntimeSecretsService(runtimeSecretsProcessingService, specLifecycleService, secretProviderDeployer);
+ return new RuntimeSecretsService(processor, specLifecycleService, specRegistry, secretProviderDeployer, environment);
}
@Bean
- RuntimeSecretsProcessingService runtimeSecretsProcessingService(
+ Processor processor(
DefinitionBrowserRegistry definitionBrowserRegistry,
ContextRegistry contextRegistry,
SpecRegistry specRegistry,
SpecLifecycleService specLifecycleService,
GrantService grantService
) {
- return new RuntimeSecretsProcessingService(
- definitionBrowserRegistry,
- contextRegistry,
- specRegistry,
- grantService,
- specLifecycleService
- );
+ return new Processor(definitionBrowserRegistry, contextRegistry, specRegistry, grantService, specLifecycleService);
}
@Bean
@@ -100,13 +97,14 @@ DefinitionBrowserRegistry definitionBrowserRegistry(List brow
@Bean
SpecLifecycleService specLifecycleService(
+ SpecRegistry specRegistry,
ContextRegistry contextRegistry,
Cache cache,
ResolverService resolverService,
GrantService grantService,
Config config
) {
- return new DefaultSpecLifecycleService(new SpecRegistry(), contextRegistry, cache, resolverService, grantService, config);
+ return new DefaultSpecLifecycleService(specRegistry, contextRegistry, cache, resolverService, grantService, config);
}
@Bean
@@ -152,13 +150,13 @@ ResolverService runtimeSecretResolver() {
}
@Bean
- ContextUpdater elContextUpdater(
+ SecretsTemplateVariableProvider secretsTemplateVariableProvider(
Cache cache,
GrantService grantService,
SpecLifecycleService specLifecycleService,
SpecRegistry specRegistry
) {
- return new ContextUpdater(cache, grantService, specLifecycleService, specRegistry);
+ return new SecretsTemplateVariableProvider(cache, grantService, specLifecycleService, specRegistry);
}
private static final Predicate ALLOW_PROVIDERS_FROM_CONFIG = context ->
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateEngineFactory b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateEngineFactory
new file mode 100644
index 000000000..3291a678f
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateEngineFactory
@@ -0,0 +1 @@
+com.graviteesource.services.runtimesecrets.spring.SecretTemplateEngineFactory
\ No newline at end of file
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateVariableProvider b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateVariableProvider
new file mode 100644
index 000000000..ec925d2f3
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/services/io.gravitee.el.TemplateVariableProvider
@@ -0,0 +1 @@
+com.graviteesource.services.runtimesecrets.el.engine.SecretsTemplateVariableProvider
\ No newline at end of file
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
index f87ed7e72..7a1d11184 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/main/resources/META-INF/spring.factories
@@ -1 +1,2 @@
-io.gravitee.el.TemplateEngineFactory=com.graviteesource.services.runtimesecrets.spring.SecretTemplateEngineFactory
\ No newline at end of file
+io.gravitee.el.TemplateVariableProvider=\
+ com.graviteesource.services.runtimesecrets.el.engine.SecretsTemplateVariableProvider
\ No newline at end of file
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/ProcessorTest.java
similarity index 95%
rename from gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java
rename to gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/ProcessorTest.java
index f63685c3e..412f6c892 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/RuntimeSecretsProcessingServiceTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/ProcessorTest.java
@@ -22,8 +22,8 @@
import com.graviteesource.services.runtimesecrets.config.Config;
import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
import com.graviteesource.services.runtimesecrets.discovery.DefinitionBrowserRegistry;
-import com.graviteesource.services.runtimesecrets.el.ContextUpdater;
import com.graviteesource.services.runtimesecrets.el.engine.SecretSpelTemplateEngine;
+import com.graviteesource.services.runtimesecrets.el.engine.SecretsTemplateVariableProvider;
import com.graviteesource.services.runtimesecrets.errors.SecretAccessDeniedException;
import com.graviteesource.services.runtimesecrets.errors.SecretProviderException;
import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
@@ -62,7 +62,7 @@
* @author GraviteeSource Team
*/
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
-class RuntimeSecretsProcessingServiceTest {
+class ProcessorTest {
public static final String FOO_ENV_ID = "foo";
public static final String BAR_ENV_ID = "bar";
@@ -95,7 +95,7 @@ class RuntimeSecretsProcessingServiceTest {
private SpecLifecycleService specLifeCycleService;
private Cache cache;
private SpelTemplateEngine spelTemplateEngine;
- private RuntimeSecretsProcessingService cut;
+ private Processor cut;
@BeforeEach
void before() {
@@ -124,14 +124,19 @@ void before() {
ContextRegistry contextRegistry = new DefaultContextRegistry();
ResolverService resolverService = new DefaultResolverService(secretProviderRegistry);
specLifeCycleService = new DefaultSpecLifecycleService(specRegistry, contextRegistry, cache, resolverService, grantService, config);
- ContextUpdater contextUpdater = new ContextUpdater(cache, grantService, specLifeCycleService, specRegistry);
+ SecretsTemplateVariableProvider secretsTemplateVariableProvider = new SecretsTemplateVariableProvider(
+ cache,
+ grantService,
+ specLifeCycleService,
+ specRegistry
+ );
spelTemplateEngine = new SecretSpelTemplateEngine(new SpelExpressionParser());
// set up EL variables
- contextUpdater.addRuntimeSecretsService(spelTemplateEngine.getTemplateContext());
+ secretsTemplateVariableProvider.provide(spelTemplateEngine.getTemplateContext());
spelTemplateEngine.getTemplateContext().setVariable("uris", Map.of("redis", "/mock/mySecret:redisPassword"));
DefinitionBrowserRegistry browserRegistry = new DefinitionBrowserRegistry(List.of(new TestDefinitionBrowser()));
- cut = new RuntimeSecretsProcessingService(browserRegistry, contextRegistry, specRegistry, grantService, specLifeCycleService);
+ cut = new Processor(browserRegistry, contextRegistry, specRegistry, grantService, specLifeCycleService);
}
@Test
@@ -429,23 +434,27 @@ void should_go_from_on_the_fly_to_named_user_flow() {
);
specLifeCycleService.deploy(spec);
+ awaitShortly()
+ .untilAsserted(() ->
+ assertThat(cache.get(FOO_ENV_ID, "redis-password")).isPresent().get().extracting(Entry::type).isEqualTo(Entry.Type.VALUE)
+ );
+
FakeDefinition fakeDefinition2 = new FakeDefinition("123", "<>", "<< redis-password>>");
cut.onDefinitionDeploy(FOO_ENV_ID, fakeDefinition2, Map.of("revision", "2"));
awaitShortly()
.untilAsserted(() -> {
assertThat(cache.get(FOO_ENV_ID, "redis-password")).isPresent().get().extracting(Entry::type).isEqualTo(Entry.Type.VALUE);
- // TODO assert old secret is still there
+ // TODO assert old secret is still there ???
assertThat(spelTemplateEngine.getValue(fakeDefinition2.getFirst(), String.class)).isEqualTo("fighters");
assertThatCode(() -> spelTemplateEngine.getValue(fakeDefinition2.getSecond(), String.class))
.isInstanceOf(SecretAccessDeniedException.class);
});
- // TODO assert old secret is evict after undeploy of revision 1
+ // TODO assert on the fly secret is evicted after undeploy of revision 1
}
@Test
void should_continue_getting_secret_when_previous_revision_removed_unused_are_evicted() {
- // PARAMTERIZED => secret are [on the fly, named, uri]
// simulate fake definition deploy
// - deploy rev1 => secret 1 + secret 2
// - deploy rev2 => secret 1
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
index 0f6d5eae2..a7bb81252 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/discovery/RefParserTest.java
@@ -135,7 +135,7 @@ void should_parse(String name, String given, Ref expected) {
@Test
void should_parse_uri_and_key() {
String expression = "/provider/secret:password";
- assertThat(RefParser.parseUriAndKey(expression, expression.length()))
+ assertThat(RefParser.parseUriAndKey(expression))
.usingRecursiveComparison()
.isEqualTo(new RefParser.UriAndKey("/provider/secret", "password"));
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
index a33b68466..d78e33b41 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/el/ServiceTest.java
@@ -22,6 +22,7 @@
import com.graviteesource.services.runtimesecrets.discovery.DefaultContextRegistry;
import com.graviteesource.services.runtimesecrets.discovery.RefParser;
import com.graviteesource.services.runtimesecrets.el.engine.SecretSpelTemplateEngine;
+import com.graviteesource.services.runtimesecrets.el.engine.SecretsTemplateVariableProvider;
import com.graviteesource.services.runtimesecrets.grant.DefaultGrantService;
import com.graviteesource.services.runtimesecrets.grant.GrantRegistry;
import com.graviteesource.services.runtimesecrets.providers.DefaultResolverService;
@@ -92,10 +93,15 @@ void before() {
ResolverService resolverService = new DefaultResolverService(secretProviderRegistry);
specLifeCycleService =
new DefaultSpecLifecycleService(specRegistry, new DefaultContextRegistry(), cache, resolverService, grantService, config);
- ContextUpdater contextUpdater = new ContextUpdater(cache, grantService, specLifeCycleService, specRegistry);
+ SecretsTemplateVariableProvider secretsTemplateVariableProvider = new SecretsTemplateVariableProvider(
+ cache,
+ grantService,
+ specLifeCycleService,
+ specRegistry
+ );
spelTemplateEngine = new SecretSpelTemplateEngine(new SpelExpressionParser());
// set up EL variables
- contextUpdater.addRuntimeSecretsService(spelTemplateEngine.getTemplateContext());
+ secretsTemplateVariableProvider.provide(spelTemplateEngine.getTemplateContext());
spelTemplateEngine.getTemplateContext().setVariable("keys", Map.of("redis", "redisPassword"));
spelTemplateEngine.getTemplateContext().setVariable("names", Map.of("redis", "redis-password"));
spelTemplateEngine.getTemplateContext().setVariable("uris", Map.of("redis", "/mock/mySecret:redisPassword"));
@@ -129,7 +135,7 @@ void should_call_service_using_fromGrant(
RefParser.parse(refAsString),
new DiscoveryLocation(new DiscoveryLocation.Definition("test", "123"))
);
- boolean authorized = grantService.isGranted(context, spec);
+ boolean authorized = grantService.grant(context, spec);
assertThat(authorized).isTrue();
grantService.grant(context, spec);
@@ -158,7 +164,7 @@ void should_call_service_using_fromELWith(String test, String specName, String n
Spec spec = new Spec(null, specName, "/mock/mySecret", "redisPassword", null, false, false, null, null, ENV_ID);
specLifeCycleService.deploy(spec);
shortAwait().untilAsserted(() -> assertThat(cache.get(ENV_ID, naturalId)).isPresent());
- boolean authorized = grantService.isGranted(context, spec);
+ boolean authorized = grantService.grant(context, spec);
assertThat(authorized).isTrue();
grantService.grant(context, spec);
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
index 466e4020f..87e9330e3 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/grant/DefaultGrantServiceTest.java
@@ -16,7 +16,6 @@
package com.graviteesource.services.runtimesecrets.grant;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import com.graviteesource.services.runtimesecrets.config.Config;
@@ -31,7 +30,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -156,26 +154,21 @@ public static Stream denials() {
"plugin acl only ko",
context("dev", "api", "123", plugin("bar")),
spec("dev", new ACLs(null, List.of(new ACLs.PluginACL("foo", null))), null)
- )
+ ),
+ arguments("no spec", context("dev", "api", "123", plugin("bar")), null)
);
}
@MethodSource("grants")
@ParameterizedTest(name = "{0}")
void should_grant(String name, DiscoveryContext context, Spec spec) {
- assertThat(cut.isGranted(context, spec)).isTrue();
+ assertThat(cut.grant(context, spec)).isTrue();
}
@MethodSource("denials")
@ParameterizedTest(name = "{0}")
void should_deny(String name, DiscoveryContext context, Spec spec) {
- assertThat(cut.isGranted(context, spec)).isFalse();
- }
-
- @Test
- void should_raise_error() {
- DiscoveryContext context = context("dev", "api", "123");
- assertThatCode(() -> cut.isGranted(context, null)).hasMessageContaining("no spec found");
+ assertThat(cut.grant(context, spec)).isFalse();
}
static DiscoveryContext context(String env, String kind, String id, String key, PayloadLocation... payloads) {
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
index 1e7e4091f..cceb4fb7a 100644
--- a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/java/com/graviteesource/services/runtimesecrets/providers/FromConfigurationSecretProviderDeployerTest.java
@@ -43,32 +43,31 @@ class FromConfigurationSecretProviderDeployerTest {
api:
secrets:
providers:
- - enabled: true
- plugin: "mock"
+ - plugin: "mock"
environments:
- "dev"
configuration:
+ enabled: true
secrets:
mySecret:
redisPassword: "foo"
ldapPassword: "bar"
- - enabled: true
- id: "all-env-secret-manager"
+ - id: "all-env-secret-manager"
plugin: "mock"
configuration:
+ enabled: true
secrets:
my_secret:
redisPassword: "very-long-password"
ldapPassword: "also-quite-not-short-password"
- - enabled: false
- id: "disabled"
+ - id: "disabled"
plugin: "mock"
- configuration: {}
+ configuration:
+ enabled: false
"""
);
private SecretProviderRegistry registry;
- private SecretProviderPluginManager pluginManager;
private FromConfigurationSecretProviderDeployer cut;
@BeforeEach
@@ -78,7 +77,7 @@ void before() {
MockEnvironment mockEnvironment = new MockEnvironment();
mockEnvironment.getPropertySources().addFirst(new MapPropertySource("test", new LinkedHashMap(yaml.getObject())));
registry = new SecretProviderRegistry();
- pluginManager = PluginManagerHelper.newPluginManagerWithMockPlugin();
+ SecretProviderPluginManager pluginManager = PluginManagerHelper.newPluginManagerWithMockPlugin();
cut = new FromConfigurationSecretProviderDeployer(mockEnvironment, registry, pluginManager);
}
diff --git a/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/logback.xml b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/logback.xml
new file mode 100644
index 000000000..36e8353a8
--- /dev/null
+++ b/gravitee-node-secrets/gravitee-node-secrets-runtime/src/test/resources/logback.xml
@@ -0,0 +1,24 @@
+
+
+
+
+