diff --git a/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ConcordConfiguration.java b/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ConcordConfiguration.java index b476732529..b20a535f4e 100644 --- a/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ConcordConfiguration.java +++ b/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ConcordConfiguration.java @@ -27,6 +27,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.util.Base64; public final class ConcordConfiguration { @@ -44,6 +47,8 @@ public static Path sharedDir() { throw new RuntimeException(e); } } + + writePrivateKey(sharedDir.resolve("signing.pem")); } public static ConcordRule configure() { @@ -66,6 +71,9 @@ public static ConcordRule configure() { pollDelay = "250 milliseconds" } } + process { + signingKeyPath = "%%sharedDir%%/signing.pem" + } } concord-agent { dependencyResolveTimeout = "30 seconds" @@ -75,7 +83,7 @@ public static ConcordRule configure() { enabled = true } } - """); + """.replaceAll("%%sharedDir%%", sharedDir().toString())); boolean localMode = Boolean.parseBoolean(System.getProperty("it.local.mode")); if (localMode) { @@ -106,6 +114,23 @@ public static String getServerUrlForAgent(ConcordRule concord) { } } + private static Path writePrivateKey(Path targetFile) { + try { + return Files.writeString(targetFile, generatePkcs8PemPrivateKey()); + } catch (Exception e) { + throw new IllegalStateException("Error writing server username signing key.", e); + } + } + + private static String generatePkcs8PemPrivateKey() throws Exception { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(2048); + KeyPair pair = keyGen.generateKeyPair(); + byte[] pkcs8 = pair.getPrivate().getEncoded(); + String base64 = Base64.getMimeEncoder(64, "\n".getBytes()).encodeToString(pkcs8); + return "-----BEGIN PRIVATE KEY-----\n" + base64 + "\n-----END PRIVATE KEY-----"; + } + private ConcordConfiguration() { } } diff --git a/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ProcessIT.java b/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ProcessIT.java index bc25cef507..455fc72cb1 100644 --- a/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ProcessIT.java +++ b/it/runtime-v2/src/test/java/com/walmartlabs/concord/it/runtime/v2/ProcessIT.java @@ -61,6 +61,23 @@ public void testArgs() throws Exception { proc.assertLog(".*Hello, Concord!.*"); } + /** + * Username signature generation. + */ + @Test + public void testUsernameSignature() throws Exception { + Payload payload = new Payload() + .archive(resource("usernameSignature")); + + ConcordProcess proc = concord.processes().start(payload); + expectStatus(proc, ProcessEntry.StatusEnum.FINISHED); + + // --- + + proc.assertNoLog(".*signature: null.*"); + proc.assertLog(".*signature: (?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\\..*"); + } + /** * Groovy script execution. */ diff --git a/it/runtime-v2/src/test/resources/com/walmartlabs/concord/it/runtime/v2/usernameSignature/concord.yml b/it/runtime-v2/src/test/resources/com/walmartlabs/concord/it/runtime/v2/usernameSignature/concord.yml new file mode 100644 index 0000000000..cff53e64bc --- /dev/null +++ b/it/runtime-v2/src/test/resources/com/walmartlabs/concord/it/runtime/v2/usernameSignature/concord.yml @@ -0,0 +1,6 @@ +configuration: + runtime: "concord-v2" + +flows: + default: + - log: "signature: ${initiator.usernameSignature == null ? 'null' : initiator.usernameSignature}." diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/process/pipelines/processors/UserInfoProcessor.java b/server/impl/src/main/java/com/walmartlabs/concord/server/process/pipelines/processors/UserInfoProcessor.java index 28233564ad..8819e03fe0 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/process/pipelines/processors/UserInfoProcessor.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/process/pipelines/processors/UserInfoProcessor.java @@ -21,8 +21,6 @@ */ import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; import com.walmartlabs.concord.server.process.Payload; import com.walmartlabs.concord.server.process.pipelines.processors.signing.Signing; import com.walmartlabs.concord.server.sdk.ConcordApplicationException; @@ -60,11 +58,14 @@ public UserInfoProcessor(String key, public Payload process(Chain chain, Payload payload) { var info = userManager.getCurrentUserInfo(); - var result = objectMapper.convertValue(info, ObjectNode.class); if (signing.isEnabled()) { - Optional.ofNullable(info.username()) - .map(this::sign) - .ifPresent(signature -> result.set("usernameSignature", signature)); + var signature = Optional.ofNullable(info.username()) + .map(this::sign); + + info = signature.isEmpty() ? info : UserInfoProvider.UserInfo.builder() + .from(info) + .usernameSignature(signature.get()) + .build(); } Map m = new HashMap<>(); @@ -75,9 +76,9 @@ public Payload process(Chain chain, Payload payload) { return chain.process(payload); } - private TextNode sign(String username) { + private String sign(String username) { try { - return TextNode.valueOf(signing.sign(username)); + return signing.sign(username); } catch (Exception e) { throw new ConcordApplicationException("Error while singing process data: " + e.getMessage(), e); } diff --git a/server/impl/src/main/java/com/walmartlabs/concord/server/user/UserInfoProvider.java b/server/impl/src/main/java/com/walmartlabs/concord/server/user/UserInfoProvider.java index ff0b279f7d..f0a954ba1b 100644 --- a/server/impl/src/main/java/com/walmartlabs/concord/server/user/UserInfoProvider.java +++ b/server/impl/src/main/java/com/walmartlabs/concord/server/user/UserInfoProvider.java @@ -77,5 +77,8 @@ static ImmutableUserInfo.Builder builder() { @Nullable Map attributes(); + + @Nullable + String usernameSignature(); } }