From 5f28780e5db55172d04c0d4bb3d5a6b96c698c1c Mon Sep 17 00:00:00 2001 From: Ishan Chattopadhyaya Date: Fri, 7 Jul 2017 15:31:03 +0530 Subject: [PATCH] SOLR-10282: bin/solr support for enabling Kerberos authentication --- solr/CHANGES.txt | 2 + solr/bin/solr | 16 +- .../java/org/apache/solr/util/SolrCLI.java | 202 ++++++++++++++++-- 3 files changed, 200 insertions(+), 20 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index abfe079df93..60c3b98ceec 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -272,6 +272,8 @@ New Features multivalued fields, a new JSON request language, and more. DocValues are now required for any field used in the analytics expression whereas previously docValues was not required. Please see SOLR-10123 for details. (Houston Putman) +* SOLR-10282: bin/solr support for enabling Kerberos authentication (Ishan Chattopadhyaya) + Bug Fixes ---------------------- * SOLR-9262: Connection and read timeouts are being ignored by UpdateShardHandler after SOLR-4509. diff --git a/solr/bin/solr b/solr/bin/solr index c9aad2f2935..3dd0ef8fe11 100755 --- a/solr/bin/solr +++ b/solr/bin/solr @@ -555,20 +555,23 @@ function print_usage() { echo "" echo "Usage: solr auth enable [-type basicAuth] -credentials user:pass [-blockUnknown ] [-updateIncludeFileOnly ]" echo " solr auth enable [-type basicAuth] -prompt [-blockUnknown ] [-updateIncludeFileOnly ]" + echo " solr auth enable -type kerberos -config "" [-updateIncludeFileOnly ]" echo " solr auth disable [-updateIncludeFileOnly ]" echo "" - echo " -type The authentication mechanism to enable. Defaults to 'basicAuth'." + echo " -type The authentication mechanism (basicAuth or kerberos) to enable. Defaults to 'basicAuth'." echo "" - echo " -credentials The username and password of the initial user" + echo " -credentials The username and password of the initial user. Applicable for basicAuth only." echo " Note: only one of -prompt or -credentials must be provided" echo "" - echo " -prompt Prompts the user to provide the credentials" + echo " -config "" Configuration parameters (Solr startup parameters). Required and applicable only for Kerberos" + echo "" + echo " -prompt Prompts the user to provide the credentials. Applicable for basicAuth only." echo " Note: only one of -prompt or -credentials must be provided" echo "" echo " -blockUnknown When true, this blocks out access to unauthenticated users. When not provided," echo " this defaults to false (i.e. unauthenticated users can access all endpoints, except the" echo " operations like collection-edit, security-edit, core-admin-edit etc.). Check the reference" - echo " guide for Basic Authentication for more details." + echo " guide for Basic Authentication for more details. Applicable for basicAuth only." echo "" echo " -updateIncludeFileOnly Only update the solr.in.sh or solr.in.cmd file, and skip actual enabling/disabling" echo " authentication (i.e. don't update security.json)" @@ -1242,6 +1245,11 @@ if [[ "$SCRIPT_CMD" == "auth" ]]; then AUTH_PARAMS=("${AUTH_PARAMS[@]}" "-credentials" "$AUTH_CREDENTIALS") shift 2 ;; + -config) + AUTH_CONFIG="`echo $2| base64`" + AUTH_PARAMS=("${AUTH_PARAMS[@]}" "-config" "$AUTH_CONFIG") + shift 2 + ;; -solrIncludeFile) SOLR_INCLUDE="$2" shift 2 diff --git a/solr/core/src/java/org/apache/solr/util/SolrCLI.java b/solr/core/src/java/org/apache/solr/util/SolrCLI.java index 30c5681330f..657b402bed9 100644 --- a/solr/core/src/java/org/apache/solr/util/SolrCLI.java +++ b/solr/core/src/java/org/apache/solr/util/SolrCLI.java @@ -43,6 +43,7 @@ import java.time.Instant; import java.time.Period; import java.util.ArrayList; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; @@ -115,6 +116,7 @@ import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.StrUtils; import org.apache.solr.security.Sha256AuthenticationProvider; import org.apache.solr.util.configuration.SSLConfigurationsFactory; import org.noggit.CharArr; @@ -3548,7 +3550,7 @@ public class SolrCLI { OptionBuilder .withArgName("type") .hasArg() - .withDescription("The authentication mechanism to enable. Defaults to 'basicAuth'.") + .withDescription("The authentication mechanism to enable (basicAuth or kerberos). Defaults to 'basicAuth'.") .create("type"), OptionBuilder .withArgName("credentials") @@ -3561,6 +3563,11 @@ public class SolrCLI { .withDescription("Prompts the user to provide the credentials. Use either -credentials or -prompt, not both") .create("prompt"), OptionBuilder + .withArgName("config") + .hasArgs() + .withDescription("Configuration parameters (Solr startup parameters). Required for Kerberos authentication") + .create("config"), + OptionBuilder .withArgName("blockUnknown") .withDescription("Blocks all access for unknown users (requires authentication for all endpoints)") .hasArg() @@ -3603,11 +3610,141 @@ public class SolrCLI { } String type = cli.getOptionValue("type", "basicAuth"); - if (type.equalsIgnoreCase("basicAuth") == false) { - System.out.println("Only type=basicAuth supported at the moment."); - exit(1); + switch (type) { + case "basicAuth": + return handleBasicAuth(cli); + case "kerberos": + return handleKerberos(cli); + default: + System.out.println("Only type=basicAuth or kerberos supported at the moment."); + exit(1); + } + return 1; + } + + private int handleKerberos(CommandLine cli) throws Exception { + String cmd = cli.getArgs()[0]; + boolean updateIncludeFileOnly = Boolean.parseBoolean(cli.getOptionValue("updateIncludeFileOnly", "false")); + String securityJson = "{" + + "\n \"authentication\":{" + + "\n \"class\":\"solr.KerberosPlugin\"" + + "\n }" + + "\n}"; + + + switch (cmd) { + case "enable": + String zkHost = null; + boolean zkInaccessible = false; + + if (!updateIncludeFileOnly) { + try { + zkHost = getZkHost(cli); + } catch (Exception ex) { + System.out.println("Unable to access ZooKeeper. Please add the following security.json to ZooKeeper (in case of SolrCloud):\n" + + securityJson + "\n"); + zkInaccessible = true; + } + if (zkHost == null) { + if (zkInaccessible == false) { + System.out.println("Unable to access ZooKeeper. Please add the following security.json to ZooKeeper (in case of SolrCloud):\n" + + securityJson + "\n"); + zkInaccessible = true; + } + } + + // check if security is already enabled or not + if (!zkInaccessible) { + try (SolrZkClient zkClient = new SolrZkClient(zkHost, 10000)) { + if (zkClient.exists("/security.json", true)) { + byte oldSecurityBytes[] = zkClient.getData("/security.json", null, null, true); + if (!"{}".equals(new String(oldSecurityBytes, StandardCharsets.UTF_8).trim())) { + System.out.println("Security is already enabled. You can disable it with 'bin/solr auth disable'. Existing security.json: \n" + + new String(oldSecurityBytes, StandardCharsets.UTF_8)); + exit(1); + } + } + } catch (Exception ex) { + if (zkInaccessible == false) { + System.out.println("Unable to access ZooKeeper. Please add the following security.json to ZooKeeper (in case of SolrCloud):\n" + + securityJson + "\n"); + zkInaccessible = true; + } + } + } + } + + if (!updateIncludeFileOnly) { + if (!zkInaccessible) { + System.out.println("Uploading following security.json: " + securityJson); + try (SolrZkClient zkClient = new SolrZkClient(zkHost, 10000)) { + zkClient.setData("/security.json", securityJson.getBytes(StandardCharsets.UTF_8), true); + } catch (Exception ex) { + if (zkInaccessible == false) { + System.out.println("Unable to access ZooKeeper. Please add the following security.json to ZooKeeper (in case of SolrCloud):\n" + + securityJson); + zkInaccessible = true; + } + } + } + } + + String config = StrUtils.join(Arrays.asList(cli.getOptionValues("config")), ' '); + // config is base64 encoded (to get around parsing problems), decode it + config = config.replaceAll(" ", ""); + config = new String(Base64.getDecoder().decode(config.getBytes("UTF-8")), "UTF-8"); + config = config.replaceAll("\n", "").replaceAll("\r", ""); + + String solrIncludeFilename = cli.getOptionValue("solrIncludeFile"); + File includeFile = new File(solrIncludeFilename); + if (includeFile.exists() == false || includeFile.canWrite() == false) { + System.out.println("Solr include file " + solrIncludeFilename + " doesn't exist or is not writeable."); + printAuthEnablingInstructions(config); + System.exit(0); + } + + // update the solr.in.sh file to contain the necessary authentication lines + updateIncludeFileEnableAuth(includeFile, null, config); + System.out.println("Please restart any running Solr nodes."); + return 0; + + case "disable": + if (!updateIncludeFileOnly) { + zkHost = getZkHost(cli); + if (zkHost == null) { + stdout.print("ZK Host not found. Solr should be running in cloud mode"); + exit(1); + } + + System.out.println("Uploading following security.json: {}"); + + try (SolrZkClient zkClient = new SolrZkClient(zkHost, 10000)) { + zkClient.setData("/security.json", "{}".getBytes(StandardCharsets.UTF_8), true); + } + } + + solrIncludeFilename = cli.getOptionValue("solrIncludeFile"); + includeFile = new File(solrIncludeFilename); + if (!includeFile.exists() || !includeFile.canWrite()) { + System.out.println("Solr include file " + solrIncludeFilename + " doesn't exist or is not writeable."); + System.out.println("Security has been disabled. Please remove any SOLR_AUTH_TYPE or SOLR_AUTHENTICATION_OPTS configuration from solr.in.sh/solr.in.cmd.\n"); + System.exit(0); + } + + // update the solr.in.sh file to comment out the necessary authentication lines + updateIncludeFileDisableAuth(includeFile); + return 0; + + default: + System.out.println("Valid auth commands are: enable, disable"); + exit(1); } + System.out.println("Options not understood."); + new HelpFormatter().printHelp("bin/solr auth [OPTIONS]", getToolOptions(this)); + return 1; + } + private int handleBasicAuth(CommandLine cli) throws Exception { String cmd = cli.getArgs()[0]; boolean prompt = Boolean.parseBoolean(cli.getOptionValue("prompt", "false")); boolean updateIncludeFileOnly = Boolean.parseBoolean(cli.getOptionValue("updateIncludeFileOnly", "false")); @@ -3715,7 +3852,7 @@ public class SolrCLI { "httpBasicAuthUser=" + username + "\nhttpBasicAuthPassword=" + password, StandardCharsets.UTF_8); // update the solr.in.sh file to contain the necessary authentication lines - updateIncludeFileEnableAuth(includeFile, basicAuthConfFile.getAbsolutePath()); + updateIncludeFileEnableAuth(includeFile, basicAuthConfFile.getAbsolutePath(), null); return 0; case "disable": @@ -3754,7 +3891,6 @@ public class SolrCLI { new HelpFormatter().printHelp("bin/solr auth [OPTIONS]", getToolOptions(this)); return 1; } - private void printAuthEnablingInstructions(String username, String password) { if (SystemUtils.IS_OS_WINDOWS) { System.out.println("\nAdd the following lines to the solr.in.cmd file so that the solr.cmd script can use subsequently.\n"); @@ -3766,8 +3902,26 @@ public class SolrCLI { + "SOLR_AUTHENTICATION_OPTS=\"-Dbasicauth=" + username + ":" + password + "\"\n"); } } + private void printAuthEnablingInstructions(String kerberosConfig) { + if (SystemUtils.IS_OS_WINDOWS) { + System.out.println("\nAdd the following lines to the solr.in.cmd file so that the solr.cmd script can use subsequently.\n"); + System.out.println("set SOLR_AUTH_TYPE=kerberos\n" + + "set SOLR_AUTHENTICATION_OPTS=\"" + kerberosConfig + "\"\n"); + } else { + System.out.println("\nAdd the following lines to the solr.in.sh file so that the ./solr script can use subsequently.\n"); + System.out.println("SOLR_AUTH_TYPE=\"kerberos\"\n" + + "SOLR_AUTHENTICATION_OPTS=\"" + kerberosConfig + "\"\n"); + } + } - private void updateIncludeFileEnableAuth(File includeFile, String basicAuthConfFile) throws IOException { + /** + * This will update the include file (e.g. solr.in.sh / solr.in.cmd) with the authentication parameters. + * @param includeFile The include file + * @param basicAuthConfFile If basicAuth, the path of the file containing credentials. If not, null. + * @param kerberosConfig If kerberos, the config string containing startup parameters. If not, null. + */ + private void updateIncludeFileEnableAuth(File includeFile, String basicAuthConfFile, String kerberosConfig) throws IOException { + assert !(basicAuthConfFile != null && kerberosConfig != null); // only one of the two needs to be populated List includeFileLines = FileUtils.readLines(includeFile, StandardCharsets.UTF_8); for (int i=0; i