[7.x] SQL: Redact credentials in connection exceptions (#58650) (#59025)

* SQL: Redact credentials in connection exceptions (#58650)

This commit adds the functionality to redact the credentials from the
exceptions generated when a connection attempt fails, preventing them
from leaking into logs, console history etc.

There are a few causes that can lead to failed connections. The most
challenging to deal with is a malformed connection string. The redaction
tries to get around it by modifying the URI to a parsable state, so that
the redaction can be applied reliably. If there's no reliability
guarantee, the redaction will bluntly replace the entire connection
string and the user informed about the option to modify it so that the
redaction won't apply. (This is done by using a caplitalized scheme,
which is legal, but otherwise never used in practice.)

The commit fixes a couple of other issues with the URI parser:
- it allows an empty hostname, or even entire connection string (as per
the existing documentation);
- it reduces the editing of the connection string in the exception
messages (so that the user easier recognize their input);
- it uses the default URI as source for the scheme and hostname.

(cherry picked from commit a0bd5929d0658c4fed44404e0c4d78eac88222fd)

* Implement String#repeat(), unavailable in Java8

Implement a client.StringUtils#repeatString() as a replacement for
String#repeat(), unavailable in Java8.
This commit is contained in:
Bogdan Pintea 2020-07-04 11:29:06 +02:00 committed by GitHub
parent c8e3128fe4
commit e88d71b187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 461 additions and 77 deletions

View File

@ -113,8 +113,6 @@ public class JdbcConfiguration extends ConnectionConfiguration {
}
private static URI parseUrl(String u) throws JdbcSQLException {
String url = u;
String format = "jdbc:es://[[http|https]://]?[host[:port]]?/[prefix]?[\\?[option=value]&]*";
if (!canAccept(u)) {
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + u + "]");
}
@ -122,7 +120,8 @@ public class JdbcConfiguration extends ConnectionConfiguration {
try {
return parseURI(removeJdbcPrefix(u), DEFAULT_URI);
} catch (IllegalArgumentException ex) {
throw new JdbcSQLException(ex, "Invalid URL [" + url + "], format should be [" + format + "]");
final String format = "jdbc:[es|elasticsearch]://[[http|https]://]?[host[:port]]?/[prefix]?[\\?[option=value]&]*";
throw new JdbcSQLException(ex, "Invalid URL: " + ex.getMessage() + "; format should be [" + format + "]");
}
}

View File

@ -41,8 +41,9 @@ public class JdbcConfigurationTests extends ESTestCase {
public void testInvalidUrl() {
JdbcSQLException e = expectThrows(JdbcSQLException.class, () -> ci("jdbc:es://localhost9200/?ssl=#5#"));
assertEquals("Invalid URL [jdbc:es://localhost9200/?ssl=#5#], format should be " +
"[jdbc:es://[[http|https]://]?[host[:port]]?/[prefix]?[\\?[option=value]&]*]", e.getMessage());
assertEquals("Invalid URL: Invalid connection configuration: Illegal character in fragment at index 28: "
+ "http://localhost9200/?ssl=#5#; format should be "
+ "[jdbc:[es|elasticsearch]://[[http|https]://]?[host[:port]]?/[prefix]?[\\?[option=value]&]*]", e.getMessage());
}
public void testJustThePrefix() throws Exception {

View File

@ -305,4 +305,14 @@ public abstract class StringUtils {
return buf.toString();
}
public static String repeatString(String in, int count) {
if (count < 0) {
throw new IllegalArgumentException("negative count: " + count);
}
StringBuffer sb = new StringBuffer(in.length() * count);
for (int i = 0; i < count; i ++) {
sb.append(in);
}
return sb.toString();
}
}

View File

@ -7,18 +7,36 @@ package org.elasticsearch.xpack.sql.client;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.elasticsearch.xpack.sql.client.StringUtils.repeatString;
public final class UriUtils {
private UriUtils() {
}
static final String HTTP_SCHEME = "http";
static final String HTTPS_SCHEME = "https";
static final String HTTP_PREFIX = HTTP_SCHEME + "://";
static final String HTTPS_PREFIX = HTTPS_SCHEME + "://";
/**
* Parses the URL provided by the user and
* Parses the URL provided by the user and substitutes any missing parts in it with defaults read from the defaultURI.
* The result is returned as an URI object.
* In case of a parsing exception, the credentials are redacted from the URISyntaxException message.
*/
public static URI parseURI(String connectionString, URI defaultURI) {
final URI uri = parseWithNoScheme(connectionString);
final URI uri = parseMaybeWithScheme(connectionString, defaultURI.getScheme() + "://");
// Repack the connection string with provided default elements - where missing from the original string - and reparse into a URI.
final String scheme = uri.getScheme() != null ? uri.getScheme() : defaultURI.getScheme();
// TODO: support for IDN
final String host = uri.getHost() != null ? uri.getHost() : defaultURI.getHost();
final String path = "".equals(uri.getPath()) ? defaultURI.getPath() : uri.getPath();
final String rawQuery = uri.getQuery() == null ? defaultURI.getRawQuery() : uri.getRawQuery();
final String rawFragment = uri.getFragment() == null ? defaultURI.getRawFragment() : uri.getRawFragment();
@ -28,7 +46,7 @@ public final class UriUtils {
// escaped query structure characters (`&` and `=`) wouldn't remain escaped when passed back through the URI constructor
// (since they are legal in the query part), and that would later interfere with correctly parsing the attributes.
// And same with escaped `#` chars in the fragment part.
String connStr = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), port, path, null, null).toString();
String connStr = new URI(scheme, uri.getUserInfo(), host, port, path, null, null).toString();
if (StringUtils.hasLength(rawQuery)) {
connStr += "?" + rawQuery;
}
@ -42,42 +60,211 @@ public final class UriUtils {
}
}
private static URI parseWithNoScheme(String connectionString) {
private static URI parseMaybeWithScheme(String connectionString, String defaultPrefix) {
URI uri;
// check if URI can be parsed correctly without adding scheme
// if the connection string is in format host:port or just host, the host is going to be null
// if the connection string contains IPv6 localhost [::1] the parsing will fail
URISyntaxException firstException = null;
String c = connectionString.toLowerCase(Locale.ROOT);
boolean hasAnHttpPrefix = c.startsWith(HTTP_PREFIX) || c.startsWith(HTTPS_PREFIX);
try {
uri = new URI(connectionString);
if (uri.getHost() == null || uri.getScheme() == null) {
uri = null;
}
} catch (URISyntaxException e) {
firstException = e;
uri = null;
// If the connection string contains no scheme plus an IP address with semicolon - like an IPv6 ([::1]), or an IPv4 plus port
// (127.0.0.1:9200) - the URI parser will fail, as it'll try to interpret the pre-`:` chars as a scheme.
if (hasAnHttpPrefix == false) {
return parseMaybeWithScheme(defaultPrefix + connectionString, null);
}
URISyntaxException s = CredentialsRedaction.redactedURISyntaxException(e);
throw new IllegalArgumentException("Invalid connection configuration: " + s.getMessage(), s);
}
if (uri == null) {
// We couldn't parse URI without adding scheme, let's try again with scheme this time
try {
return new URI("http://" + connectionString);
} catch (URISyntaxException e) {
IllegalArgumentException ie =
new IllegalArgumentException("Invalid connection configuration [" + connectionString + "]: " + e.getMessage(), e);
if (firstException != null) {
ie.addSuppressed(firstException);
}
throw ie;
}
} else {
// We managed to parse URI and all necessary pieces are present, let's make sure the scheme is correct
if ("http".equals(uri.getScheme()) == false && "https".equals(uri.getScheme()) == false) {
if (hasAnHttpPrefix == false) {
if (uri.getHost() != null) { // URI is valid and with a host, so there's a scheme (otherwise host==null), but just not HTTP(S)
throw new IllegalArgumentException(
"Invalid connection configuration [" + connectionString + "]: Only http and https protocols are supported");
"Invalid connection scheme [" + uri.getScheme() + "] configuration: only " + HTTP_SCHEME + " and " + HTTPS_SCHEME
+ " protocols are supported");
}
// no host and either (1) no scheme (like for input 'host') or (2) invalid scheme (produced by parsing 'user:pass@host' or
// 'host:9200' or just erroneous: 'ftp:/?foo' etc.): try with a HTTP scheme
if (connectionString.length() > 0) { // an empty string is a valid connection string
return parseMaybeWithScheme(defaultPrefix + connectionString, null);
}
return uri;
}
return uri;
}
public static class CredentialsRedaction {
public static final Character REDACTION_CHAR = '*';
private static final String USER_ATTR_NAME = "user";
private static final String PASS_ATTR_NAME = "password";
// redacts the value of a named attribute in a given string, by finding the substring `attrName=`; everything following that is
// considered as attribute's value.
private static String redactAttributeInString(String string, String attrName, Character replacement) {
String needle = attrName + "=";
int attrIdx = string.toLowerCase(Locale.ROOT).indexOf(needle); // note: won't catch "valid" `=password[%20]+=` cases
if (attrIdx >= 0) { // ex: `...=[value]password=foo...`
int attrEndIdx = attrIdx + needle.length();
return string.substring(0, attrEndIdx) + repeatString(String.valueOf(replacement), string.length() - attrEndIdx);
}
return string;
}
private static void redactValueForSimilarKey(String key, List<String> options, List<Map.Entry<String, String>> attrs,
Character replacement) {
List<String> similar = StringUtils.findSimilar(key, options);
for (String k : similar) {
for (Map.Entry<String, String> e : attrs) {
if (e.getKey().equals(k)) {
e.setValue(repeatString(String.valueOf(replacement), e.getValue().length()));
}
}
}
}
public static String redactCredentialsInRawUriQuery(String rawQuery, Character replacement) {
List<Map.Entry<String, String>> attrs = new ArrayList<>();
List<String> options = new ArrayList<>();
// break down the query in (key, value) tuples, redacting any malformed attribute values
String key, value;
for (String param : StringUtils.tokenize(rawQuery, "&")) {
int eqIdx = param.indexOf('=');
if (eqIdx <= 0) { // malformed param: no, or leading `=`: record entire param string as key and empty string as value
value = eqIdx < 0 ? null : StringUtils.EMPTY;
key = redactAttributeInString(param, USER_ATTR_NAME, replacement);
key = redactAttributeInString(key, PASS_ATTR_NAME, replacement);
} else {
key = param.substring(0, eqIdx);
value = param.substring(eqIdx + 1);
if (value.indexOf('=') >= 0) { // `...&user=FOOpassword=BAR&...`
value = redactAttributeInString(value, USER_ATTR_NAME, replacement);
value = redactAttributeInString(value, PASS_ATTR_NAME, replacement);
}
options.add(key);
}
attrs.add(new AbstractMap.SimpleEntry<>(key, value));
}
// redact the credential attributes, as well as any other attribute that is similar to them, i.e. mistyped
redactValueForSimilarKey(USER_ATTR_NAME, options, attrs, replacement);
redactValueForSimilarKey(PASS_ATTR_NAME, options, attrs, replacement);
// re-construct the query
StringBuilder sb = new StringBuilder(rawQuery.length());
for (Map.Entry<String, String> a : attrs) {
sb.append("&");
sb.append(a.getKey());
if (a.getValue() != null) {
sb.append("=");
sb.append(a.getValue());
}
}
return sb.substring(1);
}
private static String editURI(URI uri, List<Map.Entry<Integer, Character>> faults, boolean hasPort) {
StringBuilder sb = new StringBuilder();
if (uri.getScheme() != null) {
sb.append(uri.getScheme());
sb.append("://");
}
if (uri.getRawUserInfo() != null) {
sb.append(repeatString("\0", uri.getRawUserInfo().length()));
if (uri.getHost() != null) {
sb.append('@');
}
}
if (uri.getHost() != null) {
sb.append(uri.getHost());
}
if (hasPort || uri.getPort() > 0) {
sb.append(':');
}
if (uri.getPort() > 0) {
sb.append(uri.getPort());
}
if (uri.getRawPath() != null) {
sb.append(uri.getRawPath());
}
if (uri.getQuery() != null) {
sb.append('?');
// redact with the null character; this will later allow safe reinsertion of any character removed from the URI to make
// it parsable
sb.append(redactCredentialsInRawUriQuery(uri.getRawQuery(), '\0'));
}
if (uri.getRawFragment() != null) {
sb.append('#');
sb.append(uri.getRawFragment());
}
// reinsert any removed character back into the URI: if the reinsertion should be made between null characters, replace its
// value with a null character, since it's part of the credential value
Collections.reverse(faults);
for (Map.Entry<Integer, Character> e : faults) {
int idx = e.getKey();
if (idx >= sb.length()) {
sb.append(e.getValue());
} else {
sb.insert(idx,
(sb.charAt(idx) == '\0' && (idx + 1 >= sb.length() || sb.charAt(idx + 1) == '\0')) ? '\0' : e.getValue());
}
}
StringBuilder ret = new StringBuilder();
sb.chars().forEach(x -> ret.append(x == '\0' ? REDACTION_CHAR : (char) x));
return ret.toString();
}
private static String redactCredentialsInURLString(String urlString) {
List<Map.Entry<Integer, Character>> faults = new ArrayList<>();
boolean hasPort = false;
for (StringBuilder sb = new StringBuilder(urlString); sb.length() > 0; ) {
try {
// parse as URL; ex. `http://ho~st` parses as URI, but with unparsable authority
URI uri = new URI(sb.toString()).parseServerAuthority();
return editURI(uri, faults, hasPort);
} catch (URISyntaxException use) {
int idx = use.getIndex();
if (idx < 0 || idx >= sb.length()) {
break; // not a faulty character-related error
}
if (use.getReason().equals("Illegal character in port number")) {
// if entire port part is broken (ex. `localhost:noDigit`), the trailing `:` will be lost in the resulting URI
hasPort = true;
}
faults.add(new AbstractMap.SimpleImmutableEntry<>(use.getIndex(), sb.charAt(idx)));
sb.deleteCharAt(idx);
}
}
return null;
}
public static String redactCredentialsInConnectionString(String connectionString) {
if (connectionString.startsWith(HTTP_PREFIX.toUpperCase(Locale.ROOT))
|| connectionString.startsWith(HTTPS_PREFIX.toUpperCase(Locale.ROOT))
// too short to contain credentials
|| connectionString.length() < "_:_@_".length()
// In "[a] hierarchical URI [...] characters :, /, ?, and # stand for themselves": don't attempt to redact a URI that
// contains no credentials.
|| (connectionString.indexOf('@') < 0 && connectionString.indexOf('?') < 0)) {
return connectionString;
}
String cs = connectionString.toLowerCase(Locale.ROOT);
boolean prefixed = cs.startsWith(HTTP_PREFIX) || cs.startsWith(HTTPS_PREFIX);
String redacted = redactCredentialsInURLString((prefixed ? StringUtils.EMPTY : HTTP_PREFIX) + connectionString);
if (redacted == null) {
return "<REDACTED> ; a capitalized scheme (HTTP|HTTPS) disables the redaction";
}
return prefixed ? redacted : redacted.substring(HTTP_PREFIX.length());
}
public static URISyntaxException redactedURISyntaxException(URISyntaxException e) {
return new URISyntaxException(redactCredentialsInConnectionString(e.getInput()), e.getReason(), e.getIndex());
}
}
/**

View File

@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.client;
import org.elasticsearch.test.ESTestCase;
import static org.elasticsearch.xpack.sql.client.StringUtils.nullAsEmpty;
import static org.elasticsearch.xpack.sql.client.StringUtils.repeatString;
public class StringUtilsTests extends ESTestCase {
public void testNullAsEmpty() {
@ -16,4 +17,21 @@ public class StringUtilsTests extends ESTestCase {
String rando = randomRealisticUnicodeOfCodepointLength(5);
assertEquals(rando, nullAsEmpty(rando));
}
public void testRepeatString() {
final int count = randomIntBetween(0, 100) <= 5 ? -1 : randomIntBetween(0, 100);
final int len = randomIntBetween(0, 100);
//final String in = randomUnicodeOfLength(len);
final String in = randomAlphaOfLength(len);
final String out;
try {
out = repeatString(in, count);
assertEquals(count * len, out.length());
for (int i = 0; i < count; i ++) {
assertEquals(in, out.substring(i * len, (i + 1) * len));
}
} catch (IllegalArgumentException iae) {
assertTrue(count < 0);
}
}
}

View File

@ -8,9 +8,13 @@ package org.elasticsearch.xpack.sql.client;
import org.elasticsearch.test.ESTestCase;
import java.net.URI;
import java.util.Arrays;
import static org.elasticsearch.xpack.sql.client.UriUtils.CredentialsRedaction.REDACTION_CHAR;
import static org.elasticsearch.xpack.sql.client.StringUtils.repeatString;
import static org.elasticsearch.xpack.sql.client.UriUtils.appendSegmentToPath;
import static org.elasticsearch.xpack.sql.client.UriUtils.parseURI;
import static org.elasticsearch.xpack.sql.client.UriUtils.CredentialsRedaction.redactCredentialsInConnectionString;
import static org.elasticsearch.xpack.sql.client.UriUtils.removeQuery;
public class UriUtilsTests extends ESTestCase {
@ -41,6 +45,26 @@ public class UriUtilsTests extends ESTestCase {
assertEquals(URI.create("http://[::1]:51082/"), parseURI("[::1]:51082", DEFAULT_URI));
}
public void testUserLocalhostV6() throws Exception {
assertEquals(URI.create("http://user@[::1]:51082/"), parseURI("user@[::1]:51082", DEFAULT_URI));
}
public void testLocalhostV4() throws Exception {
assertEquals(URI.create("http://127.0.0.1:51082/"), parseURI("127.0.0.1:51082", DEFAULT_URI));
}
public void testUserLocalhostV4() throws Exception {
assertEquals(URI.create("http://user@127.0.0.1:51082/"), parseURI("user@127.0.0.1:51082", DEFAULT_URI));
}
public void testNoHost() {
assertEquals(URI.create("http://localhost:9200/path?query"), parseURI("/path?query", DEFAULT_URI));
}
public void testEmpty() {
assertEquals(URI.create("http://localhost:9200/"), parseURI("", DEFAULT_URI));
}
public void testHttpsWithUser() throws Exception {
assertEquals(URI.create("https://user@server:9200/"), parseURI("https://user@server", DEFAULT_URI));
}
@ -58,83 +82,228 @@ public class UriUtilsTests extends ESTestCase {
}
public void testUnsupportedProtocol() throws Exception {
assertEquals(
"Invalid connection configuration [ftp://server:9201/]: Only http and https protocols are supported",
expectThrows(IllegalArgumentException.class, () -> parseURI("ftp://server:9201/", DEFAULT_URI)).getMessage()
);
assertEquals("Invalid connection scheme [ftp] configuration: only http and https protocols are supported",
expectThrows(IllegalArgumentException.class, () -> parseURI("ftp://server:9201/", DEFAULT_URI)).getMessage());
}
public void testMalformed() throws Exception {
assertEquals(
"Invalid connection configuration []: Expected authority at index 7: http://",
expectThrows(IllegalArgumentException.class, () -> parseURI("", DEFAULT_URI)).getMessage()
);
public void testMalformedWhiteSpace() throws Exception {
assertEquals("Invalid connection configuration: Illegal character in authority at index 7: http:// ",
expectThrows(IllegalArgumentException.class, () -> parseURI(" ", DEFAULT_URI)).getMessage());
}
public void testNoRedaction() {
assertEquals("Invalid connection configuration: Illegal character in fragment at index 16: HTTP://host#frag#ment",
expectThrows(IllegalArgumentException.class, () -> parseURI("HTTP://host#frag#ment", DEFAULT_URI)).getMessage());
}
public void testSimpleUriRedaction() {
assertEquals("http://*************@host:9200/path?user=****&password=****",
redactCredentialsInConnectionString("http://user:password@host:9200/path?user=user&password=pass"));
}
public void testSimpleConnectionStringRedaction() {
assertEquals("*************@host:9200/path?user=****&password=****",
redactCredentialsInConnectionString("user:password@host:9200/path?user=user&password=pass"));
}
public void testNoRedactionInvalidHost() {
assertEquals("https://ho%st", redactCredentialsInConnectionString("https://ho%st"));
}
public void testUriRedactionInvalidUserPart() {
assertEquals("http://*************@@host:9200/path?user=****&password=****&at=@sign",
redactCredentialsInConnectionString("http://user:password@@host:9200/path?user=user&password=pass&at=@sign"));
}
public void testUriRedactionInvalidHost() {
assertEquals("http://*************@ho%st:9200/path?user=****&password=****&at=@sign",
redactCredentialsInConnectionString("http://user:password@ho%st:9200/path?user=user&password=pass&at=@sign"));
}
public void testUriRedactionInvalidPort() {
assertEquals("http://*************@host:port/path?user=****&password=****&at=@sign",
redactCredentialsInConnectionString("http://user:password@host:port/path?user=user&password=pass&at=@sign"));
}
public void testUriRedactionInvalidPath() {
assertEquals("http://*************@host:9200/pa^th?user=****&password=****",
redactCredentialsInConnectionString("http://user:password@host:9200/pa^th?user=user&password=pass"));
}
public void testUriRedactionInvalidQuery() {
assertEquals("http://*************@host:9200/path?user=****&password=****&invali^d",
redactCredentialsInConnectionString("http://user:password@host:9200/path?user=user&password=pass&invali^d"));
}
public void testUriRedactionInvalidFragment() {
assertEquals("https://host:9200/path?usr=****&passwo=****#ssl=5#",
redactCredentialsInConnectionString("https://host:9200/path?usr=user&passwo=pass#ssl=5#"));
}
public void testUriRedactionMisspelledUser() {
assertEquals("https://host:9200/path?usr=****&password=****",
redactCredentialsInConnectionString("https://host:9200/path?usr=user&password=pass"));
}
public void testUriRedactionMisspelledUserAndPassword() {
assertEquals("https://host:9200/path?usr=****&passwo=****",
redactCredentialsInConnectionString("https://host:9200/path?usr=user&passwo=pass"));
}
public void testUriRedactionNoScheme() {
assertEquals("host:9200/path?usr=****&passwo=****", redactCredentialsInConnectionString("host:9200/path?usr=user&passwo=pass"));
}
public void testUriRedactionNoPort() {
assertEquals("host/path?usr=****&passwo=****", redactCredentialsInConnectionString("host/path?usr=user&passwo=pass"));
}
public void testUriRedactionNoHost() {
assertEquals("/path?usr=****&passwo=****", redactCredentialsInConnectionString("/path?usr=user&passwo=pass"));
}
public void testUriRedactionNoPath() {
assertEquals("?usr=****&passwo=****", redactCredentialsInConnectionString("?usr=user&passwo=pass"));
}
public void testUriRandomRedact() {
final String scheme = randomFrom("http://", "https://", StringUtils.EMPTY);
final String host = randomFrom("host", "host:" + randomIntBetween(1, 65535), StringUtils.EMPTY);
final String path = randomFrom("", "/", "/path", StringUtils.EMPTY);
final String userVal = randomAlphaOfLengthBetween(1, 2 + randomInt(50));
final String user = "user=" + userVal;
final String passVal = randomAlphaOfLengthBetween(1, 2 + randomInt(50));
final String pass = "password=" + passVal;
final String redactedUser = "user=" + repeatString(String.valueOf(REDACTION_CHAR), userVal.length());
final String redactedPass = "password=" + repeatString(String.valueOf(REDACTION_CHAR), passVal.length());
String connStr, expectRedact, expectParse, creds = StringUtils.EMPTY;
if (randomBoolean() && host.length() > 0) {
creds = userVal;
if (randomBoolean()) {
creds += ":" + passVal;
}
connStr = scheme + creds + "@" + host + path;
expectRedact = scheme + repeatString(String.valueOf(REDACTION_CHAR), creds.length()) + "@" + host + path;
} else {
connStr = scheme + host + path;
expectRedact = scheme + host + path;
}
expectParse = scheme.length() > 0 ? scheme : "http://";
expectParse += creds + (creds.length() > 0 ? "@" : StringUtils.EMPTY);
expectParse += host.length() > 0 ? host : "localhost";
expectParse += (host.indexOf(':') > 0 ? StringUtils.EMPTY : ":" + 9200);
expectParse += path.length() > 0 ? path : "/";
Character sep = '?';
if (randomBoolean()) {
connStr += sep + user;
expectRedact += sep + redactedUser;
expectParse += sep + user;
sep = '&';
}
if (randomBoolean()) {
connStr += sep + pass;
expectRedact += sep + redactedPass;
expectParse += sep + pass;
}
assertEquals(expectRedact, redactCredentialsInConnectionString(connStr));
if ((connStr.equals("http://") || connStr.equals("https://")) == false) { // URI parser expects an authority past a scheme
assertEquals(URI.create(expectParse), parseURI(connStr, DEFAULT_URI));
}
}
public void testUriRedactionMissingSeparatorBetweenUserAndPassword() {
assertEquals("https://host:9200/path?user=*****************",
redactCredentialsInConnectionString("https://host:9200/path?user=userpassword=pass"));
}
public void testUriRedactionMissingSeparatorBeforePassword() {
assertEquals("https://host:9200/path?user=****&foo=barpassword=********&bar=foo",
redactCredentialsInConnectionString("https://host:9200/path?user=user&foo=barpassword=password&bar=foo"));
}
// tests that no other option is "similar" to the credential options and be inadvertently redacted
public void testUriRedactionAllOptions() {
StringBuilder cs = new StringBuilder("https://host:9200/path?");
String[] options = {"timezone", "connect.timeout", "network.timeout", "page.timeout", "page.size", "query.timeout", "user",
"password", "ssl", "ssl.keystore.location", "ssl.keystore.pass", "ssl.keystore.type", "ssl.truststore.location",
"ssl.truststore.pass", "ssl.truststore.type", "ssl.protocol", "proxy.http", "proxy.socks", "field.multi.value.leniency",
"index.include.frozen", "validate.properties"
};
Arrays.stream(options).forEach(e -> cs.append(e).append("=").append(e).append("&"));
String connStr = cs.substring(0, cs.length() - 1);
String expected = connStr.replace("user=user", "user=****");
expected = expected.replace("password=password", "password=********");
assertEquals(expected, redactCredentialsInConnectionString(connStr));
}
public void testUriRedactionBrokenHost() {
assertEquals("ho^st", redactCredentialsInConnectionString("ho^st"));
}
public void testUriRedactionDisabled() {
assertEquals("HTTPS://host:9200/path?user=user;password=pass",
redactCredentialsInConnectionString("HTTPS://host:9200/path?user=user;password=pass"));
}
public void testRemoveQuery() throws Exception {
assertEquals(URI.create("http://server:9100"),
removeQuery(URI.create("http://server:9100?query"), "http://server:9100?query", DEFAULT_URI));
removeQuery(URI.create("http://server:9100?query"), "http://server:9100?query", DEFAULT_URI));
}
public void testRemoveQueryTrailingSlash() throws Exception {
assertEquals(URI.create("http://server:9100/"),
removeQuery(URI.create("http://server:9100/?query"), "http://server:9100/?query", DEFAULT_URI));
removeQuery(URI.create("http://server:9100/?query"), "http://server:9100/?query", DEFAULT_URI));
}
public void testRemoveQueryNoQuery() throws Exception {
assertEquals(URI.create("http://server:9100"),
removeQuery(URI.create("http://server:9100"), "http://server:9100", DEFAULT_URI));
assertEquals(URI.create("http://server:9100"), removeQuery(URI.create("http://server:9100"), "http://server:9100", DEFAULT_URI));
}
public void testAppendEmptySegmentToPath() throws Exception {
assertEquals(URI.create("http://server:9100"),
appendSegmentToPath(URI.create("http://server:9100"), ""));
assertEquals(URI.create("http://server:9100"), appendSegmentToPath(URI.create("http://server:9100"), ""));
}
public void testAppendNullSegmentToPath() throws Exception {
assertEquals(URI.create("http://server:9100"),
appendSegmentToPath(URI.create("http://server:9100"), null));
assertEquals(URI.create("http://server:9100"), appendSegmentToPath(URI.create("http://server:9100"), null));
}
public void testAppendSegmentToNullPath() throws Exception {
assertEquals(
"URI must not be null",
expectThrows(IllegalArgumentException.class, () -> appendSegmentToPath(null, "/_sql")).getMessage()
);
assertEquals("URI must not be null",
expectThrows(IllegalArgumentException.class, () -> appendSegmentToPath(null, "/_sql")).getMessage());
}
public void testAppendSegmentToEmptyPath() throws Exception {
assertEquals(URI.create("/_sql"),
appendSegmentToPath(URI.create(""), "/_sql"));
assertEquals(URI.create("/_sql"), appendSegmentToPath(URI.create(""), "/_sql"));
}
public void testAppendSlashSegmentToPath() throws Exception {
assertEquals(URI.create("http://server:9100"),
appendSegmentToPath(URI.create("http://server:9100"), "/"));
assertEquals(URI.create("http://server:9100"), appendSegmentToPath(URI.create("http://server:9100"), "/"));
}
public void testAppendSqlSegmentToPath() throws Exception {
assertEquals(URI.create("http://server:9100/_sql"),
appendSegmentToPath(URI.create("http://server:9100"), "/_sql"));
assertEquals(URI.create("http://server:9100/_sql"), appendSegmentToPath(URI.create("http://server:9100"), "/_sql"));
}
public void testAppendSqlSegmentNoSlashToPath() throws Exception {
assertEquals(URI.create("http://server:9100/_sql"),
appendSegmentToPath(URI.create("http://server:9100"), "_sql"));
assertEquals(URI.create("http://server:9100/_sql"), appendSegmentToPath(URI.create("http://server:9100"), "_sql"));
}
public void testAppendSegmentToPath() throws Exception {
assertEquals(URI.create("http://server:9100/es_rest/_sql"),
appendSegmentToPath(URI.create("http://server:9100/es_rest"), "/_sql"));
appendSegmentToPath(URI.create("http://server:9100/es_rest"), "/_sql"));
}
public void testAppendSegmentNoSlashToPath() throws Exception {
assertEquals(URI.create("http://server:9100/es_rest/_sql"),
appendSegmentToPath(URI.create("http://server:9100/es_rest"), "_sql"));
appendSegmentToPath(URI.create("http://server:9100/es_rest"), "_sql"));
}
public void testAppendSegmentTwoSlashesToPath() throws Exception {
assertEquals(URI.create("https://server:9100/es_rest/_sql"),
appendSegmentToPath(URI.create("https://server:9100/es_rest/"), "/_sql"));
appendSegmentToPath(URI.create("https://server:9100/es_rest/"), "/_sql"));
}
}