From 293039d9c03a1cfada987f2de6d87dd1d5c69c0d Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 28 Jan 2025 12:28:26 +0100 Subject: [PATCH 1/2] Example of "forwarding" dispatcher. --- .../dispatchers/ForwardingDispatcher.java | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java new file mode 100644 index 0000000..a65ed79 --- /dev/null +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2008 Sonatype, Inc. All rights reserved. + * + * This program is licensed to you under the Apache License Version 2.0, + * and you may not use this file except in compliance with the Apache License Version 2.0. + * You may obtain a copy of the Apache License Version 2.0 at http://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the Apache License Version 2.0 is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Apache License Version 2.0 for the specific language governing permissions and limitations there under. + */ + +package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; + +import org.codehaus.plexus.components.secdispatcher.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This dispatcher forwards requests fully to defined sources. + */ +@Singleton +@Named(ForwardingDispatcher.NAME) +public class ForwardingDispatcher implements Dispatcher, DispatcherMeta { + public static final String NAME = "forwarding"; + + private static final String CONF_SOURCE = "source"; + + protected final Map sources; + + @Inject + public ForwardingDispatcher(Map sources) { + this.sources = sources; + } + + @Override + public String name() { + return NAME; + } + + @Override + public String displayName() { + return "Forwarding Password Dispatcher"; + } + + @Override + public Collection fields() { + return List.of( + Field.builder(CONF_SOURCE) + .optional(false) + .description("Source of the password") + .options(sources.entrySet().stream() + .map(e -> { + MasterSource ms = e.getValue(); + if (ms instanceof MasterSourceMeta m) { + Field.Builder b = + Field.builder(e.getKey()).description(m.description()); + if (m.configTemplate().isPresent()) { + b.defaultValue(m.configTemplate().get()); + } + return b.build(); + } else { + return Field.builder(e.getKey()) + .description(e.getKey() + + "(Field not described, needs manual configuration)") + .build(); + } + }) + .toList()) + .build()); + } + + @Override + public EncryptPayload encrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + throw new UnsupportedOperationException("Forwarding dispatcher does not support encryption"); + } + + @Override + public String decrypt(String str, Map attributes, Map config) + throws SecDispatcherException { + MasterSource masterSource = getPasswordSource(config); + return masterSource.handle(str); + } + + @Override + public SecDispatcher.ValidationResponse validateConfiguration(Map config) { + HashMap> report = new HashMap<>(); + ArrayList subsystems = new ArrayList<>(); + boolean valid = false; + String masterSource = config.get(CONF_SOURCE); + if (masterSource == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Source configuration missing"); + } else { + SecDispatcher.ValidationResponse masterSourceResponse = null; + for (MasterSource masterPasswordSource : sources.values()) { + masterSourceResponse = masterPasswordSource.validateConfiguration(masterSource); + if (masterSourceResponse != null) { + break; + } + } + if (masterSourceResponse == null) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Configured Source configuration not handled"); + } else { + subsystems.add(masterSourceResponse); + if (!masterSourceResponse.isValid()) { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.ERROR, k -> new ArrayList<>()) + .add("Configured Source configuration invalid"); + } else { + report.computeIfAbsent(SecDispatcher.ValidationResponse.Level.INFO, k -> new ArrayList<>()) + .add("Configured Source configuration valid"); + valid = true; + } + } + } + return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), valid, report, subsystems); + } + + protected MasterSource getPasswordSource(Map config) throws SecDispatcherException { + String masterSource = config.get(CONF_SOURCE); + if (masterSource == null) { + throw new SecDispatcherException("Invalid configuration: Missing configuration " + CONF_SOURCE); + } + MasterSource source = sources.get(masterSource); + if (source != null) { + return source; + } + throw new SecDispatcherException("No source found the given masterSource: " + masterSource); + } +} From 5f0a83b3db9464970ffcd849f03aa9a1280fdce4 Mon Sep 17 00:00:00 2001 From: Tamas Cservenak Date: Tue, 28 Jan 2025 12:43:19 +0100 Subject: [PATCH 2/2] Add test, fixes --- .../dispatchers/ForwardingDispatcher.java | 58 +++++++++---------- .../internal/DefaultSecDispatcherTest.java | 33 ++++++++++- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java index a65ed79..e9c5fa5 100644 --- a/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java +++ b/src/main/java/org/codehaus/plexus/components/secdispatcher/internal/dispatchers/ForwardingDispatcher.java @@ -13,22 +13,23 @@ package org.codehaus.plexus.components.secdispatcher.internal.dispatchers; -import org.codehaus.plexus.components.secdispatcher.Dispatcher; -import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; -import org.codehaus.plexus.components.secdispatcher.MasterSource; -import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; -import org.codehaus.plexus.components.secdispatcher.SecDispatcher; -import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; - import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.codehaus.plexus.components.secdispatcher.Dispatcher; +import org.codehaus.plexus.components.secdispatcher.DispatcherMeta; +import org.codehaus.plexus.components.secdispatcher.MasterSource; +import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta; +import org.codehaus.plexus.components.secdispatcher.SecDispatcher; +import org.codehaus.plexus.components.secdispatcher.SecDispatcherException; + /** * This dispatcher forwards requests fully to defined sources. */ @@ -58,29 +59,26 @@ public String displayName() { @Override public Collection fields() { - return List.of( - Field.builder(CONF_SOURCE) - .optional(false) - .description("Source of the password") - .options(sources.entrySet().stream() - .map(e -> { - MasterSource ms = e.getValue(); - if (ms instanceof MasterSourceMeta m) { - Field.Builder b = - Field.builder(e.getKey()).description(m.description()); - if (m.configTemplate().isPresent()) { - b.defaultValue(m.configTemplate().get()); - } - return b.build(); - } else { - return Field.builder(e.getKey()) - .description(e.getKey() - + "(Field not described, needs manual configuration)") - .build(); - } - }) - .toList()) - .build()); + return List.of(Field.builder(CONF_SOURCE) + .optional(false) + .description("Source of the password") + .options(sources.entrySet().stream() + .map(e -> { + MasterSource ms = e.getValue(); + if (ms instanceof MasterSourceMeta m) { + Field.Builder b = Field.builder(e.getKey()).description(m.description()); + if (m.configTemplate().isPresent()) { + b.defaultValue(m.configTemplate().get()); + } + return b.build(); + } else { + return Field.builder(e.getKey()) + .description(e.getKey() + "(Field not described, needs manual configuration)") + .build(); + } + }) + .toList()) + .build()); } @Override diff --git a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java index 352e01e..21442fe 100644 --- a/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java +++ b/src/test/java/org/codehaus/plexus/components/secdispatcher/internal/DefaultSecDispatcherTest.java @@ -22,6 +22,7 @@ import org.codehaus.plexus.components.secdispatcher.SecDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.cipher.AESGCMNoPadding; +import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.ForwardingDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.LegacyDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.dispatchers.MasterDispatcher; import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource; @@ -80,6 +81,18 @@ void masterWithSystemPropertyRoundTrip() throws Exception { roundtrip(); } + @Test + void forwardingWithEnvDecrypt() throws Exception { + saveSec("forwarding", Map.of("source", "env")); + decryptForwarding("{[name=forwarding,version=something]env:MASTER_PASSWORD}", "masterPw"); + } + + @Test + void forwardingWithSystemPropertyDecrypt() throws Exception { + saveSec("forwarding", Map.of("source", "system-property")); + decryptForwarding("{[name=forwarding,version=something]system-property:masterPassword}", "masterPw"); + } + @Test void validate() throws Exception { saveSec("master", Map.of("source", "system-property:masterPassword", "cipher", AESGCMNoPadding.CIPHER_ALG)); @@ -157,7 +170,7 @@ void detection() { protected void roundtrip() throws Exception { DefaultSecDispatcher sd = construct(); - assertEquals(2, sd.availableDispatchers().size()); + assertEquals(3, sd.availableDispatchers().size()); String encrypted = sd.encrypt("supersecret", Map.of(SecDispatcher.DISPATCHER_NAME_ATTR, "master", "a", "b")); // example: // {[name=master,cipher=AES/GCM/NoPadding,a=b]vvq66pZ7rkvzSPStGTI9q4QDnsmuDwo+LtjraRel2b0XpcGJFdXcYAHAS75HUA6GLpcVtEkmyQ==} @@ -170,6 +183,14 @@ protected void roundtrip() throws Exception { assertEquals("supersecret", pass); } + protected void decryptForwarding(String encrypted, String decrypted) throws Exception { + DefaultSecDispatcher sd = construct(); + + assertEquals(3, sd.availableDispatchers().size()); + String pass = sd.decrypt(encrypted); + assertEquals(decrypted, pass); + } + protected DefaultSecDispatcher construct() { return new DefaultSecDispatcher( Map.of( @@ -184,7 +205,15 @@ protected DefaultSecDispatcher construct() { GpgAgentMasterSource.NAME, new GpgAgentMasterSource())), "legacy", - new LegacyDispatcher()), + new LegacyDispatcher(), + "forwarding", + new ForwardingDispatcher(Map.of( + EnvMasterSource.NAME, + new EnvMasterSource(), + SystemPropertyMasterSource.NAME, + new SystemPropertyMasterSource(), + GpgAgentMasterSource.NAME, + new GpgAgentMasterSource()))), CONFIG_PATH); }