You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by sm...@apache.org on 2021/07/21 16:00:02 UTC

[knox] branch master updated: KNOX-2637 - Added a new Knox CLI command to generate JWK secrets (#471)

This is an automated email from the ASF dual-hosted git repository.

smolnar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 248d9aa  KNOX-2637 - Added a new Knox CLI command to generate JWK secrets (#471)
248d9aa is described below

commit 248d9aad876ed90d308002d24f1e7cc55111accb
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Wed Jul 21 17:59:52 2021 +0200

    KNOX-2637 - Added a new Knox CLI command to generate JWK secrets (#471)
---
 .../java/org/apache/knox/gateway/util/KnoxCLI.java | 118 ++++++++++++++++-----
 .../org/apache/knox/gateway/util/KnoxCLITest.java  | 107 ++++++++++++++-----
 2 files changed, 175 insertions(+), 50 deletions(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java b/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
index dbf9e76..db0ff0c 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/util/KnoxCLI.java
@@ -17,6 +17,36 @@
  */
 package org.apache.knox.gateway.util;
 
+import java.io.BufferedReader;
+import java.io.Console;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStoreException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.net.ssl.SSLException;
+
 import org.apache.commons.codec.binary.Base64;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
@@ -63,34 +93,15 @@ import org.eclipse.persistence.oxm.MediaType;
 import org.jboss.shrinkwrap.api.exporter.ExplodedExporter;
 import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
 
-import javax.net.ssl.SSLException;
-import java.io.BufferedReader;
-import java.io.Console;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.PrintStream;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyStoreException;
-import java.security.cert.Certificate;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.UUID;
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.OctetSequenceKey;
+import com.nimbusds.jose.jwk.gen.OctetSequenceKeyGenerator;
 
 public class KnoxCLI extends Configured implements Tool {
 
+  private static final Collection<String> SUPPORTED_JWK_ALGORITHMS = Stream
+      .of(JWSAlgorithm.HS256.getName(), JWSAlgorithm.HS384.getName(), JWSAlgorithm.HS512.getName()).collect(Collectors.toSet());
   private static final String USAGE_PREFIX = "KnoxCLI {cmd} [options]";
   private static final String COMMANDS =
       "   [--help]\n" +
@@ -115,7 +126,8 @@ public class KnoxCLI extends Configured implements Tool {
       "   [" + RemoteRegistryDeleteProviderConfigCommand.USAGE + "]\n" +
       "   [" + RemoteRegistryDeleteDescriptorCommand.USAGE + "]\n" +
       "   [" + RemoteRegistryGetACLCommand.USAGE + "]\n" +
-      "   [" + TopologyConverter.USAGE + "]\n";
+      "   [" + TopologyConverter.USAGE + "]\n" +
+      "   [" + JWKGenerator.USAGE  + "]\n";
 
   /** allows stdout to be captured if necessary */
   public PrintStream out = System.out;
@@ -135,6 +147,8 @@ public class KnoxCLI extends Configured implements Tool {
   private String user;
   private String pass;
   private boolean groups;
+  private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
+  private String alias;
 
   private String remoteRegistryClient;
   private String remoteRegistryEntryName;
@@ -479,6 +493,18 @@ public class KnoxCLI extends Configured implements Tool {
           printKnoxShellUsage();
           return -1;
         }
+      } else if (args[i].equalsIgnoreCase("generate-jwk")) {
+        command = new JWKGenerator();
+      } else if (args[i].equalsIgnoreCase("--jwkAlg")) {
+        final String algName = args[++i];
+        if (!SUPPORTED_JWK_ALGORITHMS.contains(algName)) {
+          printKnoxShellUsage();
+          return -1;
+        } else {
+          jwsAlgorithm = JWSAlgorithm.parse(algName);
+        }
+      } else if (args[i].equalsIgnoreCase("--saveAlias")) {
+        alias = args[++i];
       } else {
         printKnoxShellUsage();
         return -1;
@@ -563,6 +589,9 @@ public class KnoxCLI extends Configured implements Tool {
       out.println(TopologyConverter.USAGE + "\n\n" + TopologyConverter.DESC);
       out.println();
       out.println( div );
+      out.println(JWKGenerator.USAGE + "\n\n" + JWKGenerator.DESC);
+      out.println();
+      out.println( div );
     }
   }
 
@@ -2262,6 +2291,43 @@ public class KnoxCLI extends Configured implements Tool {
 
   }
 
+  public class JWKGenerator extends Command {
+
+    public static final String USAGE = "generate-jwk [--jwkAlg HS256|HS384|HS512] [--saveAlias alias] [--topology topology]";
+    public static final String DESC =
+        "Generates a JSON Web Key using the supplied algorithm name and prints the generated key value on the screen. \n"
+            + "As an alternative to displaying this possibly sensitive information on the screen you may want to save it as an alias.\n"
+            + "Options are as follows: \n"
+            + "--jwkAlg (optional) defines the name of the desired JSON Web Signature algorithm name; defaults to HS256. Other accepted values are HS384 and HS512 \n"
+            + "--saveAlias (optional) if this is set, the given alias name is used to save the generated JWK instead of printing it on the screen \n"
+            + "--topology (optional) the name of the topology (aka. cluster) to be used when saving the JWK as an alias. If none specified, the alias is going to be saved for the Gateway \n";
+
+    @Override
+    public String getUsage() {
+      return USAGE + ":\n\n" + DESC;
+    }
+
+    @Override
+    public void execute() throws Exception {
+      final int keyLength = Integer.parseInt(jwsAlgorithm.getName().substring(2));
+      try {
+        final OctetSequenceKey jwk = new OctetSequenceKeyGenerator(keyLength).keyID(UUID.randomUUID().toString()).algorithm(jwsAlgorithm).generate();
+        final String jwkAsText = jwk.getKeyValue().toJSONString().replace("\"", "");
+        if (alias != null) {
+          if (cluster == null) {
+            cluster = "__gateway";
+          }
+          getAliasService().addAliasForCluster(cluster, alias, jwkAsText);
+          out.println(alias + " has been successfully created.");
+        } else {
+          out.println(jwkAsText);
+        }
+      } catch (JOSEException e) {
+        throw new RuntimeException("Error while generating " + keyLength + " bits JWK secret", e);
+      }
+    }
+  }
+
   private static Properties loadBuildProperties() {
     Properties properties = new Properties();
     try(InputStream inputStream = KnoxCLI.class.getClassLoader().getResourceAsStream( "build.properties" )) {
diff --git a/gateway-server/src/test/java/org/apache/knox/gateway/util/KnoxCLITest.java b/gateway-server/src/test/java/org/apache/knox/gateway/util/KnoxCLITest.java
index 8e5995c..674c48c 100644
--- a/gateway-server/src/test/java/org/apache/knox/gateway/util/KnoxCLITest.java
+++ b/gateway-server/src/test/java/org/apache/knox/gateway/util/KnoxCLITest.java
@@ -17,9 +17,28 @@
  */
 package org.apache.knox.gateway.util;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.mycila.xmltool.XMLDoc;
-import com.mycila.xmltool.XMLTag;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.UUID;
+
 import org.apache.commons.io.FileUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
@@ -30,31 +49,15 @@ import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistr
 import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
 import org.apache.knox.gateway.services.security.AliasService;
 import org.apache.knox.gateway.services.security.MasterService;
+import org.apache.knox.gateway.services.security.token.impl.TokenMAC;
 import org.apache.knox.test.TestUtils;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.util.UUID;
-
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.mycila.xmltool.XMLDoc;
+import com.mycila.xmltool.XMLTag;
+import com.nimbusds.jose.JWSAlgorithm;
 
 /**
  * @author larry
@@ -1124,6 +1127,62 @@ public class KnoxCLITest {
     }
   }
 
+  @Test
+  public void testGeneratingJwkInvalidAlgorithm() throws Exception {
+    outContent.reset();
+    final KnoxCLI cli = new KnoxCLI();
+    cli.run(new String[] { "generate-jwk", "--jwkAlg", "HS255", "--master", "master" });
+    // confirm that the output is the help message
+    assertThat(outContent.toString(StandardCharsets.UTF_8.name()), containsString("generate-jwk [--jwkAlg HS256|HS384|HS512]"));
+    outContent.reset();
+  }
+
+  @Test
+  public void testGeneratingJwk256() throws Exception {
+    testGeneratingJWK(JWSAlgorithm.HS256);
+  }
+
+  @Test
+  public void testGeneratingJwk256SavingAsAlias() throws Exception {
+    testGeneratingJWK(JWSAlgorithm.HS256, TokenMAC.KNOX_TOKEN_HASH_KEY_ALIAS_NAME);
+  }
+
+  @Test
+  public void testGeneratingJwk384() throws Exception {
+    testGeneratingJWK(JWSAlgorithm.HS384);
+  }
+
+  @Test
+  public void testGeneratingJwk512() throws Exception {
+    testGeneratingJWK(JWSAlgorithm.HS512);
+  }
+
+  private void testGeneratingJWK(JWSAlgorithm jwkAlgorithm) throws Exception {
+    testGeneratingJWK(jwkAlgorithm, null);
+  }
+
+  private void testGeneratingJWK(JWSAlgorithm jwkAlgorithm, String alias) throws Exception {
+    outContent.reset();
+    final KnoxCLI cli = new KnoxCLI();
+    final String[] args = alias == null ? new String[] { "generate-jwk", "--jwkAlg", jwkAlgorithm.getName(), "--master", "master" }
+        : new String[] { "generate-jwk", "--jwkAlg", jwkAlgorithm.getName(), "--master", "master", "--saveAlias", alias };
+    cli.run(args);
+    final String commandOutput = outContent.toString(StandardCharsets.UTF_8.name());
+    outContent.reset();
+
+    if (alias == null) {
+      // confirm that the output is a generated secret and *NOT* the help message
+      assertThat(commandOutput, not(containsString("generate-jwk [--jwkAlg HS256|HS384|HS512]")));
+    } else {
+      assertThat(commandOutput, containsString(alias + " has been successfully created."));
+
+      final AliasService aliasService = KnoxCLI.getGatewayServices().getService(ServiceType.ALIAS_SERVICE);
+      assertNotNull(new String(aliasService.getPasswordFromAliasForGateway(alias)));
+    }
+
+  }
+
+
   private File createDir() throws IOException {
     return TestUtils
         .createTempDir(this.getClass().getSimpleName() + "-");