diff --git a/changelog/unreleased/SOLR-18233-Strengthen-Basic-Authentication-password-policy.yml b/changelog/unreleased/SOLR-18233-Strengthen-Basic-Authentication-password-policy.yml new file mode 100644 index 000000000000..3c06a95dfe6a --- /dev/null +++ b/changelog/unreleased/SOLR-18233-Strengthen-Basic-Authentication-password-policy.yml @@ -0,0 +1,7 @@ +title: Strengthen Basic Authentication password policy and harden template users created by bin/solr auth enable +type: fixed +authors: + - name: Jan Høydahl +links: + - name: SOLR-18233 + url: https://issues.apache.org/jira/browse/SOLR-18233 diff --git a/solr/core/src/java/org/apache/solr/cli/AuthTool.java b/solr/core/src/java/org/apache/solr/cli/AuthTool.java index 5c19ac300d7f..e61b378a4c7e 100644 --- a/solr/core/src/java/org/apache/solr/cli/AuthTool.java +++ b/solr/core/src/java/org/apache/solr/cli/AuthTool.java @@ -227,6 +227,13 @@ private void handleBasicAuth(CommandLine cli) throws Exception { } while (password.isEmpty()); } + if (username.equals(password)) { + CLIO.err( + "Error: username and password must not be identical." + + " This credential would never authenticate."); + runtime.exit(1); + } + boolean blockUnknown = Boolean.parseBoolean(cli.getOptionValue(BLOCK_UNKNOWN_OPTION, "true")); @@ -238,7 +245,7 @@ private void handleBasicAuth(CommandLine cli) throws Exception { ObjectMapper mapper = new ObjectMapper(); JsonNode securityJson1 = mapper.readTree(resource.openStream()); - ((ObjectNode) securityJson1).put("blockUnknown", blockUnknown); + ((ObjectNode) securityJson1.get("authentication")).put("blockUnknown", blockUnknown); JsonNode credentialsNode = securityJson1.get("authentication").get("credentials"); ((ObjectNode) credentialsNode) .put(username, Sha256AuthenticationProvider.getSaltedHashedValue(password)); @@ -286,6 +293,16 @@ private void handleBasicAuth(CommandLine cli) throws Exception { String.format( Locale.ROOT, "Successfully enabled basic auth with username [%s].", username); echo(successMessage); + if (!updateIncludeFileOnly) { + CLIO.out( + "\nIMPORTANT: The following template users have been created with NO password set" + + " and cannot log in until passwords are assigned:"); + CLIO.out(" - admin (roles: admin, index, search)"); + CLIO.out(" - index (roles: index, search)"); + CLIO.out(" - search (roles: search)"); + CLIO.out( + "Set their passwords using the Admin UI Security page or the authentication API."); + } return; } case "disable": diff --git a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java index 31c38537f695..6e1646a5666f 100644 --- a/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java +++ b/solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java @@ -93,6 +93,7 @@ public void init(Map pluginConfig) { @Override public boolean authenticate(String username, String password) { + if (username != null && username.equals(password)) return false; String cred = credentials.get(username); if (cred == null || cred.isEmpty()) return false; cred = cred.trim(); @@ -165,6 +166,10 @@ public Map edit(Map latestConf, List config = new HashMap<>(); + Map credentials = new HashMap<>(); + credentials.put(user, hashedValue); + config.put("credentials", credentials); + + Sha256AuthenticationProvider provider = new Sha256AuthenticationProvider(); + provider.init(config); + assertFalse( + "authenticate() must reject username==password even when hash matches", + provider.authenticate(user, user)); + } + + public void testSetUserRejectsUsernameEqualPassword() { + Sha256AuthenticationProvider provider = new Sha256AuthenticationProvider(); + provider.init(createConfigMap("ignore", "me")); + Map latestConf = createConfigMap("ignore", "me"); + String user = "bob"; + CommandOperation cmd = new CommandOperation("set-user", Map.of(user, user)); + provider.edit(latestConf, List.of(cmd)); + assertTrue("set-user should report an error when username==password", cmd.hasError()); + } + private Map createConfigMap(String user, String pw) { Map config = new HashMap<>(); Map credentials = new HashMap<>(); diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/basic-authentication-plugin.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/basic-authentication-plugin.adoc index 19536088939e..2a6ebf7759fc 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/basic-authentication-plugin.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/basic-authentication-plugin.adoc @@ -185,6 +185,7 @@ If users need to be restricted to a specific collection, that can be done with t === Add a User or Edit a Password The `set-user` command allows you to add users and change their passwords. +Passwords must not be identical to the username. For example, the following defines two users and their passwords: [tabs#set-user] diff --git a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc index d50c3bbdb2fe..c29c306e8f58 100644 --- a/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc +++ b/solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc @@ -881,7 +881,7 @@ The `bin/solr` script allows enabling or disabling Authentication, allowing you Currently this command is only available when using SolrCloud mode and must be run on the machine hosting Solr. -For Basic Authentication the script provides https://github.com/apache/solr/blob/main/solr/core/src/resources/security.json[user roles and permission mappings], and maps the created user to the `superadmin` role. +For Basic Authentication the script provides https://github.com/apache/solr/blob/main/solr/core/src/resources/security.json[user roles and permission mappings], and maps the created user to all roles (`superadmin`, `admin`, `index`, `search`). === Enabling Basic Authentication @@ -893,7 +893,7 @@ For more information on Basic Authentication support specifically, see the secti The `bin/solr auth enable` command makes several changes to enable Basic Authentication: -* Take the base https://github.com/apache/solr/blob/main/solr/core/resources/security.json[security.json] file, evolves it using `auth` command parameters, and uploads the new file to ZooKeeper. +* Takes the base https://github.com/apache/solr/blob/main/solr/core/src/resources/security.json[security.json] file, applies `auth` command parameters, and uploads the new file to ZooKeeper. + * Adds two lines to `bin/solr.in.sh` or `bin\solr.in.cmd` to set the authentication type, and the path to `basicAuth.conf`: + @@ -905,6 +905,19 @@ SOLR_AUTHENTICATION_OPTS="-Dsolr.httpclient.config=/path/to/solr-{solr-full-vers ---- * Creates the file `server/solr/basicAuth.conf` to store the credential information that is used with `bin/solr` commands. +In addition to the operator-created user, the command also creates three template users with predefined role assignments. +These users have no password set and cannot log in until passwords are explicitly assigned: + +[cols="1,2",options="header"] +|=== +|Username |Roles +|`admin` |admin, index, search +|`index` |index, search +|`search` |search +|=== + +After enabling Basic Authentication, set passwords for these template users using the Admin UI Security page or the xref:basic-authentication-plugin.adoc#add-a-user-or-edit-a-password[authentication API]. + Here are some example usages: [source,plain] diff --git a/solr/webapp/web/js/angular/controllers/security.js b/solr/webapp/web/js/angular/controllers/security.js index fd65a289988f..1ffdf85460a0 100644 --- a/solr/webapp/web/js/angular/controllers/security.js +++ b/solr/webapp/web/js/angular/controllers/security.js @@ -412,6 +412,12 @@ solrAdminApp.controller('SecurityController', function ($scope, $timeout, $cooki return false; } + var username = $scope.upsertUser.username ? $scope.upsertUser.username.trim() : ""; + if (password === username) { + $scope.validationError = "Password must not be the same as the username"; + return false; + } + return true; };