Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions solr/bin/solr
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ function print_usage() {
echo ""
echo " -e/--example <name> Name of the example to run; available examples:"
echo " cloud: SolrCloud example"
echo " secure_solr: Single-node SolrCloud with Basic Authentication and techproducts data"
echo " techproducts: Comprehensive example illustrating many of Solr's core capabilities"
echo " schemaless: Schema-less example (schema is inferred from data during indexing)"
echo " films: Example of starting with _default configset and adding explicit fields dynamically"
Expand Down Expand Up @@ -598,6 +599,17 @@ else
exit
fi

if [[ "$SCRIPT_CMD" == "ssl" ]]; then
SSL_INCLUDE_ARGS=(--solr-include-file "${SOLR_INCLUDE:-$SOLR_TIP/bin/solr.in.sh}")
for arg in "$@"; do
case "$arg" in
--solr-include-file|--solr-include-file=*) SSL_INCLUDE_ARGS=(); break ;;
esac
done
run_tool ssl "${SSL_INCLUDE_ARGS[@]}" "$@"
exit $?
fi

# configure authentication
if [[ "$SCRIPT_CMD" == "auth" ]]; then
: "${SOLR_SERVER_DIR:=$DEFAULT_SERVER_DIR}"
Expand Down
1 change: 1 addition & 0 deletions solr/bin/solr.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ goto err
@echo.
@echo -e/--example name Name of the example to run; available examples:
@echo cloud: SolrCloud example
@echo secure_solr: Single-node SolrCloud with Basic Authentication and techproducts data
@echo techproducts: Comprehensive example illustrating many of Solr's core capabilities
@echo schemaless: Schema-less example (schema is inferred from data during indexing)
@echo films: Example of starting with _default configset and defining explicit fields dynamically
Expand Down
99 changes: 29 additions & 70 deletions solr/core/src/java/org/apache/solr/cli/AuthTool.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,11 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
Expand Down Expand Up @@ -230,9 +228,8 @@ public String getHeader() {
List of options:""";
}

List<String> authenticationVariables =
Arrays.asList(
"SOLR_AUTHENTICATION_CLIENT_BUILDER", "SOLR_AUTH_TYPE", "SOLR_AUTHENTICATION_OPTS");
private static final Set<String> AUTHENTICATION_VARIABLES =
Set.of("SOLR_AUTHENTICATION_CLIENT_BUILDER", "SOLR_AUTH_TYPE", "SOLR_AUTHENTICATION_OPTS");

@Override
public Options getOptions() {
Expand Down Expand Up @@ -361,10 +358,13 @@ private void enableBasicAuth(AuthParams params) throws Exception {
runtime.exit(0);
}

Files.writeString(
if (Files.exists(basicAuthConfFile)) {
echo("Backup: " + IncludeFileEditor.backup(basicAuthConfFile));
}
IncludeFileEditor.writePrivate(
basicAuthConfFile,
"httpBasicAuthUser=" + username + "\nhttpBasicAuthPassword=" + password,
StandardCharsets.UTF_8);
("httpBasicAuthUser=" + username + "\nhttpBasicAuthPassword=" + password)
.getBytes(StandardCharsets.UTF_8));

// update the solr.in.sh file to contain the necessary authentication lines
updateIncludeFileEnableAuth(includeFile, basicAuthConfFile);
Expand Down Expand Up @@ -474,73 +474,32 @@ private void printAuthEnablingInstructions(String username, String password) {
}
}

/**
* 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.
*/
private void updateIncludeFileEnableAuth(Path includeFile, Path basicAuthConfFile)
throws IOException {
List<String> includeFileLines = Files.readAllLines(includeFile, StandardCharsets.UTF_8);
for (int i = 0; i < includeFileLines.size(); i++) {
String line = includeFileLines.get(i);
if (authenticationVariables.contains(line.trim().split("=")[0].trim())) { // Non-Windows
includeFileLines.set(i, "# " + line);
}
if (line.trim().split("=")[0].trim().startsWith("set ")
&& authenticationVariables.contains(
line.trim().split("=")[0].trim().substring(4))) { // Windows
includeFileLines.set(i, "REM " + line);
}
}
includeFileLines.add(""); // blank line

if (basicAuthConfFile != null) { // for basicAuth
if (Constants.WINDOWS) {
includeFileLines.add("REM The following lines added by solr.cmd for enabling BasicAuth");
includeFileLines.add("set SOLR_AUTH_TYPE=basic");
includeFileLines.add(
"set SOLR_AUTHENTICATION_OPTS=\"-Dsolr.httpclient.config=" + basicAuthConfFile + "\"");
} else {
includeFileLines.add("# The following lines added by ./solr for enabling BasicAuth");
includeFileLines.add("SOLR_AUTH_TYPE=\"basic\"");
includeFileLines.add(
"SOLR_AUTHENTICATION_OPTS=\"-Dsolr.httpclient.config=" + basicAuthConfFile + "\"");
}
}

String lines = includeFileLines.stream().collect(Collectors.joining(System.lineSeparator()));
Files.writeString(includeFile, lines, StandardCharsets.UTF_8);

if (basicAuthConfFile != null) {
echoIfVerbose("Written out credentials file: " + basicAuthConfFile);
}
var format =
Constants.WINDOWS ? IncludeFileEditor.Format.WINDOWS : IncludeFileEditor.Format.SHELL;
Map<String, String> settings = new LinkedHashMap<>();
settings.put("SOLR_AUTH_TYPE", "basic");
String authenticationOptions = "-Dsolr.httpclient.config=" + basicAuthConfFile;
settings.put(
"SOLR_AUTHENTICATION_OPTS",
Constants.WINDOWS ? "\"" + authenticationOptions + "\"" : authenticationOptions);
String updated =
IncludeFileEditor.update(
Files.readString(includeFile), settings, AUTHENTICATION_VARIABLES, format);
echo("Backup: " + IncludeFileEditor.save(includeFile, updated));
echoIfVerbose("Written out credentials file: " + basicAuthConfFile);
echoIfVerbose("Updated Solr include file: " + includeFile.toAbsolutePath());
}

private void updateIncludeFileDisableAuth(Path includeFile) throws IOException {
List<String> includeFileLines = Files.readAllLines(includeFile, StandardCharsets.UTF_8);
boolean hasChanged = false;
for (int i = 0; i < includeFileLines.size(); i++) {
String line = includeFileLines.get(i);
if (authenticationVariables.contains(line.trim().split("=")[0].trim())) { // Non-Windows
includeFileLines.set(i, "# " + line);
hasChanged = true;
}
if (line.trim().split("=")[0].trim().startsWith("set ")
&& authenticationVariables.contains(
line.trim().split("=")[0].trim().substring(4))) { // Windows
includeFileLines.set(i, "REM " + line);
hasChanged = true;
}
}
if (hasChanged) {
String lines = includeFileLines.stream().collect(Collectors.joining(System.lineSeparator()));
Files.writeString(includeFile, lines, StandardCharsets.UTF_8);
echoIfVerbose("Commented out necessary lines from " + includeFile.toAbsolutePath());
var format =
Constants.WINDOWS ? IncludeFileEditor.Format.WINDOWS : IncludeFileEditor.Format.SHELL;
String original = Files.readString(includeFile);
String updated = IncludeFileEditor.disable(original, AUTHENTICATION_VARIABLES, format);
if (!original.equals(updated)) {
echo("Backup: " + IncludeFileEditor.save(includeFile, updated));
echoIfVerbose("Commented out authentication settings in " + includeFile.toAbsolutePath());
}
}

Expand Down
146 changes: 146 additions & 0 deletions solr/core/src/java/org/apache/solr/cli/IncludeFileEditor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.solr.cli;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/** Edits Solr shell and Windows include files without changing unrelated settings. */
final class IncludeFileEditor {
enum Format {
SHELL(Pattern.compile("\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=.*"), "# "),
WINDOWS(Pattern.compile("\\s*(?i:set)\\s+\"?([A-Za-z_][A-Za-z0-9_]*)\\s*=.*"), "REM ");

private final Pattern assignment;
private final String comment;

Format(Pattern assignment, String comment) {
this.assignment = assignment;
this.comment = comment;
}

private String variable(String line) {
var matcher = assignment.matcher(line);
if (!matcher.matches()) return null;
return this == WINDOWS ? matcher.group(1).toUpperCase(Locale.ROOT) : matcher.group(1);
}
}

private IncludeFileEditor() {}

static String update(
String original, Map<String, String> settings, Set<String> replaced, Format format) {
String newline = newline(original, format);
StringBuilder output = new StringBuilder();
for (String line : original.split("\\R")) {
String variable = format.variable(line);
if (variable == null || !replaced.contains(variable)) output.append(line).append(newline);
}
settings.forEach(
(key, value) -> {
if (!key.matches("[A-Za-z_][A-Za-z0-9_]*")
|| value.indexOf('\n') >= 0
|| value.indexOf('\r') >= 0
|| value.indexOf('\0') >= 0) {
throw new IllegalArgumentException(
"Include-file settings must be single-line variable assignments.");
}
if (format == Format.WINDOWS) {
output.append("set ").append(key).append('=').append(value);
} else {
output.append(key).append("='").append(value.replace("'", "'\"'\"'")).append("'");
}
output.append(newline);
});
return output.toString();
}

static String disable(String original, Set<String> settings, Format format) {
String newline = newline(original, format);
StringBuilder output = new StringBuilder();
boolean changed = false;
for (String line : original.split("\\R")) {
String variable = format.variable(line);
if (variable != null && settings.contains(variable)) {
output.append(format.comment);
changed = true;
}
output.append(line).append(newline);
}
return changed ? output.toString() : original;
}

private static String newline(String original, Format format) {
return original.contains("\r\n") || (original.isEmpty() && format == Format.WINDOWS)
? "\r\n"
: "\n";
}

static Path backup(Path path) throws IOException {
Path target = path.toRealPath();
Path backup = Files.createTempFile(target.getParent(), target.getFileName() + ".", ".bak");
try {
Files.copy(
target, backup, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
restrictPermissions(backup);
return backup;
} catch (IOException failure) {
Files.deleteIfExists(backup);
throw failure;
}
}

static Path save(Path path, String content) throws IOException {
Path backup = backup(path);
writePrivate(path, content.getBytes(StandardCharsets.UTF_8));
return backup;
}

static void writePrivate(Path path, byte[] data) throws IOException {
Path target = Files.exists(path) ? path.toRealPath() : path.toAbsolutePath().normalize();
Path staged = Files.createTempFile(target.getParent(), ".solr-config-", ".tmp");
try {
restrictPermissions(staged);
if (Files.exists(target)) {
var sourceAcl = Files.getFileAttributeView(target, AclFileAttributeView.class);
var destinationAcl = Files.getFileAttributeView(staged, AclFileAttributeView.class);
if (sourceAcl != null && destinationAcl != null) destinationAcl.setAcl(sourceAcl.getAcl());
}
Files.write(staged, data);
Files.move(
staged, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(staged);
}
}

private static void restrictPermissions(Path path) throws IOException {
if (path.getFileSystem().supportedFileAttributeViews().contains("posix")) {
Files.setPosixFilePermissions(path, PosixFilePermissions.fromString("rw-------"));
}
}
}
Loading
Loading