Skip to content

Commit bc5a2eb

Browse files
authored
SOLR-18233 Strengthen Basic Authentication password policy and harden template users created by bin/solr auth enable (#4477)
1 parent 6a7d766 commit bc5a2eb

8 files changed

Lines changed: 100 additions & 17 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
title: Strengthen Basic Authentication password policy and harden template users created by bin/solr auth enable
2+
type: fixed
3+
authors:
4+
- name: Jan Høydahl
5+
links:
6+
- name: SOLR-18233
7+
url: https://issues.apache.org/jira/browse/SOLR-18233

solr/core/src/java/org/apache/solr/cli/AuthTool.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,12 @@ private void handleBasicAuth(CommandLine cli) throws Exception {
227227
} while (password.isEmpty());
228228
}
229229

230-
boolean blockUnknown =
231-
Boolean.parseBoolean(cli.getOptionValue(BLOCK_UNKNOWN_OPTION, "true"));
230+
if (username.equals(password)) {
231+
CLIO.err(
232+
"Error: username and password must not be identical."
233+
+ " This credential would never authenticate.");
234+
runtime.exit(1);
235+
}
232236

233237
String resourceName = "security.json";
234238
final URL resource = SolrCore.class.getClassLoader().getResource(resourceName);
@@ -238,7 +242,11 @@ private void handleBasicAuth(CommandLine cli) throws Exception {
238242

239243
ObjectMapper mapper = new ObjectMapper();
240244
JsonNode securityJson1 = mapper.readTree(resource.openStream());
241-
((ObjectNode) securityJson1).put("blockUnknown", blockUnknown);
245+
// Only override blockUnknown if explicitly passed; otherwise let the template decide
246+
if (cli.hasOption(BLOCK_UNKNOWN_OPTION)) {
247+
boolean blockUnknown = Boolean.parseBoolean(cli.getOptionValue(BLOCK_UNKNOWN_OPTION));
248+
((ObjectNode) securityJson1.get("authentication")).put("blockUnknown", blockUnknown);
249+
}
242250
JsonNode credentialsNode = securityJson1.get("authentication").get("credentials");
243251
((ObjectNode) credentialsNode)
244252
.put(username, Sha256AuthenticationProvider.getSaltedHashedValue(password));
@@ -286,6 +294,16 @@ private void handleBasicAuth(CommandLine cli) throws Exception {
286294
String.format(
287295
Locale.ROOT, "Successfully enabled basic auth with username [%s].", username);
288296
echo(successMessage);
297+
if (!updateIncludeFileOnly) {
298+
CLIO.out(
299+
"\nIMPORTANT: The following template users have been created with NO password set"
300+
+ " and cannot log in until passwords are assigned:");
301+
CLIO.out(" - admin (roles: admin, index, search)");
302+
CLIO.out(" - index (roles: index, search)");
303+
CLIO.out(" - search (roles: search)");
304+
CLIO.out(
305+
"Set their passwords using the Admin UI Security page or the authentication API.");
306+
}
289307
return;
290308
}
291309
case "disable":

solr/core/src/java/org/apache/solr/security/Sha256AuthenticationProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public void init(Map<String, Object> pluginConfig) {
9393

9494
@Override
9595
public boolean authenticate(String username, String password) {
96+
if (username != null && username.equals(password)) return false;
9697
String cred = credentials.get(username);
9798
if (cred == null || cred.isEmpty()) return false;
9899
cred = cred.trim();
@@ -165,6 +166,10 @@ public Map<String, Object> edit(Map<String, Object> latestConf, List<CommandOper
165166
cmd.addError("name and password must be non-null");
166167
return null;
167168
}
169+
if (e.getKey().equals(String.valueOf(e.getValue()))) {
170+
cmd.addError("Password must not be the same as the username");
171+
return null;
172+
}
168173
putUser(e.getKey(), String.valueOf(e.getValue()), map);
169174
}
170175
}

solr/core/src/resources/security.json

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
"blockUnknown": false,
44
"class": "solr.BasicAuthPlugin",
55
"credentials": {
6-
"search": "9ch2qWOmNSeGpfcgLRXafhm5z3KeRti5qCNLn7SmK1I= aXNjZWd4YW9mMzZ0cjE1Nw==",
7-
"index": "of9xlSadImtR0MH4obzJvKSZkuE5DIJh5NOui2hWDeA= dTRuYzU4Y3F4N2hxd2sxeA==",
8-
"admin": "6clS8rTEj1x1LP/uRCxOZsLdps7Sovokru09WdJX+7A= NGMyZGFhN2lrNHFsdXZybA==",
9-
"superadmin": "9wzPajmLBIIi8BmToy8lxveDxfL6Vl/BX/Ss3xrs3XQ= OWZna2hwendocXFnODU5ZQ=="
6+
"search": "",
7+
"index": "",
8+
"admin": ""
109
}
1110
},
1211
"authorization": {
@@ -67,8 +66,7 @@
6766
"user-role": {
6867
"search": ["search"],
6968
"index": ["index", "search"],
70-
"admin": ["admin", "index", "search"],
71-
"superadmin": ["superadmin", "admin", "index", "search"]
69+
"admin": ["admin", "index", "search"]
7270
}
7371
}
7472
}

