Skip to content

Commit

Permalink
Don't require config for the forwarding dispatcher
Browse files Browse the repository at this point in the history
Allow to use arbitrary master sources in a mix & match fashion

Some refactoring
  • Loading branch information
kwin committed Feb 2, 2025
1 parent 5f0a83b commit 40f56a5
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 161 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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 javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

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.SecDispatcher;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher.ValidationResponse.Level;
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;

/**
* This dispatcher does not actually perform any crypto operations, but just forwards the string to be decrypted
* to a {@link MasterSource}. The given string is supposed to contain a valid source reference which is resolvable
* by one of the bound {@link MasterSource} implementations (and not actually an encrypted value).
* This dispatcher doesn't support encryption, but just validates and returns the given master source reference.
*/
@Singleton
@Named(MasterSourceLookupDispatcher.NAME)
public class MasterSourceLookupDispatcher implements Dispatcher, DispatcherMeta {
public static final String NAME = "masterSourceLookup";

protected final Collection<MasterSource> sources;

@Inject
public MasterSourceLookupDispatcher(Collection<MasterSource> sources) {
this.sources = sources;
}

@Override
public String name() {
return NAME;
}

@Override
public String displayName() {
return "Master Source Lookup Dispatcher";
}

@Override
public Collection<Field> fields() {
return Collections.emptyList();
}

@Override
public EncryptPayload encrypt(String str, Map<String, String> attributes, Map<String, String> config)
throws SecDispatcherException {
// just make sure the given string is a valid reference!
decrypt(str, attributes, config);
return new EncryptPayload(attributes, str);
}

@Override
public String decrypt(String str, Map<String, String> attributes, Map<String, String> config)
throws SecDispatcherException {
Optional<String> plain = sources.stream()
.map(source -> source.handle(str))
.filter(Objects::nonNull)
.findFirst();
if (plain.isPresent()) {
return plain.get();
} else {
throw new SecDispatcherException("No master source found for : " + str);
}
}

@Override
public SecDispatcher.ValidationResponse validateConfiguration(Map<String, String> config) {
// there is nothing really to validate without having a master reference at hand (which is outside the config)
Map<Level, List<String>> report = Collections.singletonMap(
SecDispatcher.ValidationResponse.Level.INFO, List.of("Configured Source configuration valid"));
return new SecDispatcher.ValidationResponse(getClass().getSimpleName(), true, report, Collections.emptyList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;

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.dispatchers.MasterSourceLookupDispatcher;
import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource;
import org.codehaus.plexus.components.secdispatcher.internal.sources.GpgAgentMasterSource;
import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource;
Expand Down Expand Up @@ -82,15 +84,15 @@ void masterWithSystemPropertyRoundTrip() throws Exception {
}

@Test
void forwardingWithEnvDecrypt() throws Exception {
saveSec("forwarding", Map.of("source", "env"));
decryptForwarding("{[name=forwarding,version=something]env:MASTER_PASSWORD}", "masterPw");
void masterSourceLookupWithEnvDecrypt() throws Exception {
saveSec("masterSourceLookup", Collections.emptyMap());
assertDecrypted("{[name=masterSourceLookup,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");
void masterSourceLookupWithSystemPropertyDecrypt() throws Exception {
saveSec("masterSourceLookup", Collections.emptyMap());
assertDecrypted("{[name=masterSourceLookup,version=something]system-property:masterPassword}", "masterPw");
}

@Test
Expand Down Expand Up @@ -183,12 +185,12 @@ protected void roundtrip() throws Exception {
assertEquals("supersecret", pass);
}

protected void decryptForwarding(String encrypted, String decrypted) throws Exception {
protected void assertDecrypted(String encrypted, String expectedPlainText) throws Exception {
DefaultSecDispatcher sd = construct();

assertEquals(3, sd.availableDispatchers().size());
String pass = sd.decrypt(encrypted);
assertEquals(decrypted, pass);
String plainText = sd.decrypt(encrypted);
assertEquals(expectedPlainText, plainText);
}

protected DefaultSecDispatcher construct() {
Expand All @@ -206,14 +208,9 @@ protected DefaultSecDispatcher construct() {
new GpgAgentMasterSource())),
"legacy",
new LegacyDispatcher(),
"forwarding",
new ForwardingDispatcher(Map.of(
EnvMasterSource.NAME,
new EnvMasterSource(),
SystemPropertyMasterSource.NAME,
new SystemPropertyMasterSource(),
GpgAgentMasterSource.NAME,
new GpgAgentMasterSource()))),
"masterSourceLookup",
new MasterSourceLookupDispatcher(List.of(
new EnvMasterSource(), new SystemPropertyMasterSource(), new GpgAgentMasterSource()))),
CONFIG_PATH);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 java.util.Collections;
import java.util.Map;

import org.codehaus.plexus.components.secdispatcher.Dispatcher.EncryptPayload;
import org.codehaus.plexus.components.secdispatcher.SecDispatcher.ValidationResponse;
import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
import org.codehaus.plexus.components.secdispatcher.internal.sources.EnvMasterSource;
import org.codehaus.plexus.components.secdispatcher.internal.sources.SystemPropertyMasterSource;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class MasterSourceLookupDispatcherTest {

@Test
void testUnknownPrefix() {
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
new MasterSourceLookupDispatcher(Collections.singleton(new EnvMasterSource()));
assertThrows(
SecDispatcherException.class,
() -> masterSourceLookupDispatcher.decrypt("unknown-prefix:test", Map.of(), Map.of()));
assertThrows(
SecDispatcherException.class,
() -> masterSourceLookupDispatcher.encrypt("unknown-prefix:test", Map.of(), Map.of()));
}

@Test
void testSystemPropertyMasterSourceDecrypt() {
System.setProperty("myprop", "plaintext");
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
// SecDispatcher "un decorates" the PW
String cleartext = masterSourceLookupDispatcher.decrypt("system-property:myprop", Map.of(), Map.of());
assertEquals("plaintext", cleartext);
}

@Test
void testEncrypt() {
System.setProperty("myprop", "plaintext");
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
// SecDispatcher "un decorates" the PW
EncryptPayload payload = masterSourceLookupDispatcher.encrypt("system-property:myprop", Map.of(), Map.of());
assertEquals("system-property:myprop", payload.getEncrypted());
}

@Test
void testValidateConfiguration() {
MasterSourceLookupDispatcher masterSourceLookupDispatcher =
new MasterSourceLookupDispatcher(Collections.singleton(new SystemPropertyMasterSource()));
ValidationResponse response = masterSourceLookupDispatcher.validateConfiguration(Collections.emptyMap());
assertTrue(response.isValid());
}
}

0 comments on commit 40f56a5

Please sign in to comment.