Loading pom.xml +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ <properties> <java.version>17</java.version> <org.etsi.osl.model.tmf.version>1.2.0</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.tmf.version>1.3.0-alpha</org.etsi.osl.model.tmf.version> </properties> <repositories> Loading src/main/java/org/etsi/osl/secretcontroller/ResourceRulesService.java +23 −62 Original line number Diff line number Diff line package org.etsi.osl.secretcontroller; import org.etsi.osl.secretcontroller.config.RulesConfig; import org.etsi.osl.secretcontroller.rules.CharacteristicRule; import org.etsi.osl.secretcontroller.rules.ResourceRule; import org.etsi.osl.secretcontroller.rules.RuleEvaluatorService; import org.etsi.osl.tmf.common.model.EValueType; import org.etsi.osl.tmf.ri639.model.Characteristic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tools.jackson.core.exc.StreamReadException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Service Loading @@ -33,80 +29,45 @@ public class ResourceRulesService { this.mapper = mapper; } public Optional<ResourceRulesApplier> getResourceRules(org.etsi.osl.tmf.ri639.model.Resource resource) { final var cat = resource.getCategory(); final var rule = this.rulesConfig.getResourceRules().get(cat); public ResourceRulesApplier getResourceRules(org.etsi.osl.tmf.ri639.model.Resource resource) { ResourceRule rule = null; if (rule == null) return Optional.empty(); final var cat = resource.getCategory(); if (cat != null) rule = this.rulesConfig.getResourceRules().get(cat); return Optional.of(new ResourceRulesApplier(this.mapper, this.ruleEvaluatorService, resource, rule)); return new ResourceRulesApplier(this.mapper, this.ruleEvaluatorService, resource, Optional.ofNullable(rule)); } public static class ResourceRulesApplier { private final ResourceRule rule; private final Optional<ResourceRule> rule; private final RuleEvaluatorService ruleEvaluatorService; private final Map<String, CharacteristicRule> extraRules = new HashMap<>(); public ResourceRulesApplier(ObjectMapper mapper, RuleEvaluatorService ruleEvaluatorService, org.etsi.osl.tmf.ri639.model.Resource resource, ResourceRule rule) { Optional<ResourceRule> rule) { this.rule = rule; this.ruleEvaluatorService = ruleEvaluatorService; if (!rule.isKubernetesSecret()) return; // Kubernetes secret extra handling final var rawSecret = resource.getResourceCharacteristicByName("json").getValue().getValue(); final JsonNode json; try { json = mapper.readTree(rawSecret); } catch (StreamReadException e) { // If reading fails the secret most likely has already been erased log.debug(e.getMessage()); return; } final var secretData = json.get("data"); // The secret may have already been erased if (secretData == null || !secretData.isObject()) return; final var replaceRule = new CharacteristicRule(); replaceRule.setAction(SecretApplyResult.Action.Replace); replaceRule.setBase64Decode(true); // Convert all characteristics with secret data into secrets for (final var field : secretData.propertyNames()) this.extraRules.put(field, replaceRule); final var eraseRule = new CharacteristicRule(); eraseRule.setAction(SecretApplyResult.Action.Erase); // All these fields contain the secret data in one way or another and must be erased this.extraRules.put("data", eraseRule); this.extraRules.put("json", eraseRule); this.extraRules.put("metadata", eraseRule); } public SecretApplyResult saveCharacteristic(Characteristic characteristic) { final var name = characteristic.getName(); final var valueType = characteristic.getValueType(); final var value = characteristic.getValue().getValue(); final var extraRule = this.extraRules.get(name); if (extraRule != null) return this.ruleEvaluatorService.evaluateRule(characteristic.getValue().getValue(), extraRule); for (final var cRule : this.rule.getCharacteristics()) { if (this.rule.isPresent()) { for (final var cRule : this.rule.get().getCharacteristics()) { final var m = cRule.getMatch().matcher(name); if (!m.matches()) continue; final var value = characteristic.getValue().getValue(); return this.ruleEvaluatorService.evaluateRule(value, cRule); } } else if (valueType != null && valueType.equals(EValueType.SECRET.getValue())) { return SecretApplyResult.replace(value); } return SecretApplyResult.ignore(); } Loading src/main/java/org/etsi/osl/secretcontroller/ServiceRulesService.java +18 −16 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package org.etsi.osl.secretcontroller; import org.etsi.osl.secretcontroller.config.RulesConfig; import org.etsi.osl.secretcontroller.rules.RuleEvaluatorService; import org.etsi.osl.secretcontroller.rules.ServiceRule; import org.etsi.osl.tmf.common.model.EValueType; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Loading @@ -20,36 +21,37 @@ public class ServiceRulesService { this.ruleEvaluatorService = ruleEvaluatorService; } public Optional<ServiceRulesApplier> getServiceRules(org.etsi.osl.tmf.sim638.model.Service svc) { public ServiceRulesApplier getServiceRules(org.etsi.osl.tmf.sim638.model.Service svc) { final var name = svc.getName(); final var rule = this.rulesConfig.getServiceRules().get(name); if (rule == null) return Optional.empty(); return Optional.of(new ServiceRulesApplier(this.ruleEvaluatorService, rule)); return new ServiceRulesApplier(this.ruleEvaluatorService, Optional.ofNullable(rule)); } public static class ServiceRulesApplier { private final ServiceRule rule; private final Optional<ServiceRule> rule; private final RuleEvaluatorService ruleEvaluatorService; public ServiceRulesApplier(RuleEvaluatorService ruleEvaluatorService, ServiceRule rule) { public ServiceRulesApplier(RuleEvaluatorService ruleEvaluatorService, Optional<ServiceRule> rule) { this.rule = rule; this.ruleEvaluatorService = ruleEvaluatorService; } public SecretApplyResult saveCharacteristic(Characteristic characteristic) { final var name = characteristic.getName(); final var valueType = characteristic.getValueType(); final var value = characteristic.getValue().getValue(); for (final var cRule : this.rule.characteristics()) { if (this.rule.isPresent()) { for (final var cRule : this.rule.get().characteristics()) { final var m = cRule.getMatch().matcher(name); if (!m.matches()) continue; var value = characteristic.getValue().getValue(); return this.ruleEvaluatorService.evaluateRule(value, cRule); } } else if (valueType != null && valueType.equals(EValueType.SECRET.getValue())) { return SecretApplyResult.replace(value); } return SecretApplyResult.ignore(); } Loading src/main/java/org/etsi/osl/secretcontroller/VaultService.java +26 −13 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ import org.springframework.vault.core.VaultKeyValueOperations; import org.springframework.vault.core.VaultKeyValueOperationsSupport; import org.springframework.vault.core.VaultTemplate; import org.springframework.web.util.UriUtils; import tools.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Optional; @Service public class VaultService { Loading @@ -29,31 +29,44 @@ public class VaultService { if (vaultUri.endsWith("/")) vaultUri = vaultUri.substring(0, vaultUri.length() - 1); this.vaultBaseSecretUri = String.format("%s/v1/%s", vaultUri, kvPath); this.vaultBaseSecretUri = String.format("%s/v1/%s/data/", vaultUri, kvPath); } public final String getVaultKey(String namespace, String key) { return String.format("%s::%s", namespace, key); public String getVaultKey(String namespace, String key) { return String.format("%s/%s", namespace, key); } public final String getVaultUri(String secretKey) { return String.format( "%s/data/%s", vaultBaseSecretUri, UriUtils.encodePathSegment(secretKey, StandardCharsets.US_ASCII) ); public String getVaultUri(String secretKey) { return vaultBaseSecretUri + UriUtils.encodePath(secretKey, StandardCharsets.US_ASCII); } public boolean isAlreadySecret(String value) { return value.startsWith(this.vaultBaseSecretUri); } public Optional<String> extractVaultKeyFromUri(String secretUri) { if (!secretUri.startsWith(this.vaultBaseSecretUri)) return Optional.empty(); return Optional.of(secretUri.substring(this.vaultBaseSecretUri.length())); } public Optional<Map<String, ?>> getSecret(String key) { final var secret = this.vaultKV.get(key); if (secret == null) return Optional.empty(); final var data = secret.getData(); if (data == null) return Optional.empty(); return Optional.of(data); } public void upsertSecret(String key, Map<String, ?> data) { final var secret = Map.of("data", data); try { this.vaultKV.patch(key, secret); this.vaultKV.patch(key, data); } catch (SecretNotFoundException e) { this.vaultKV.put(key, secret); this.vaultKV.put(key, data); } } Loading src/main/java/org/etsi/osl/secretcontroller/listeners/ControllerCommandsListener.java 0 → 100644 +67 −0 Original line number Diff line number Diff line package org.etsi.osl.secretcontroller.listeners; import org.etsi.osl.secretcontroller.VaultService; import org.etsi.osl.secretcontroller.listeners.commands.UnsealCharacteristicCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.jms.core.JmsClient; import org.springframework.jms.support.JmsHeaders; import org.springframework.jms.support.JmsMessageHeaderAccessor; import org.springframework.stereotype.Component; import tools.jackson.databind.ObjectMapper; import java.util.Map; @Component public class ControllerCommandsListener { private static final Logger log = LoggerFactory.getLogger(ControllerCommandsListener.class); private final VaultService vaultService; private final JmsClient jmsClient; private final ObjectMapper objectMapper; @Autowired public ControllerCommandsListener(VaultService vaultService, JmsClient jmsClient, ObjectMapper objectMapper) { this.vaultService = vaultService; this.jmsClient = jmsClient; this.objectMapper = objectMapper; } @JmsListener(destination = "${topics.SECRETS_CHARACTERISTIC_UNSEAL}") public void processUnsealCommand(String content, JmsMessageHeaderAccessor jmsHeaderAccessor) { final var destination = jmsHeaderAccessor.getReplyTo(); if (destination == null) { log.warn("Unseal request: no reply destination"); return; } final var cmd = this.objectMapper.readValue(content, UnsealCharacteristicCommand.class); final var vaultKeyOpt = this.vaultService.extractVaultKeyFromUri(cmd.secretURI()); if (vaultKeyOpt.isEmpty()) { // Ignore this command as the URI isn't for this controller log.debug("Unseal request: secret URI does not belong to this controller"); return; } final var vaultKey = vaultKeyOpt.get(); Object result = null; final var secretData = this.vaultService.getSecret(vaultKey); if (secretData.isPresent()) { final var characteristicValue = secretData.get().get(cmd.characteristic()); if (characteristicValue != null) { result = characteristicValue; } else { log.debug("Unseal request: Characteristic not found"); } } else { log.debug("Unseal request: Secret not found"); } this.jmsClient.destination(destination) .send(this.objectMapper.writeValueAsString(result), Map.of(JmsHeaders.CORRELATION_ID, jmsHeaderAccessor.getCorrelationId())); } } Loading
pom.xml +1 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,7 @@ <properties> <java.version>17</java.version> <org.etsi.osl.model.tmf.version>1.2.0</org.etsi.osl.model.tmf.version> <org.etsi.osl.model.tmf.version>1.3.0-alpha</org.etsi.osl.model.tmf.version> </properties> <repositories> Loading
src/main/java/org/etsi/osl/secretcontroller/ResourceRulesService.java +23 −62 Original line number Diff line number Diff line package org.etsi.osl.secretcontroller; import org.etsi.osl.secretcontroller.config.RulesConfig; import org.etsi.osl.secretcontroller.rules.CharacteristicRule; import org.etsi.osl.secretcontroller.rules.ResourceRule; import org.etsi.osl.secretcontroller.rules.RuleEvaluatorService; import org.etsi.osl.tmf.common.model.EValueType; import org.etsi.osl.tmf.ri639.model.Characteristic; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import tools.jackson.core.exc.StreamReadException; import tools.jackson.databind.JsonNode; import tools.jackson.databind.ObjectMapper; import java.util.HashMap; import java.util.Map; import java.util.Optional; @Service Loading @@ -33,80 +29,45 @@ public class ResourceRulesService { this.mapper = mapper; } public Optional<ResourceRulesApplier> getResourceRules(org.etsi.osl.tmf.ri639.model.Resource resource) { final var cat = resource.getCategory(); final var rule = this.rulesConfig.getResourceRules().get(cat); public ResourceRulesApplier getResourceRules(org.etsi.osl.tmf.ri639.model.Resource resource) { ResourceRule rule = null; if (rule == null) return Optional.empty(); final var cat = resource.getCategory(); if (cat != null) rule = this.rulesConfig.getResourceRules().get(cat); return Optional.of(new ResourceRulesApplier(this.mapper, this.ruleEvaluatorService, resource, rule)); return new ResourceRulesApplier(this.mapper, this.ruleEvaluatorService, resource, Optional.ofNullable(rule)); } public static class ResourceRulesApplier { private final ResourceRule rule; private final Optional<ResourceRule> rule; private final RuleEvaluatorService ruleEvaluatorService; private final Map<String, CharacteristicRule> extraRules = new HashMap<>(); public ResourceRulesApplier(ObjectMapper mapper, RuleEvaluatorService ruleEvaluatorService, org.etsi.osl.tmf.ri639.model.Resource resource, ResourceRule rule) { Optional<ResourceRule> rule) { this.rule = rule; this.ruleEvaluatorService = ruleEvaluatorService; if (!rule.isKubernetesSecret()) return; // Kubernetes secret extra handling final var rawSecret = resource.getResourceCharacteristicByName("json").getValue().getValue(); final JsonNode json; try { json = mapper.readTree(rawSecret); } catch (StreamReadException e) { // If reading fails the secret most likely has already been erased log.debug(e.getMessage()); return; } final var secretData = json.get("data"); // The secret may have already been erased if (secretData == null || !secretData.isObject()) return; final var replaceRule = new CharacteristicRule(); replaceRule.setAction(SecretApplyResult.Action.Replace); replaceRule.setBase64Decode(true); // Convert all characteristics with secret data into secrets for (final var field : secretData.propertyNames()) this.extraRules.put(field, replaceRule); final var eraseRule = new CharacteristicRule(); eraseRule.setAction(SecretApplyResult.Action.Erase); // All these fields contain the secret data in one way or another and must be erased this.extraRules.put("data", eraseRule); this.extraRules.put("json", eraseRule); this.extraRules.put("metadata", eraseRule); } public SecretApplyResult saveCharacteristic(Characteristic characteristic) { final var name = characteristic.getName(); final var valueType = characteristic.getValueType(); final var value = characteristic.getValue().getValue(); final var extraRule = this.extraRules.get(name); if (extraRule != null) return this.ruleEvaluatorService.evaluateRule(characteristic.getValue().getValue(), extraRule); for (final var cRule : this.rule.getCharacteristics()) { if (this.rule.isPresent()) { for (final var cRule : this.rule.get().getCharacteristics()) { final var m = cRule.getMatch().matcher(name); if (!m.matches()) continue; final var value = characteristic.getValue().getValue(); return this.ruleEvaluatorService.evaluateRule(value, cRule); } } else if (valueType != null && valueType.equals(EValueType.SECRET.getValue())) { return SecretApplyResult.replace(value); } return SecretApplyResult.ignore(); } Loading
src/main/java/org/etsi/osl/secretcontroller/ServiceRulesService.java +18 −16 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ package org.etsi.osl.secretcontroller; import org.etsi.osl.secretcontroller.config.RulesConfig; import org.etsi.osl.secretcontroller.rules.RuleEvaluatorService; import org.etsi.osl.secretcontroller.rules.ServiceRule; import org.etsi.osl.tmf.common.model.EValueType; import org.etsi.osl.tmf.common.model.service.Characteristic; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; Loading @@ -20,36 +21,37 @@ public class ServiceRulesService { this.ruleEvaluatorService = ruleEvaluatorService; } public Optional<ServiceRulesApplier> getServiceRules(org.etsi.osl.tmf.sim638.model.Service svc) { public ServiceRulesApplier getServiceRules(org.etsi.osl.tmf.sim638.model.Service svc) { final var name = svc.getName(); final var rule = this.rulesConfig.getServiceRules().get(name); if (rule == null) return Optional.empty(); return Optional.of(new ServiceRulesApplier(this.ruleEvaluatorService, rule)); return new ServiceRulesApplier(this.ruleEvaluatorService, Optional.ofNullable(rule)); } public static class ServiceRulesApplier { private final ServiceRule rule; private final Optional<ServiceRule> rule; private final RuleEvaluatorService ruleEvaluatorService; public ServiceRulesApplier(RuleEvaluatorService ruleEvaluatorService, ServiceRule rule) { public ServiceRulesApplier(RuleEvaluatorService ruleEvaluatorService, Optional<ServiceRule> rule) { this.rule = rule; this.ruleEvaluatorService = ruleEvaluatorService; } public SecretApplyResult saveCharacteristic(Characteristic characteristic) { final var name = characteristic.getName(); final var valueType = characteristic.getValueType(); final var value = characteristic.getValue().getValue(); for (final var cRule : this.rule.characteristics()) { if (this.rule.isPresent()) { for (final var cRule : this.rule.get().characteristics()) { final var m = cRule.getMatch().matcher(name); if (!m.matches()) continue; var value = characteristic.getValue().getValue(); return this.ruleEvaluatorService.evaluateRule(value, cRule); } } else if (valueType != null && valueType.equals(EValueType.SECRET.getValue())) { return SecretApplyResult.replace(value); } return SecretApplyResult.ignore(); } Loading
src/main/java/org/etsi/osl/secretcontroller/VaultService.java +26 −13 Original line number Diff line number Diff line Loading @@ -8,10 +8,10 @@ import org.springframework.vault.core.VaultKeyValueOperations; import org.springframework.vault.core.VaultKeyValueOperationsSupport; import org.springframework.vault.core.VaultTemplate; import org.springframework.web.util.UriUtils; import tools.jackson.databind.ObjectMapper; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Optional; @Service public class VaultService { Loading @@ -29,31 +29,44 @@ public class VaultService { if (vaultUri.endsWith("/")) vaultUri = vaultUri.substring(0, vaultUri.length() - 1); this.vaultBaseSecretUri = String.format("%s/v1/%s", vaultUri, kvPath); this.vaultBaseSecretUri = String.format("%s/v1/%s/data/", vaultUri, kvPath); } public final String getVaultKey(String namespace, String key) { return String.format("%s::%s", namespace, key); public String getVaultKey(String namespace, String key) { return String.format("%s/%s", namespace, key); } public final String getVaultUri(String secretKey) { return String.format( "%s/data/%s", vaultBaseSecretUri, UriUtils.encodePathSegment(secretKey, StandardCharsets.US_ASCII) ); public String getVaultUri(String secretKey) { return vaultBaseSecretUri + UriUtils.encodePath(secretKey, StandardCharsets.US_ASCII); } public boolean isAlreadySecret(String value) { return value.startsWith(this.vaultBaseSecretUri); } public Optional<String> extractVaultKeyFromUri(String secretUri) { if (!secretUri.startsWith(this.vaultBaseSecretUri)) return Optional.empty(); return Optional.of(secretUri.substring(this.vaultBaseSecretUri.length())); } public Optional<Map<String, ?>> getSecret(String key) { final var secret = this.vaultKV.get(key); if (secret == null) return Optional.empty(); final var data = secret.getData(); if (data == null) return Optional.empty(); return Optional.of(data); } public void upsertSecret(String key, Map<String, ?> data) { final var secret = Map.of("data", data); try { this.vaultKV.patch(key, secret); this.vaultKV.patch(key, data); } catch (SecretNotFoundException e) { this.vaultKV.put(key, secret); this.vaultKV.put(key, data); } } Loading
src/main/java/org/etsi/osl/secretcontroller/listeners/ControllerCommandsListener.java 0 → 100644 +67 −0 Original line number Diff line number Diff line package org.etsi.osl.secretcontroller.listeners; import org.etsi.osl.secretcontroller.VaultService; import org.etsi.osl.secretcontroller.listeners.commands.UnsealCharacteristicCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jms.annotation.JmsListener; import org.springframework.jms.core.JmsClient; import org.springframework.jms.support.JmsHeaders; import org.springframework.jms.support.JmsMessageHeaderAccessor; import org.springframework.stereotype.Component; import tools.jackson.databind.ObjectMapper; import java.util.Map; @Component public class ControllerCommandsListener { private static final Logger log = LoggerFactory.getLogger(ControllerCommandsListener.class); private final VaultService vaultService; private final JmsClient jmsClient; private final ObjectMapper objectMapper; @Autowired public ControllerCommandsListener(VaultService vaultService, JmsClient jmsClient, ObjectMapper objectMapper) { this.vaultService = vaultService; this.jmsClient = jmsClient; this.objectMapper = objectMapper; } @JmsListener(destination = "${topics.SECRETS_CHARACTERISTIC_UNSEAL}") public void processUnsealCommand(String content, JmsMessageHeaderAccessor jmsHeaderAccessor) { final var destination = jmsHeaderAccessor.getReplyTo(); if (destination == null) { log.warn("Unseal request: no reply destination"); return; } final var cmd = this.objectMapper.readValue(content, UnsealCharacteristicCommand.class); final var vaultKeyOpt = this.vaultService.extractVaultKeyFromUri(cmd.secretURI()); if (vaultKeyOpt.isEmpty()) { // Ignore this command as the URI isn't for this controller log.debug("Unseal request: secret URI does not belong to this controller"); return; } final var vaultKey = vaultKeyOpt.get(); Object result = null; final var secretData = this.vaultService.getSecret(vaultKey); if (secretData.isPresent()) { final var characteristicValue = secretData.get().get(cmd.characteristic()); if (characteristicValue != null) { result = characteristicValue; } else { log.debug("Unseal request: Characteristic not found"); } } else { log.debug("Unseal request: Secret not found"); } this.jmsClient.destination(destination) .send(this.objectMapper.writeValueAsString(result), Map.of(JmsHeaders.CORRELATION_ID, jmsHeaderAccessor.getCorrelationId())); } }