solr/core/src/test/org/apache/solr/security/TestSha256AuthenticationProvider.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,33 @@ public void testBasicAuthDeleteFinalUser() throws IOException {
104104
}
105105
}
106106

107+
public void testAuthenticateRejectsUsernameEqualPassword() {
108+
// Simulate a credential store that has the username's own hash as the password
109+
// (e.g. set up before this policy was in effect) and verify authenticate() still rejects it.
110+
String user = "alice";
111+
String hashedValue = Sha256AuthenticationProvider.getSaltedHashedValue(user);
112+
Map<String, Object> config = new HashMap<>();
113+
Map<String, String> credentials = new HashMap<>();
114+
credentials.put(user, hashedValue);
115+
config.put("credentials", credentials);
116+
117+
Sha256AuthenticationProvider provider = new Sha256AuthenticationProvider();
118+
provider.init(config);
119+
assertFalse(
120+
"authenticate() must reject username==password even when hash matches",
121+
provider.authenticate(user, user));
122+
}
123+
124+
public void testSetUserRejectsUsernameEqualPassword() {
125+
Sha256AuthenticationProvider provider = new Sha256AuthenticationProvider();
126+
provider.init(createConfigMap("ignore", "me"));
127+
Map<String, Object> latestConf = createConfigMap("ignore", "me");
128+
String user = "bob";
129+
CommandOperation cmd = new CommandOperation("set-user", Map.of(user, user));
130+
provider.edit(latestConf, List.of(cmd));
131+
assertTrue("set-user should report an error when username==password", cmd.hasError());
132+
}
133+
107134
private Map<String, Object> createConfigMap(String user, String pw) {
108135
Map<String, Object> config = new HashMap<>();
109136
Map<String, String> credentials = new HashMap<>();

solr/solr-ref-guide/modules/deployment-guide/pages/basic-authentication-plugin.adoc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ To control user permissions, you may need to configure an authorization plugin a
2424

2525
== Enable Basic Authentication
2626

27-
To use Basic authentication, you must first create a `security.json` file.
28-
This file and where to put it is described in detail in the section xref:authentication-and-authorization-plugins.adoc#configuring-security-json[Configuring security.json].
27+
When running in cloud mode, Basic authentication can be enabled from the command line using the `bin/solr auth enable` command, which applies a best-practice security template with pre-configured roles and permissions.
28+
See xref:solr-control-script-reference.adoc#enabling-basic-authentication[Enabling Basic Authentication] in the Solr Control Script Reference for details.
2929

30-
If running in cloud mode, you can use the `bin/solr auth` command-line utility to enable security for a new installation, see: `bin/solr auth --help` for more details.
30+
Alternatively, you can create the `security.json` file manually.
31+
This file and where to put it is described in detail in the section xref:authentication-and-authorization-plugins.adoc#configuring-security-json[Configuring security.json].
3132

3233
For Basic authentication, `security.json` must have an `authentication` block which defines the class being used for authentication.
3334
Usernames and passwords could be added when the file is created, or can be added later with the Authentication API, described below.
@@ -185,6 +186,7 @@ If users need to be restricted to a specific collection, that can be done with t
185186
=== Add a User or Edit a Password
186187

187188
The `set-user` command allows you to add users and change their passwords.
189+
Passwords must not be identical to the username.
188190
For example, the following defines two users and their passwords:
189191

190192
[tabs#set-user]

solr/solr-ref-guide/modules/deployment-guide/pages/solr-control-script-reference.adoc

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -916,19 +916,23 @@ The `bin/solr` script allows enabling or disabling Authentication, allowing you
916916

917917
Currently this command is only available when using SolrCloud mode and must be run on the machine hosting Solr.
918918

919-
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.
919+
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`).
920920

921921

922922
=== Enabling Basic Authentication
923923

924924
The command `bin/solr auth enable` configures Solr to use Basic Authentication when accessing the User Interface, using `bin/solr` and any API requests.
925925

926+
NOTE: This command currently requires SolrCloud mode — it uploads the generated `security.json` to ZooKeeper so that all nodes pick it up automatically.
927+
For user-managed (standalone) clusters, you must create the `security.json` file manually and place it in each node's Solr home directory.
928+
See xref:basic-authentication-plugin.adoc[] for details.
929+
926930
TIP: For more information about Solr's authentication plugins, see the section xref:securing-solr.adoc[].
927931
For more information on Basic Authentication support specifically, see the section xref:basic-authentication-plugin.adoc[].
928932

929933
The `bin/solr auth enable` command makes several changes to enable Basic Authentication:
930934

931-
* 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.
935+
* Takes the base https://github.com/apache/solr/blob/main/solr/core/src/resources/security.json[security.json] template with best-practice roles and permissions, applies `auth` command parameters, and uploads the result to ZooKeeper.
932936
+
933937
* Adds two lines to `bin/solr.in.sh` or `bin\solr.in.cmd` to set the authentication type, and the path to `basicAuth.conf`:
934938
+
@@ -940,6 +944,19 @@ SOLR_AUTHENTICATION_OPTS="-Dsolr.httpclient.config=/path/to/solr-{solr-full-vers
940944
----
941945
* Creates the file `server/solr/basicAuth.conf` to store the credential information that is used with `bin/solr` commands.
942946

947+
In addition to the operator-created user, the command also creates three template users with predefined role assignments.
948+
These users have no password set and cannot log in until passwords are explicitly assigned:
949+
950+
[cols="1,2",options="header"]
951+
|===
952+
|Username |Roles
953+
|`admin` |admin, index, search
954+
|`index` |index, search
955+
|`search` |search
956+
|===
957+
958+
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].
959+
943960
Here are some example usages:
944961

945962
[source,plain]
@@ -981,11 +998,14 @@ Either `--credentials` or `--prompt` *must* be specified.
981998
+
982999
[%autowidth,frame=none]
9831000
|===
984-
|Optional |Default: `true`
1001+
|Optional |Default: use value from `security.json` template (`false`)
9851002
|===
9861003
+
987-
When `true`, this blocks out access to unauthenticated users from accessing Solr.
988-
When `false`, unauthenticated users will still be able to access Solr, but only for operations not explicitly requiring a user role in the Authorization plugin configuration.
1004+
Controls whether unauthenticated requests are blocked.
1005+
The default `security.json` template sets `blockUnknown` to `false` because it includes a `RuleBasedAuthorizationPlugin` with fine-grained permissions — unauthenticated users can only access endpoints explicitly granted to the `null` role (by default *health* and *metrics-read*, left open so that load balancers and monitoring tools can operate without credentials).
1006+
All other operations require an authenticated user with the appropriate role.
1007+
+
1008+
If you want to require authentication for _all_ requests (including health checks and metrics), pass `--block-unknown true` explicitly.
9891009

9901010
`--solr-include-file <includeFilePath>`::
9911011
+

solr/webapp/web/js/angular/controllers/security.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,12 @@ solrAdminApp.controller('SecurityController', function ($scope, $timeout, $cooki
412412
return false;
413413
}
414414

415+
var username = $scope.upsertUser.username ? $scope.upsertUser.username.trim() : "";
416+
if (password === username) {
417+
$scope.validationError = "Password must not be the same as the username";
418+
return false;
419+
}
420+
415421
return true;
416422
};
417423

0 commit comments

Comments
 (0)