From de4bc1d7149c0c0260416a09cdc6a44fa7cf899e Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Thu, 24 Feb 2022 00:30:32 +0100 Subject: [PATCH 1/6] securityadmin: Replace TransportClient by RestHighLevelClient Signed-off-by: rs-eliatra --- .../security/OpenSearchSecurityPlugin.java | 5 +- .../ConfigUpdateNodeResponse.java | 14 +- .../configupdate/ConfigUpdateResponse.java | 18 +- .../security/filter/SecurityRestFilter.java | 6 +- .../rest/SecurityConfigUpdateAction.java | 82 ++ .../security/rest/SecurityWhoAmIAction.java | 121 +++ .../security/support/WildcardMatcher.java | 2 +- .../security/tools/SecurityAdmin.java | 831 +++++++++++------- .../SecurityAdminIEndpointsTests.java | 109 +++ .../SecurityAdminInvalidConfigsTests.java | 31 +- .../security/SecurityAdminMigrationTests.java | 4 +- .../security/SecurityAdminTests.java | 158 +++- 12 files changed, 998 insertions(+), 383 deletions(-) create mode 100644 src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java create mode 100644 src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java create mode 100644 src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 6e42c73b05..95f7d5d33b 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -57,6 +57,8 @@ import org.opensearch.security.dlic.rest.api.SecurityRestApiActions; import org.opensearch.security.filter.SecurityRestFilter; import org.opensearch.security.http.SecurityHttpServerTransport; +import org.opensearch.security.rest.SecurityConfigUpdateAction; +import org.opensearch.security.rest.SecurityWhoAmIAction; import org.opensearch.security.ssl.OpenSearchSecuritySSLPlugin; import org.opensearch.security.ssl.rest.SecuritySSLReloadCertsAction; import org.opensearch.security.ssl.rest.SecuritySSLCertsInfoAction; @@ -459,7 +461,8 @@ public List getRestHandlers(Settings settings, RestController restC handlers.add(new DashboardsInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); handlers.add(new TenantInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool), Objects.requireNonNull(cs), Objects.requireNonNull(adminDns), Objects.requireNonNull(cr))); - + handlers.add(new SecurityConfigUpdateAction(settings, restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); + handlers.add(new SecurityWhoAmIAction(settings ,restController,Objects.requireNonNull(threadPool), adminDns, configPath, principalExtractor)); if (sslCertReloadEnabled) { handlers.add(new SecuritySSLReloadCertsAction(settings, restController, sks, Objects.requireNonNull(threadPool), Objects.requireNonNull(adminDns))); } diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java index 3c68740cd8..e9c8f088e4 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java @@ -37,8 +37,10 @@ import org.opensearch.cluster.node.DiscoveryNode; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; -public class ConfigUpdateNodeResponse extends BaseNodeResponse { +public class ConfigUpdateNodeResponse extends BaseNodeResponse implements ToXContentObject { private String[] updatedConfigTypes; private String message; @@ -78,4 +80,14 @@ public void writeTo(StreamOutput out) throws IOException { public String toString() { return "ConfigUpdateNodeResponse [updatedConfigTypes=" + Arrays.toString(updatedConfigTypes) + ", message=" + message + "]"; } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("updated_config_types", updatedConfigTypes); + builder.field("updated_config_size", updatedConfigTypes == null ? 0: updatedConfigTypes.length); + builder.field("message", message); + builder.endObject(); + return builder; + } } diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java index bb38593d7a..6ed7cf4154 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java @@ -38,13 +38,15 @@ import org.opensearch.cluster.ClusterName; import org.opensearch.common.io.stream.StreamInput; import org.opensearch.common.io.stream.StreamOutput; +import org.opensearch.common.xcontent.ToXContentObject; +import org.opensearch.common.xcontent.XContentBuilder; -public class ConfigUpdateResponse extends BaseNodesResponse { +public class ConfigUpdateResponse extends BaseNodesResponse implements ToXContentObject { public ConfigUpdateResponse(StreamInput in) throws IOException { super(in); } - + public ConfigUpdateResponse(final ClusterName clusterName, List nodes, List failures) { super(clusterName, nodes, failures); } @@ -58,4 +60,16 @@ public List readNodesFrom(final StreamInput in) throws public void writeNodesTo(final StreamOutput out, List nodes) throws IOException { out.writeList(nodes); } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject("configupdate_response"); + builder.field("nodes", getNodesMap()); + builder.field("node_size", getNodes().size()); + builder.field("has_failures", hasFailures()); + builder.field("failures_size", failures().size()); + builder.endObject(); + + return builder; + } } diff --git a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java index 800ac13a14..be3bf44129 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java @@ -83,6 +83,8 @@ public class SecurityRestFilter { private WhitelistingSettings whitelistingSettings; private static final String HEALTH_SUFFIX = "health"; + private static final String WHO_AM_I_SUFFIX = "whoami"; + private static final String REGEX_PATH_PREFIX = "/("+ LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" +"(.*)"; private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX); @@ -184,7 +186,9 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha Matcher matcher = PATTERN_PATH_PREFIX.matcher(request.path()); final String suffix = matcher.matches() ? matcher.group(2) : null; - if(request.method() != Method.OPTIONS && !(HEALTH_SUFFIX.equals(suffix))) { + if(request.method() != Method.OPTIONS + && !(HEALTH_SUFFIX.equals(suffix)) + && !(WHO_AM_I_SUFFIX.equals(suffix))) { if (!registry.authenticate(request, channel, threadContext)) { // another roundtrip org.apache.logging.log4j.ThreadContext.remove("user"); diff --git a/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java new file mode 100644 index 0000000000..4e1ac87266 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.security.rest; + +import com.google.common.collect.ImmutableList; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.rest.*; +import org.opensearch.rest.action.RestActions.NodesResponseRestListener; +import org.opensearch.security.action.configupdate.ConfigUpdateAction; +import org.opensearch.security.action.configupdate.ConfigUpdateRequest; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.ssl.util.SSLRequestHelper; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import static org.opensearch.rest.RestRequest.Method.PUT; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + +public class SecurityConfigUpdateAction extends BaseRestHandler { + + private static final List routes = addRoutesPrefix(ImmutableList.of( + new Route(PUT, "/configupdate")), + "/_plugins/_security"); + + private final ThreadContext threadContext; + private final AdminDNs adminDns; + private final Settings settings; + private final Path configPath; + private final PrincipalExtractor principalExtractor; + + public SecurityConfigUpdateAction(final Settings settings, final RestController controller, final ThreadPool threadPool, final AdminDNs adminDns, + Path configPath, PrincipalExtractor principalExtractor) { + super(); + this.threadContext = threadPool.getThreadContext(); + this.adminDns = adminDns; + this.settings = settings; + this.configPath = configPath; + this.principalExtractor = principalExtractor; + } + + @Override public List routes() { + return routes; + } + + @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + String[] configTypes = request.paramAsStringArrayOrEmptyIfAll("config_types"); + + SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); + + if (sslInfo == null) { + return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "")); + } + + final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + + //only allowed for admins + if (user == null || !adminDns.isAdmin(user)) { + return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "")); + } else { + ConfigUpdateRequest configUpdateRequest = new ConfigUpdateRequest(configTypes); + return channel -> { + client.execute(ConfigUpdateAction.INSTANCE, configUpdateRequest, new NodesResponseRestListener<>(channel)); + }; + } + } + + @Override public String getName() { + return "Security config update"; + } + +} diff --git a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java new file mode 100644 index 0000000000..13708ae9e3 --- /dev/null +++ b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java @@ -0,0 +1,121 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.security.rest; + +import com.google.common.collect.ImmutableList; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentBuilder; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestStatus; +import org.opensearch.security.configuration.AdminDNs; +import org.opensearch.security.ssl.transport.PrincipalExtractor; +import org.opensearch.security.ssl.util.SSLRequestHelper; +import org.opensearch.security.ssl.util.SSLRequestHelper.SSLInfo; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.WildcardMatcher; +import org.opensearch.threadpool.ThreadPool; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rest.RestRequest.Method.POST; +import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; + + +public class SecurityWhoAmIAction extends BaseRestHandler { + + private static final List routes = addRoutesPrefix(ImmutableList.of( + new Route(GET, "/whoami"), + new Route(POST, "/whoami")), + "/_plugins/_security"); + + private final Logger log = LogManager.getLogger(this.getClass()); + private final AdminDNs adminDns; + private final Settings settings; + private final Path configPath; + private final PrincipalExtractor principalExtractor; + private final List nodesDn ; + + public SecurityWhoAmIAction(final Settings settings, final RestController controller, + final ThreadPool threadPool, final AdminDNs adminDns, Path configPath, PrincipalExtractor principalExtractor) { + super(); + this.adminDns = adminDns; + this.settings = settings; + this.configPath = configPath; + this.principalExtractor = principalExtractor; + + nodesDn = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + XContentBuilder builder = channel.newBuilder(); + BytesRestResponse response = null; + + try { + + SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); + + if(sslInfo == null) { + response = new BytesRestResponse(RestStatus.FORBIDDEN, "No security data"); + } else { + + final String dn = sslInfo.getPrincipal(); + final boolean isAdmin = adminDns.isAdminDN(dn); + final boolean isNodeCertificateRequest = dn != null && WildcardMatcher.from(nodesDn, true).matchAny(dn); + + builder.startObject(); + builder.field("dn", dn); + builder.field("is_admin", isAdmin); + builder.field("is_node_certificate_request", isNodeCertificateRequest); + builder.endObject(); + + response = new BytesRestResponse(RestStatus.OK, builder); + + } + } catch (final Exception e1) { + log.error(e1.toString(), e1); + builder = channel.newBuilder(); + builder.startObject(); + builder.field("error", e1.toString()); + builder.endObject(); + response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); + } finally { + if (builder != null) { + builder.close(); + } + } + + channel.sendResponse(response); + } + }; + } + + @Override + public String getName() { + return "Security Plugin Who am i"; + } + +} diff --git a/src/main/java/org/opensearch/security/support/WildcardMatcher.java b/src/main/java/org/opensearch/security/support/WildcardMatcher.java index 7cbda07753..a62ed2567e 100644 --- a/src/main/java/org/opensearch/security/support/WildcardMatcher.java +++ b/src/main/java/org/opensearch/security/support/WildcardMatcher.java @@ -252,7 +252,7 @@ public boolean matchAny(Collection candidates) { return matchAny(candidates.stream()); } - public boolean matchAny(String[] candidates) { + public boolean matchAny(String... candidates) { return matchAny(Arrays.stream(candidates)); } diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index a80f60c560..9f557e1004 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -30,30 +30,14 @@ package org.opensearch.security.tools; -import java.io.ByteArrayInputStream; -import java.io.Console; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import org.opensearch.security.OpenSearchSecurityPlugin; -import org.opensearch.security.auditlog.config.AuditConfig; -import org.opensearch.security.securityconf.impl.NodesDn; -import org.opensearch.security.securityconf.impl.WhitelistingSettings; -import org.opensearch.security.ssl.OpenSearchSecuritySSLPlugin; +import com.fasterxml.jackson.databind.InjectableValues; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Charsets; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterators; +import com.google.common.io.ByteSource; +import com.google.common.io.CharStreams; +import com.google.common.io.Files; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; @@ -61,41 +45,43 @@ import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; -import org.opensearch.OpenSearchException; +import org.apache.http.HttpHost; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; import org.opensearch.ExceptionsHelper; +import org.opensearch.OpenSearchException; +import org.opensearch.OpenSearchStatusException; import org.opensearch.Version; import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; -import org.opensearch.action.admin.cluster.node.info.NodesInfoRequest; -import org.opensearch.action.admin.cluster.node.info.NodesInfoResponse; -import org.opensearch.action.admin.cluster.node.info.PluginsAndModules; -import org.opensearch.action.admin.cluster.node.stats.NodesStatsRequest; -import org.opensearch.action.admin.cluster.node.stats.NodesStatsResponse; import org.opensearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest; -import org.opensearch.action.admin.cluster.tasks.PendingClusterTasksRequest; -import org.opensearch.action.admin.cluster.tasks.PendingClusterTasksResponse; import org.opensearch.action.admin.indices.create.CreateIndexRequest; import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; import org.opensearch.action.admin.indices.get.GetIndexRequest; import org.opensearch.action.admin.indices.get.GetIndexRequest.Feature; import org.opensearch.action.admin.indices.get.GetIndexResponse; import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest; -import org.opensearch.action.admin.indices.stats.IndicesStatsRequest; -import org.opensearch.action.admin.indices.stats.IndicesStatsResponse; import org.opensearch.action.get.GetRequest; import org.opensearch.action.get.GetResponse; import org.opensearch.action.index.IndexRequest; import org.opensearch.action.support.WriteRequest.RefreshPolicy; import org.opensearch.action.support.master.AcknowledgedResponse; -import org.opensearch.client.Client; +import org.opensearch.client.Request; +import org.opensearch.client.RequestOptions; +import org.opensearch.client.Response; +import org.opensearch.client.RestClient; +import org.opensearch.client.RestClientBuilder; +import org.opensearch.client.RestHighLevelClient; import org.opensearch.client.transport.NoNodeAvailableException; -import org.opensearch.client.transport.TransportClient; import org.opensearch.cluster.health.ClusterHealthStatus; import org.opensearch.common.Strings; import org.opensearch.common.bytes.BytesReference; import org.opensearch.common.collect.Tuple; +import org.opensearch.common.settings.Setting; import org.opensearch.common.settings.Settings; -import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.xcontent.NamedXContentRegistry; import org.opensearch.common.xcontent.XContentBuilder; @@ -104,28 +90,15 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.index.IndexNotFoundException; -import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.PluginInfo; -import org.opensearch.security.action.configupdate.ConfigUpdateAction; -import org.opensearch.security.action.configupdate.ConfigUpdateNodeResponse; -import org.opensearch.security.action.configupdate.ConfigUpdateRequest; -import org.opensearch.security.action.configupdate.ConfigUpdateResponse; -import org.opensearch.security.action.whoami.WhoAmIAction; -import org.opensearch.security.action.whoami.WhoAmIRequest; -import org.opensearch.security.action.whoami.WhoAmIResponse; -import org.opensearch.security.securityconf.Migration; -import org.opensearch.security.ssl.util.ExceptionUtils; -import org.opensearch.security.ssl.util.SSLConfigConstants; -import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.support.ConfigHelper; -import org.opensearch.security.support.SecurityJsonNode; -import org.opensearch.security.support.SecurityUtils; -import org.opensearch.transport.Netty4Plugin; - -import com.fasterxml.jackson.databind.JsonNode; +import org.opensearch.rest.RestStatus; import org.opensearch.security.DefaultObjectMapper; +import org.opensearch.security.NonValidatingObjectMapper; +import org.opensearch.security.auditlog.config.AuditConfig; +import org.opensearch.security.securityconf.Migration; import org.opensearch.security.securityconf.impl.CType; +import org.opensearch.security.securityconf.impl.NodesDn; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; +import org.opensearch.security.securityconf.impl.WhitelistingSettings; import org.opensearch.security.securityconf.impl.v6.RoleMappingsV6; import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7; import org.opensearch.security.securityconf.impl.v7.ConfigV7; @@ -133,10 +106,41 @@ import org.opensearch.security.securityconf.impl.v7.RoleMappingsV7; import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.securityconf.impl.v7.TenantV7; -import com.google.common.io.CharStreams; -import com.google.common.io.Files; +import org.opensearch.security.ssl.util.ExceptionUtils; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.support.ConfigHelper; +import org.opensearch.security.support.PemKeyReader; +import org.opensearch.security.support.SecurityJsonNode; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import java.io.ByteArrayInputStream; +import java.io.Console; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; import static org.opensearch.common.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; +import static org.opensearch.security.support.SecurityUtils.replaceEnvVars; @SuppressWarnings("deprecation") public class SecurityAdmin { @@ -213,7 +217,6 @@ public static int execute(final String[] args) throws Exception { options.addOption(Option.builder("r").longOpt("retrieve").desc("retrieve current config").build()); options.addOption(Option.builder("f").longOpt("file").hasArg().argName("file").desc("file").build()); options.addOption(Option.builder("t").longOpt("type").hasArg().argName("file-type").desc("file-type").build()); - options.addOption(Option.builder("tsalias").longOpt("truststore-alias").hasArg().argName("alias").desc("Truststore alias").build()); options.addOption(Option.builder("ksalias").longOpt("keystore-alias").hasArg().argName("alias").desc("Keystore alias").build()); options.addOption(Option.builder("ec").longOpt("enabled-ciphers").hasArg().argName("cipers").desc("Comma separated list of enabled TLS ciphers").build()); options.addOption(Option.builder("ep").longOpt("enabled-protocols").hasArg().argName("protocols").desc("Comma separated list of enabled TLS protocols").build()); @@ -258,7 +261,7 @@ public static int execute(final String[] args) throws Exception { //when adding new options also adjust validate(CommandLine line) String hostname = "localhost"; - int port = 9300; + int port = 9200; String kspass = System.getenv(OPENDISTRO_SECURITY_KS_PASS); String tspass = System.getenv(OPENDISTRO_SECURITY_TS_PASS); String cd = "."; @@ -275,7 +278,6 @@ public static int execute(final String[] args) throws Exception { String type = null; boolean retrieve = false; String ksAlias = null; - String tsAlias = null; String[] enabledProtocols = new String[0]; String[] enabledCiphers = new String[0]; Integer updateSettings = null; @@ -303,7 +305,12 @@ public static int execute(final String[] args) throws Exception { final boolean resolveEnvVars; Integer validateConfig = null; String migrateOffline = null; - + + InjectableValues.Std injectableValues = new InjectableValues.Std(); + injectableValues.addValue(Settings.class, Settings.builder().build()); + DefaultObjectMapper.inject(injectableValues); + NonValidatingObjectMapper.inject(injectableValues); + CommandLineParser parser = new DefaultParser(); try { CommandLine line = parser.parse( options, args ); @@ -342,7 +349,6 @@ public static int execute(final String[] args) throws Exception { type = line.getOptionValue("t", type); retrieve = line.hasOption("r"); ksAlias = line.getOptionValue("ksalias", ksAlias); - tsAlias = line.getOptionValue("tsalias", tsAlias); index = line.getOptionValue("i", index); String enabledCiphersString = line.getOptionValue("ec", null); @@ -408,7 +414,6 @@ public static int execute(final String[] args) throws Exception { return -1; } - if(validateConfig != null) { System.out.println("Validate configuration for Version "+validateConfig.intValue()); return validateConfig(cd, file, type, validateConfig.intValue()); @@ -446,98 +451,54 @@ public static int execute(final String[] args) throws Exception { } System.out.println(" ... done"); - - final Settings.Builder settingsBuilder = Settings - .builder() - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, !nhnv) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME, !nrhn) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED, true) - .put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLE_OPENSSL_IF_AVAILABLE, OpenSearchSecuritySSLPlugin.OPENSSL_SUPPORTED && useOpenSSLIfAvailable) - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_CIPHERS, enabledCiphers) - .putList(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED_PROTOCOLS, enabledProtocols) - - .put("cluster.name", clustername) - .put("client.transport.ignore_cluster_name", icl) - .put("client.transport.sniff", sniff); - - if(ksAlias != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_ALIAS, ksAlias); - } - - if(tsAlias != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_ALIAS, tsAlias); - } - + if(ks != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, ks); - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE, kst==null?(ks.endsWith(".jks")?"JKS":"PKCS12"):kst); - if(kspass == null && promptForPassword) { kspass = promptForPassword("Keystore", "kspass", OPENDISTRO_SECURITY_KS_PASS); } - - if(kspass != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD, kspass); - } } if(ts != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, ts); - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE, tst==null?(ts.endsWith(".jks")?"JKS":"PKCS12"):tst); - if(tspass == null && promptForPassword) { tspass = promptForPassword("Truststore", "tspass", OPENDISTRO_SECURITY_TS_PASS); } - - if(tspass != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, tspass); - } } - - if(cacert != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH, cacert); - } - - if(cert != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH, cert); - } - + if(key != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH, key); - + if(keypass == null && promptForPassword) { keypass = promptForPassword("Pemkey", "keypass", OPENDISTRO_SECURITY_KEYPASS); } - - if(keypass != null) { - settingsBuilder.put(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PEMKEY_PASSWORD, keypass); - } + } - Settings settings = settingsBuilder.build(); + final SSLContext sslContext = sslContext(ks, kspass, ts, tspass, cacert, cert, key, keypass, ksAlias); - try (@SuppressWarnings("resource") - TransportClient tc = new TransportClientImpl(settings, asCollection(Netty4Plugin.class, OpenSearchSecurityPlugin.class)) - .addTransportAddress(new TransportAddress(new InetSocketAddress(hostname, port)))) { + try (RestHighLevelClient restHighLevelClient = getRestHighLevelClient(sslContext, nhnv, enabledProtocols, enabledCiphers, hostname, port)) { - - final WhoAmIResponse whoAmIRes = tc.execute(WhoAmIAction.INSTANCE, new WhoAmIRequest()).actionGet(); - System.out.println("Connected as "+whoAmIRes.getDn()); + Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); + if (whoAmIRes.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to check whether cluster is sane because return code was " + whoAmIRes.getStatusLine()); + return (-1); + } - if(!whoAmIRes.isAdmin()) { - - System.out.println("ERR: "+whoAmIRes.getDn()+" is not an admin user"); - - if(!whoAmIRes.isNodeCertificateRequest()) { - System.out.println("Seems you use a client certificate but this one is not registered as admin_dn"); - System.out.println("Make sure opensearch.yml on all nodes contains:"); + JsonNode whoAmIResNode = DefaultObjectMapper.objectMapper.readTree(whoAmIRes.getEntity().getContent()); + System.out.println("Connected as " + whoAmIResNode.get("dn")); + + if (!whoAmIResNode.get("is_admin").asBoolean()) { + + System.out.println("ERR: " + whoAmIResNode.get("dn") + " is not an admin user"); + + if (!whoAmIResNode.get("is_node_certificate_request").asBoolean()) { + System.out.println("Seems you use a client certificate but this one is not registered as admin_dn"); + System.out.println("Make sure opensearch.yml on all nodes contains:"); System.out.println("plugins.security.authcz.admin_dn:"+System.lineSeparator()+ - " - \""+whoAmIRes.getDn()+"\""); + " - \"" + whoAmIResNode.get("dn") + "\""); } else { System.out.println("Seems you use a node certificate. This is not permitted, you have to use a client certificate and register it as admin_dn in opensearch.yml"); } return (-1); - } else if(whoAmIRes.isNodeCertificateRequest()) { + } else if (whoAmIResNode.get("is_node_certificate_request").asBoolean()) { System.out.println("ERR: Seems you use a node certificate which is also an admin certificate"); System.out.println(" That may have worked with older OpenSearch Security versions but it indicates"); System.out.println(" a configuration error and is therefore forbidden now."); @@ -548,7 +509,7 @@ public static int execute(final String[] args) throws Exception { } try { - if(issueWarnings(tc) != 0) { + if(issueWarnings(restHighLevelClient) != 0) { return (-1); } } catch (Exception e1) { @@ -556,22 +517,39 @@ public static int execute(final String[] args) throws Exception { throw e1; } - if(updateSettings != null) { + if(updateSettings != null) { Settings indexSettings = Settings.builder().put("index.number_of_replicas", updateSettings).build(); - ConfigUpdateResponse res = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(getTypes(true))).actionGet(); - if(res.hasFailures()) { - System.out.println("ERR: Unabe to reload config due to "+res.failures()); + Response res = restHighLevelClient.getLowLevelClient() + .performRequest(new Request("PUT", "/_plugins/_security/configupdate?config_types=" + Joiner.on(",").join(getTypes(true)))); + + if (res.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to reload configuration because return code was " + res.getStatusLine()); + return (-1); } - final AcknowledgedResponse response = tc.admin().indices().updateSettings((new UpdateSettingsRequest(index).settings(indexSettings))).actionGet(); + + JsonNode resNode = DefaultObjectMapper.objectMapper.readTree(res.getEntity().getContent()); + + if (resNode.get("configupdate_response").get("has_failures").asBoolean()) { + System.out.println("ERR: Unable to reload config due to " + responseToString(res, false) + "/" + resNode); + } + final AcknowledgedResponse response = restHighLevelClient.indices().putSettings((new UpdateSettingsRequest(index).settings(indexSettings)), RequestOptions.DEFAULT); System.out.println("Reload config on all nodes"); System.out.println("Update number of replicas to "+(updateSettings) +" with result: "+response.isAcknowledged()); - return ((response.isAcknowledged() && !res.hasFailures())?0:-1); + return ((response.isAcknowledged() && !resNode.get("configupdate_response").get("has_failures").asBoolean()) ? 0 : -1); } - - if(reload) { - ConfigUpdateResponse res = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(getTypes(false))).actionGet(); - if(res.hasFailures()) { - System.out.println("ERR: Unabe to reload config due to "+res.failures()); + + if(reload) { + Response res = restHighLevelClient.getLowLevelClient() + .performRequest(new Request("PUT", "/_plugins/_security/configupdate?config_types=" + Joiner.on(",").join(getTypes(false)))); + + if (res.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to reload configuration because return code was " + res.getStatusLine()); + return (-1); + } + + JsonNode resNode = DefaultObjectMapper.objectMapper.readTree(res.getEntity().getContent()); + if (resNode.get("configupdate_response").get("has_failures").asBoolean()) { + System.out.println("ERR: Unable to reload config due to " + responseToString(res, false) + "/" + resNode); return -1; } System.out.println("Reload config on all nodes"); @@ -581,9 +559,9 @@ public static int execute(final String[] args) throws Exception { if(si) { return (0); } - - if(whoami) { - System.out.println(whoAmIRes.toString()); + + if (whoami) { + System.out.println(whoAmIResNode.toPrettyString()); return (0); } @@ -591,25 +569,32 @@ public static int execute(final String[] args) throws Exception { if(replicaAutoExpand != null) { Settings indexSettings = Settings.builder() .put("index.auto_expand_replicas", replicaAutoExpand?"0-all":"false") - .build(); - ConfigUpdateResponse res = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(getTypes(false))).actionGet(); - if(res.hasFailures()) { - System.out.println("ERR: Unabe to reload config due to "+res.failures()); + .build(); + Response res = restHighLevelClient.getLowLevelClient().performRequest(new Request("PUT", "/_plugins/_security/configupdate?config_types=" + Joiner.on(",").join(getTypes(false)))); + + if (res.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to reload configuration because return code was " + whoAmIRes.getStatusLine()); + return (-1); } - final AcknowledgedResponse response = tc.admin().indices().updateSettings((new UpdateSettingsRequest(index).settings(indexSettings))).actionGet(); - System.out.println("Reload config on all nodes"); - System.out.println("Auto-expand replicas "+(replicaAutoExpand?"enabled":"disabled")); - return ((response.isAcknowledged() && !res.hasFailures())?0:-1); - } - - if(enableShardAllocation) { - final boolean successful = tc.admin().cluster() - .updateSettings(new ClusterUpdateSettingsRequest() - .transientSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS) - .persistentSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS)) - .actionGet() - .isAcknowledged(); - + + JsonNode resNode = DefaultObjectMapper.objectMapper.readTree(res.getEntity().getContent()); + + if (resNode.get("configupdate_response").get("has_failures").asBoolean()) { + System.out.println("ERR: Unable to reload config due to " + responseToString(res, false) + "/" + resNode); + } + final AcknowledgedResponse response = restHighLevelClient.indices().putSettings((new UpdateSettingsRequest(index).settings(indexSettings)), RequestOptions.DEFAULT); + System.out.println("Reload config on all nodes"); + System.out.println("Auto-expand replicas " + (replicaAutoExpand ? "enabled" : "disabled")); + return ((response.isAcknowledged() && !resNode.get("configupdate_response").get("has_failures").asBoolean()) ? 0 : -1); + } + + if (enableShardAllocation) { + final boolean successful = restHighLevelClient.cluster() + .putSettings(new ClusterUpdateSettingsRequest() + .transientSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS) + .persistentSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS), RequestOptions.DEFAULT) + .isAcknowledged(); + if(successful) { System.out.println("Persistent and transient shard allocation enabled"); } else { @@ -622,24 +607,24 @@ public static int execute(final String[] args) throws Exception { if(failFast) { System.out.println("Fail-fast is activated"); } - - if(diagnose) { - generateDiagnoseTrace(tc); - } - + + if (diagnose) { + generateDiagnoseTrace(restHighLevelClient); + } + System.out.println("Contacting opensearch cluster '"+clustername+"'"+(acceptRedCluster?"":" and wait for YELLOW clusterstate")+" ..."); - - ClusterHealthResponse chr = null; - - while(chr == null) { + + ClusterHealthResponse chResponse = null; + + while (chResponse == null) { try { - final ClusterHealthRequest chrequest = new ClusterHealthRequest().timeout(TimeValue.timeValueMinutes(5)); - if(!acceptRedCluster) { - chrequest.waitForYellowStatus(); - } - chr = tc.admin().cluster().health(chrequest).actionGet(); - } catch (Exception e) { - + final ClusterHealthRequest chRequest = new ClusterHealthRequest().timeout(TimeValue.timeValueMinutes(5)); + if (!acceptRedCluster) { + chRequest.waitForYellowStatus(); + } + chResponse = restHighLevelClient.cluster().health(chRequest, RequestOptions.DEFAULT); + } catch (Exception e) { + Throwable rootCause = ExceptionUtils.getRootCause(e); if(!failFast) { @@ -666,8 +651,8 @@ public static int execute(final String[] args) throws Exception { } } - final boolean timedOut = chr.isTimedOut(); - + final boolean timedOut = chResponse.isTimedOut(); + if (!acceptRedCluster && timedOut) { System.out.println("ERR: Timed out while waiting for a green or yellow cluster state."); System.out.println(" * Try running securityadmin.sh with -icl (but no -cl) and -nhnv (If that works you need to check your clustername as well as hostnames in your TLS certificates)"); @@ -676,29 +661,35 @@ public static int execute(final String[] args) throws Exception { System.out.println(" * Add --accept-red-cluster to allow securityadmin to operate on a red cluster."); return (-1); } - - System.out.println("Clustername: "+chr.getClusterName()); - System.out.println("Clusterstate: "+chr.getStatus()); - System.out.println("Number of nodes: "+chr.getNumberOfNodes()); - System.out.println("Number of data nodes: "+chr.getNumberOfDataNodes()); - + + System.out.println("Clustername: " + chResponse.getClusterName()); + System.out.println("Clusterstate: " + chResponse.getStatus()); + System.out.println("Number of nodes: " + chResponse.getNumberOfNodes()); + System.out.println("Number of data nodes: " + chResponse.getNumberOfDataNodes()); + + GetIndexResponse securityIndex = null; try { - securityIndex = tc.admin().indices().getIndex(new GetIndexRequest().indices(index).addFeatures(Feature.MAPPINGS)).actionGet(); - } catch (IndexNotFoundException e1) { + securityIndex = restHighLevelClient.indices().get(new GetIndexRequest().indices(index).addFeatures(Feature.MAPPINGS), RequestOptions.DEFAULT); + } catch (OpenSearchStatusException e1) { + if(e1.status() == RestStatus.NOT_FOUND) { //ignore + } else { + System.out.println("Unable to get index because return code was " + e1.status().getStatus()); + return (-1); + } } final boolean indexExists = securityIndex != null; - - final NodesInfoResponse nodesInfo = tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet(); + + int expectedNodeCount = restHighLevelClient.cluster().health(new ClusterHealthRequest(), RequestOptions.DEFAULT).getNumberOfNodes(); if(deleteConfigIndex) { - return deleteConfigIndex(tc, index, indexExists); + return deleteConfigIndex(restHighLevelClient, index, indexExists); } if (!indexExists) { System.out.print(index +" index does not exists, attempt to create it ... "); - final int created = createConfigIndex(tc, index, explicitReplicas); + final int created = createConfigIndex(restHighLevelClient, index, explicitReplicas); if(created != 0) { return created; } @@ -707,17 +698,17 @@ public static int execute(final String[] args) throws Exception { System.out.println(index+" index already exists, so we do not need to create one."); try { - ClusterHealthResponse chrsg = tc.admin().cluster().health(new ClusterHealthRequest(index)).actionGet(); - - if (chrsg.isTimedOut()) { + ClusterHealthResponse clusterHealthResponse = restHighLevelClient.cluster().health(new ClusterHealthRequest(index), RequestOptions.DEFAULT); + + if (clusterHealthResponse.isTimedOut()) { System.out.println("ERR: Timed out while waiting for "+index+" index state."); } - - if (chrsg.getStatus() == ClusterHealthStatus.RED) { + + if (clusterHealthResponse.getStatus() == ClusterHealthStatus.RED) { System.out.println("ERR: "+index+" index state is RED."); } - - if (chrsg.getStatus() == ClusterHealthStatus.YELLOW) { + + if (clusterHealthResponse.getStatus() == ClusterHealthStatus.YELLOW) { System.out.println("INFO: "+index+" index state is YELLOW, it seems you miss some replicas"); } @@ -745,29 +736,29 @@ public static int execute(final String[] args) throws Exception { if(legacy) { System.out.println("Legacy index '"+index+"' (ES 6) detected (or forced). You should migrate the configuration!"); } - + if(retrieve) { String date = DATE_FORMAT.format(new Date()); - - boolean success = retrieveFile(tc, cd+"config_"+date+".yml", index, "config", legacy); - success = retrieveFile(tc, cd+"roles_"+date+".yml", index, "roles", legacy) && success; - success = retrieveFile(tc, cd+"roles_mapping_"+date+".yml", index, "rolesmapping", legacy) && success; - success = retrieveFile(tc, cd+"internal_users_"+date+".yml", index, "internalusers", legacy) && success; - success = retrieveFile(tc, cd+"action_groups_"+date+".yml", index, "actiongroups", legacy) && success; - success = retrieveFile(tc, cd+"audit_"+date+".yml", index, "audit", legacy) && success; + + boolean success = retrieveFile(restHighLevelClient, cd + "config_" + date + ".yml", index, "config", legacy); + success = retrieveFile(restHighLevelClient, cd + "roles_" + date + ".yml", index, "roles", legacy) && success; + success = retrieveFile(restHighLevelClient, cd + "roles_mapping_" + date + ".yml", index, "rolesmapping", legacy) && success; + success = retrieveFile(restHighLevelClient, cd + "internal_users_" + date + ".yml", index, "internalusers", legacy) && success; + success = retrieveFile(restHighLevelClient, cd + "action_groups_" + date + ".yml", index, "actiongroups", legacy) && success; + success = retrieveFile(restHighLevelClient, cd + "audit_" + date + ".yml", index, "audit", legacy) && success; if(!legacy) { - success = retrieveFile(tc, cd+"security_tenants_"+date+".yml", index, "tenants", legacy) && success; + success = retrieveFile(restHighLevelClient, cd + "security_tenants_" + date + ".yml", index, "tenants", legacy) && success; } final boolean populateFileIfEmpty = true; - success = retrieveFile(tc, cd+"nodes_dn_"+date+".yml", index, "nodesdn", legacy, populateFileIfEmpty) && success; - success = retrieveFile(tc, cd+"whitelist_"+date+".yml", index, "whitelist", legacy, populateFileIfEmpty) && success; + success = retrieveFile(restHighLevelClient, cd+"nodes_dn_"+date+".yml", index, "nodesdn", legacy, populateFileIfEmpty) && success; + success = retrieveFile(restHighLevelClient, cd+"whitelist_"+date+".yml", index, "whitelist", legacy, populateFileIfEmpty) && success; return (success?0:-1); } if(backup != null) { - return backup(tc, index, new File(backup), legacy); + return backup(restHighLevelClient, index, new File(backup), legacy); } if(migrate != null) { @@ -775,7 +766,7 @@ public static int execute(final String[] args) throws Exception { System.out.println("ERR: Seems cluster is already migrated"); return -1; } - return migrate(tc, index, new File(migrate), nodesInfo, resolveEnvVars); + return migrate(restHighLevelClient, index, new File(migrate), expectedNodeCount, resolveEnvVars); } boolean isCdAbs = new File(cd).isAbsolute(); @@ -798,59 +789,64 @@ public static int execute(final String[] args) throws Exception { return (-1); } - boolean success = uploadFile(tc, file, index, type, legacy, resolveEnvVars); + boolean success = uploadFile(restHighLevelClient, file, index, type, legacy, resolveEnvVars); if(!success) { System.out.println("ERR: cannot upload configuration, see errors above"); return -1; } - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(new String[]{type})).actionGet(); - - success = checkConfigUpdateResponse(cur, nodesInfo, 1) && success; - + Response cur = restHighLevelClient.getLowLevelClient().performRequest(new Request("PUT", "/_plugins/_security/configupdate?config_types=" + type)); + success = checkConfigUpdateResponse(cur, expectedNodeCount, 1) && success; + System.out.println("Done with "+(success?"success":"failures")); return (success?0:-1); } - return upload(tc, index, cd, legacy, nodesInfo, resolveEnvVars); + return upload(restHighLevelClient, index, cd, legacy, expectedNodeCount, resolveEnvVars); } - // TODO audit changes to .opendistro_security index } - private static boolean checkConfigUpdateResponse(ConfigUpdateResponse response, NodesInfoResponse nir, int expectedConfigCount) { + private static boolean checkConfigUpdateResponse(Response response, int expectedNodeCount, int expectedConfigCount) throws IOException { + + if (response.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to check configupdate response because return code was " + response.getStatusLine()); + } - final int expectedNodeCount = nir.getNodes().size(); + JsonNode resNode = DefaultObjectMapper.objectMapper.readTree(response.getEntity().getContent()); - if(response.hasFailures()) { - System.out.println("FAIL: "+response.failures().size()+" nodes reported failures. First failure is "+response.failures().get(0)); + if (resNode.at("/configupdate_response/has_failures").asBoolean()) { + System.out.println("FAIL: " + resNode.at("/configupdate_response/failures_size").asInt() + " nodes reported failures. Failure is " + responseToString(response, false) + "/" + resNode); } - boolean success = response.getNodes().size() == expectedNodeCount; + + boolean success = resNode.at("/configupdate_response/node_size").asInt() == expectedNodeCount; if(!success) { - System.out.println("FAIL: Expected "+expectedNodeCount+" nodes to return response, but got "+response.getNodes().size()); + System.out.println("FAIL: Expected " + expectedNodeCount + " nodes to return response, but got " + resNode.at("/configupdate_response/node_size").asInt()); } - - for(String nodeId: response.getNodesMap().keySet()) { - ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); - boolean successNode = (node.getUpdatedConfigTypes() != null && node.getUpdatedConfigTypes().length == expectedConfigCount); + + for (JsonNode n : resNode.at("/configupdate_response/nodes")) { + boolean successNode = (n.get("updated_config_types") != null && n.get("updated_config_size").asInt() == expectedConfigCount); + if(!successNode) { - System.out.println("FAIL: Expected "+expectedConfigCount+" config types for node "+nodeId+" but got "+node.getUpdatedConfigTypes().length+" ("+Arrays.toString(node.getUpdatedConfigTypes()) + ") due to: "+(node.getMessage()==null?"unknown reason":node.getMessage())); + System.out.println("FAIL: Expected " + expectedConfigCount + " config types for node " + n + " but got " + n.get("updated_config_size").asInt() + " (" + n.get("updated_config_types") + ") due to: " + (n.get("message") == null ? "unknown reason" : n.get("message"))); + } else { + System.out.println("SUCC: Expected " + expectedConfigCount + " config types for node " + n + " is " + n.get("updated_config_size").asInt() + " (" + n.get("updated_config_types") + ") due to: " + (n.get("message") == null ? "unknown reason" : n.get("message"))); } success = success && successNode; } - return success && !response.hasFailures(); + return success && !resNode.at("/configupdate_response/has_failures").asBoolean(); } - private static boolean uploadFile(final Client tc, final String filepath, final String index, final String _id, final boolean legacy, boolean resolveEnvVars) { - return uploadFile(tc, filepath, index, _id, legacy, resolveEnvVars, false); + private static boolean uploadFile(final RestHighLevelClient restHighLevelClient, final String filepath, final String index, final String _id, final boolean legacy, boolean resolveEnvVars) { + return uploadFile(restHighLevelClient, filepath, index, _id, legacy, resolveEnvVars, false); } - private static boolean uploadFile(final Client tc, final String filepath, final String index, final String _id, final boolean legacy, boolean resolveEnvVars, + private static boolean uploadFile(final RestHighLevelClient restHighLevelClient, final String filepath, final String index, final String _id, final boolean legacy, boolean resolveEnvVars, final boolean populateEmptyIfMissing) { - + String type = "_doc"; String id = _id; @@ -874,13 +870,14 @@ private static boolean uploadFile(final Client tc, final String filepath, final } } - System.out.println("Will update '"+type+"/" + id + "' with " + filepath+" "+(legacy?"(legacy mode)":"")); - - try (Reader reader = ConfigHelper.createFileOrStringReader(CType.fromString(_id), legacy ? 1 : 2, filepath, populateEmptyIfMissing)) { - final String content = CharStreams.toString(reader); - final String res = tc - .index(new IndexRequest(index).type(type).id(id).setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .source(_id, readXContent(resolveEnvVars? SecurityUtils.replaceEnvVars(content, Settings.EMPTY):content, XContentType.YAML))).actionGet().getId(); + System.out.println("Will update '" + type + "/" + id + "' with " + filepath + " " + (legacy ? "(legacy mode)" : "")); + + try (Reader reader = ConfigHelper.createFileOrStringReader(CType.fromString(_id), legacy ? 1 : 2, filepath, populateEmptyIfMissing)) { + final String content = CharStreams.toString(reader); + final String res = restHighLevelClient + .index(new IndexRequest(index).type(type).id(id).setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .source(_id, readXContent(resolveEnvVars ? replaceEnvVars(content, Settings.EMPTY) : content, XContentType.YAML)), RequestOptions.DEFAULT).getId(); + if (id.equals(res)) { System.out.println(" SUCC: Configuration for '" + _id + "' created or updated"); @@ -896,12 +893,11 @@ private static boolean uploadFile(final Client tc, final String filepath, final return false; } - private static boolean retrieveFile(final Client tc, final String filepath, final String index, final String _id, final boolean legacy) { - return retrieveFile(tc, filepath, index, _id, legacy, false); - } + private static boolean retrieveFile(final RestHighLevelClient restHighLevelClient, final String filepath, final String index, final String _id, final boolean legacy) { + return retrieveFile(restHighLevelClient, filepath, index, _id, legacy, false); + } - private static boolean retrieveFile(final Client tc, final String filepath, final String index, final String _id, final boolean legacy, final boolean populateFileIfEmpty) { - + private static boolean retrieveFile(final RestHighLevelClient restHighLevelClient, final String filepath, final String index, final String _id, final boolean legacy, final boolean populateFileIfEmpty) { String type = "_doc"; String id = _id; @@ -914,7 +910,7 @@ private static boolean retrieveFile(final Client tc, final String filepath, fina System.out.println("Will retrieve '"+type+"/" +id+"' into "+filepath+" "+(legacy?"(legacy mode)":"")); try (Writer writer = new FileWriter(filepath)) { - final GetResponse response = tc.get(new GetRequest(index).type(type).id(id).refresh(true).realtime(false)).actionGet(); + final GetResponse response = restHighLevelClient.get(new GetRequest(index).type(type).id(id).refresh(true).realtime(false), RequestOptions.DEFAULT); boolean isEmpty = !response.isExists() || response.isSourceEmpty(); String yaml; @@ -945,7 +941,7 @@ private static boolean retrieveFile(final Client tc, final String filepath, fina try { ConfigHelper.fromYamlString(yaml, CType.fromString(_id), 2, 0, 0); } catch (Exception e) { - System.out.println("ERR: Seems " + _id + " from cluster is not in SG 7 format: " + e); + System.out.println("ERR: Seems " + _id + " from cluster is not in 7 format: " + e); return false; } } @@ -1001,24 +997,8 @@ private static String convertToYaml(String type, BytesReference bytes, boolean p } } - protected static class TransportClientImpl extends TransportClient { - - public TransportClientImpl(Settings settings, Collection> plugins) { - super(settings, plugins); - } - - public TransportClientImpl(Settings settings, Settings defaultSettings, Collection> plugins) { - super(settings, defaultSettings, plugins, null); - } - } - - @SafeVarargs - protected static Collection> asCollection(Class... plugins) { - return Arrays.asList(plugins); - } + protected static void generateDiagnoseTrace(final RestHighLevelClient restHighLevelClient) { - protected static void generateDiagnoseTrace(final Client tc) { - final String date = DATE_FORMAT.format(new Date()); final StringBuilder sb = new StringBuilder(); @@ -1027,55 +1007,55 @@ protected static void generateDiagnoseTrace(final Client tc) { sb.append("Client properties: "+System.getProperties()+System.lineSeparator()); sb.append(date+System.lineSeparator()); sb.append(System.lineSeparator()); - + try { sb.append("Who am i:"+System.lineSeparator()); - final WhoAmIResponse whoAmIRes = tc.execute(WhoAmIAction.INSTANCE, new WhoAmIRequest()).actionGet(); - sb.append(Strings.toString(whoAmIRes,true, true)); + final Response whoAmIRes = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_plugins/_security/whoami")); + sb.append(responseToString(whoAmIRes, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { sb.append("ClusterHealthRequest:"+System.lineSeparator()); - ClusterHealthResponse nir = tc.admin().cluster().health(new ClusterHealthRequest()).actionGet(); - sb.append(Strings.toString(nir,true, true)); + ClusterHealthResponse nir = restHighLevelClient.cluster().health(new ClusterHealthRequest(), RequestOptions.DEFAULT); + sb.append(Strings.toString(nir, true, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { sb.append(System.lineSeparator()+"NodesInfoResponse:"+System.lineSeparator()); - NodesInfoResponse nir = tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet(); - sb.append(Strings.toString(nir,true, true)); + Response nir = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_nodes")); + sb.append(responseToString(nir, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { sb.append(System.lineSeparator()+"NodesStatsRequest:"+System.lineSeparator()); - NodesStatsResponse nir = tc.admin().cluster().nodesStats(new NodesStatsRequest()).actionGet(); - sb.append(Strings.toString(nir,true, true)); + Response nir = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_nodes/stats")); + sb.append(responseToString(nir, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { sb.append(System.lineSeparator()+"PendingClusterTasksRequest:"+System.lineSeparator()); - PendingClusterTasksResponse nir = tc.admin().cluster().pendingClusterTasks(new PendingClusterTasksRequest()).actionGet(); - sb.append(Strings.toString(nir,true, true)); + Response nir = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_cluster/pending_tasks")); + sb.append(responseToString(nir, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { sb.append(System.lineSeparator()+"IndicesStatsRequest:"+System.lineSeparator()); - IndicesStatsResponse nir = tc.admin().indices().stats(new IndicesStatsRequest()).actionGet(); - sb.append(Strings.toString(nir, true, true)); + Response nir = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_stats")); + sb.append(responseToString(nir, true)); } catch (Exception e1) { sb.append(ExceptionsHelper.stackTrace(e1)); } - + try { File dfile = new File("securityadmin_diag_trace_"+date+".txt"); Files.asCharSink(dfile, StandardCharsets.UTF_8).write(sb); @@ -1084,9 +1064,9 @@ protected static void generateDiagnoseTrace(final Client tc) { System.out.println("ERR: cannot write diag trace file due to "+e1); } } - - private static void validate(CommandLine line) throws ParseException{ - + + private static void validate(CommandLine line) throws ParseException { + if(line.hasOption("ts") && line.hasOption("cacert")) { System.out.println("WARN: It makes no sense to specify -ts as well as -cacert"); } @@ -1134,12 +1114,26 @@ private static String promptForPassword(String passwordName, String commandLineO } return new String(console.readPassword("[%s]", passwordName+" password:")); } - - private static int issueWarnings(Client tc) { - NodesInfoResponse nir = tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet(); - Version maxVersion = nir.getNodes().stream().max((n1,n2) -> n1.getVersion().compareTo(n2.getVersion())).get().getVersion(); - Version minVersion = nir.getNodes().stream().min((n1,n2) -> n1.getVersion().compareTo(n2.getVersion())).get().getVersion(); - + + private static int issueWarnings(RestHighLevelClient restHighLevelClient) throws IOException { + Response res = restHighLevelClient.getLowLevelClient().performRequest(new Request("GET", "/_nodes")); + + if (res.getStatusLine().getStatusCode() != 200) { + System.out.println("Unable to get nodes " + res.getStatusLine()); + return -1; + } + + JsonNode resNode = DefaultObjectMapper.objectMapper.readTree(res.getEntity().getContent()); + + int nodeCount = Iterators.size(resNode.at("/nodes").iterator()); + + if (nodeCount > 0) { + + JsonNode[] nodeVersions = Iterators.toArray(resNode.at("/nodes").iterator(), JsonNode.class); + + Version maxVersion = Version.fromString(Arrays.stream(nodeVersions).max((n1, n2) -> Version.fromString(n1.asText()).compareTo(Version.fromString(n2.asText()))).get().asText()); + Version minVersion = Version.fromString(Arrays.stream(nodeVersions).min((n1, n2) -> Version.fromString(n1.asText()).compareTo(Version.fromString(n2.asText()))).get().asText()); + if(!maxVersion.equals(minVersion)) { System.out.println("ERR: Your cluster consists of different node versions. It is not allowed to run securityadmin against a mixed cluster."); System.out.println(" Minimum node version is "+minVersion.toString()); @@ -1151,21 +1145,27 @@ private static int issueWarnings(Client tc) { } else { System.out.println("OpenSearch Version: "+minVersion.toString()); } - - if(nir.getNodes().size() > 0) { - List pluginInfos = nir.getNodes().get(0).getInfo(PluginsAndModules.class).getPluginInfos(); - String securityVersion = pluginInfos.stream().filter(p->p.getClassname().equals("org.opensearch.security.OpenSearchSecurityPlugin")).map(p->p.getVersion()).findFirst().orElse(""); - System.out.println("OpenSearch Security Version: "+securityVersion); + + + for (JsonNode n : nodeVersions[0].get("plugins")) { + if ("org.opensearch.security.OpenSearchSecurityPlugin".equals(n.get("name").asText())) { + System.out.println("OpenSearch Security Version: " + n.get("version")); + break; + } + } + + } else { + System.out.println("ERR: Your cluster consists of zero nodes"); } - + return 0; } - - private static int deleteConfigIndex(TransportClient tc, String index, boolean indexExists) { + + private static int deleteConfigIndex(RestHighLevelClient restHighLevelClient, String index, boolean indexExists) throws IOException { boolean success = true; - + if(indexExists) { - success = tc.admin().indices().delete(new DeleteIndexRequest(index)).actionGet().isAcknowledged(); + success = restHighLevelClient.indices().delete(new DeleteIndexRequest(index), RequestOptions.DEFAULT).isAcknowledged(); System.out.print("Deleted index '"+index+"'"); } else { System.out.print("No index '"+index+"' exists, so no need to delete it"); @@ -1173,8 +1173,8 @@ private static int deleteConfigIndex(TransportClient tc, String index, boolean i return (success?0:-1); } - - private static int createConfigIndex(TransportClient tc, String index, String explicitReplicas) { + + private static int createConfigIndex(RestHighLevelClient restHighLevelClient, String index, String explicitReplicas) throws IOException { Map indexSettings = new HashMap<>(); indexSettings.put("index.number_of_shards", 1); @@ -1188,9 +1188,9 @@ private static int createConfigIndex(TransportClient tc, String index, String ex indexSettings.put("index.auto_expand_replicas", "0-all"); } - final boolean indexCreated = tc.admin().indices().create(new CreateIndexRequest(index) - .settings(indexSettings)) - .actionGet().isAcknowledged(); + final boolean indexCreated = restHighLevelClient.indices().create(new CreateIndexRequest(index) + .settings(indexSettings), RequestOptions.DEFAULT) + .isAcknowledged(); if (indexCreated) { System.out.println("done ("+(explicitReplicas!=null?explicitReplicas:"0-all")+" replicas)"); @@ -1201,17 +1201,17 @@ private static int createConfigIndex(TransportClient tc, String index, String ex return (-1); } } - - private static int backup(TransportClient tc, String index, File backupDir, boolean legacy) { + + private static int backup(RestHighLevelClient tc, String index, File backupDir, boolean legacy) { backupDir.mkdirs(); - + boolean success = retrieveFile(tc, backupDir.getAbsolutePath()+"/config.yml", index, "config", legacy); success = retrieveFile(tc, backupDir.getAbsolutePath()+"/roles.yml", index, "roles", legacy) && success; - + success = retrieveFile(tc, backupDir.getAbsolutePath()+"/roles_mapping.yml", index, "rolesmapping", legacy) && success; success = retrieveFile(tc, backupDir.getAbsolutePath()+"/internal_users.yml", index, "internalusers", legacy) && success; success = retrieveFile(tc, backupDir.getAbsolutePath()+"/action_groups.yml", index, "actiongroups", legacy) && success; - + if(!legacy) { success = retrieveFile(tc, backupDir.getAbsolutePath()+"/tenants.yml", index, "tenants", legacy) && success; } @@ -1221,9 +1221,9 @@ private static int backup(TransportClient tc, String index, File backupDir, bool return success?0:-1; } - - private static int upload(TransportClient tc, String index, String cd, boolean legacy, NodesInfoResponse nodesInfo, boolean resolveEnvVars) { - boolean success = uploadFile(tc, cd+"config.yml", index, "config", legacy, resolveEnvVars); + + private static int upload(RestHighLevelClient tc, String index, String cd, boolean legacy, int expectedNodeCount, boolean resolveEnvVars) throws IOException { + boolean success = uploadFile(tc, cd + "config.yml", index, "config", legacy, resolveEnvVars); success = uploadFile(tc, cd+"roles.yml", index, "roles", legacy, resolveEnvVars) && success; success = uploadFile(tc, cd+"roles_mapping.yml", index, "rolesmapping", legacy, resolveEnvVars) && success; @@ -1245,17 +1245,16 @@ private static int upload(TransportClient tc, String index, String cd, boolean l System.out.println("ERR: cannot upload configuration, see errors above"); return -1; } - - ConfigUpdateResponse cur = tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(getTypes(legacy))).actionGet(); - success = checkConfigUpdateResponse(cur, nodesInfo, getTypes(legacy).length) && success; + Response cur = tc.getLowLevelClient().performRequest(new Request("PUT", "/_plugins/_security/configupdate?config_types=" + Joiner.on(",").join(getTypes((legacy))))); + success = checkConfigUpdateResponse(cur, expectedNodeCount, getTypes(legacy).length) && success; System.out.println("Done with "+(success?"success":"failures")); return (success?0:-1); } - - private static int migrate(TransportClient tc, String index, File backupDir, NodesInfoResponse nodesInfo, boolean resolveEnvVars) { - + + private static int migrate(RestHighLevelClient tc, String index, File backupDir, int expectedNodeCount, boolean resolveEnvVars) throws IOException { + System.out.println("== Migration started =="); System.out.println("======================="); @@ -1313,7 +1312,7 @@ private static int migrate(TransportClient tc, String index, File backupDir, Nod System.out.println("-> Upload new configuration into OpenSearch cluster"); - int uploadResult = upload(tc, index, v7Dir.getAbsolutePath()+"/", false, nodesInfo, resolveEnvVars); + int uploadResult = upload(tc, index, v7Dir.getAbsolutePath() + "/", false, expectedNodeCount, resolveEnvVars); if(uploadResult == 0) { System.out.println(" done"); @@ -1349,7 +1348,7 @@ private static int validateConfig(String cd, String file, String type, int versi ConfigHelper.fromYamlFile(file, CType.fromString(type), version==7?2:1, 0, 0); return 0; } catch (Exception e) { - System.out.println("ERR: Seems "+file+" is not in SG "+version+" format: "+e); + System.out.println("ERR: Seems "+file+" is not in "+version+" format: "+e); return -1; } } else if(cd != null) { @@ -1379,15 +1378,177 @@ private static boolean validateConfigFile(String file, CType cType, int version) System.out.println(file+" OK" ); return true; } catch (Exception e) { - System.out.println("ERR: Seems "+file+" is not in SG "+version+" format: "+e); + System.out.println("ERR: Seems "+file+" is not in "+version+" format: "+e); return false; } } - - private static String[] getTypes(boolean legacy) { - if(legacy) { - return new String[]{"config","roles","rolesmapping","internalusers","actiongroups","nodesdn", "audit"}; - } - return CType.lcStringValues().toArray(new String[0]); - } + + private static String[] getTypes(boolean legacy) { + if (legacy) { + return new String[]{"config", "roles", "rolesmapping", "internalusers", "actiongroups", "nodesdn", "audit"}; + } + return CType.lcStringValues().toArray(new String[0]); + } + + + private static RestHighLevelClient getRestHighLevelClient(SSLContext sslContext, + boolean nhnv, + String[] enabledProtocols, + String[] enabledCiphers, + String hostname, + int port) { + + final HostnameVerifier hnv = !nhnv ? new DefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE; + + String[] supportedProtocols = enabledProtocols.length > 0 ? enabledProtocols : null; + String[] supportedCipherSuites = enabledCiphers.length > 0 ? enabledCiphers : null; + + HttpHost httpHost = new HttpHost(hostname, port, "https"); + + RestClientBuilder restClientBuilder = RestClient.builder(httpHost) + .setHttpClientConfigCallback( + builder -> builder.setSSLStrategy( + new SSLIOSessionStrategy( + sslContext, + supportedProtocols, + supportedCipherSuites, + hnv + ) + ) + ); + return new RestHighLevelClient(restClientBuilder); + } + + + private static SSLContext sslContext( + String ks, + String kspass, + String ts, + String tspass, + + String cacert, + String cert, + String key, + String keypass, + String ksAlias) throws Exception { + + KeyStore trustStore = null; + KeyStore keyStore = null; + + if (ks != null) { + File keyStoreFile = Paths.get(ks).toFile(); + String keyStoreFileName = keyStoreFile.getName(); + + String type = getType(keyStoreFileName); + + keyStore = KeyStore.getInstance(type.toUpperCase()); + keyStore.load(new FileInputStream(keyStoreFile), kspass == null ? null : kspass.toCharArray()); + } + + if (ts != null) { + File trustStoreFile = Paths.get(ts).toFile(); + String trustStoreFileName = trustStoreFile.getName(); + + String type = getType(trustStoreFileName); + + trustStore = KeyStore.getInstance(type.toUpperCase()); + trustStore.load(new FileInputStream(trustStoreFile), tspass == null ? null : tspass.toCharArray()); + } + + if (cacert != null) { + File caCertFile = Paths.get(cacert).toFile(); + try (FileInputStream in = new FileInputStream(caCertFile)) { + X509Certificate[] certificates = PemKeyReader.loadCertificatesFromStream(in); + trustStore = PemKeyReader.toTruststore("al", certificates); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Could not find certificate file " + caCertFile, e); + } catch (IOException | CertificateException e) { + throw new IllegalArgumentException("Error while reading certificate file " + caCertFile, e); + } + } + + String pass = cert != null ? keypass : kspass; + + if (cert != null && key != null) { + File certFile = Paths.get(cert).toFile(); + X509Certificate[] certificates; + PrivateKey privateKey; + try (FileInputStream in = new FileInputStream(certFile)) { + certificates = PemKeyReader.loadCertificatesFromStream(in); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Could not find certificate file " + certFile, e); + } catch (IOException | CertificateException e) { + throw new IllegalArgumentException("Error while reading certificate file " + certFile, e); + } + + File keyFile = Paths.get(key).toFile(); + try (FileInputStream in = new FileInputStream(keyFile)) { + privateKey = PemKeyReader.toPrivateKey(in, keypass); + } catch (FileNotFoundException e) { + throw new IllegalArgumentException("Could not find certificate key file " + keyFile, e); + } catch (IOException e) { + throw new IllegalArgumentException("Error while reading certificate key file " + keyFile, e); + } + + if(pass == null){ + pass = "hardcoded"; + } + keyStore = PemKeyReader.toKeystore("al",pass.toCharArray(), certificates, privateKey); + } + + + final SSLContextBuilder sslContextBuilder = SSLContexts.custom(); + + + if (trustStore != null) { + sslContextBuilder.loadTrustMaterial(trustStore, null); + } + + if (keyStore != null) { + sslContextBuilder.loadKeyMaterial(keyStore, pass.toCharArray(), (aliases, socket) -> { + if (aliases == null || aliases.isEmpty()) { + return ksAlias; + } + + if (ksAlias == null || ksAlias.isEmpty()) { + return aliases.keySet().iterator().next(); + } + + return ksAlias; + }); + } + return sslContextBuilder.build(); + } + + private static String getType(String trustStoreFileName) { + if (trustStoreFileName.endsWith(".jks")) { + return "JKS"; + } else if (trustStoreFileName.endsWith(".pfx") || trustStoreFileName.endsWith(".p12")) { + return "PKCS12"; + } else { + throw new IllegalArgumentException("Unknwon file type: " + trustStoreFileName); + } + } + + private static String responseToString(Response response, boolean prettyJson) { + ByteSource byteSource = new ByteSource() { + @Override + public InputStream openStream() throws IOException { + return response.getEntity().getContent(); + } + }; + + try { + String value = byteSource.asCharSource(Charsets.UTF_8).read(); + + if (prettyJson) { + return DefaultObjectMapper.objectMapper.readTree(value).toPrettyString(); + } + + return value; + } catch (Exception e) { + e.printStackTrace(); + return "ERR: Unable to handle response due to " + e; + } + } } diff --git a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java new file mode 100644 index 0000000000..1ac171d4d1 --- /dev/null +++ b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.security; + +import org.apache.http.HttpStatus; +import org.junit.Assert; +import org.junit.Test; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.test.SingleClusterTest; +import org.opensearch.security.test.helper.file.FileHelper; +import org.opensearch.security.test.helper.rest.RestHelper; + +public class SecurityAdminIEndpointsTests extends SingleClusterTest { + + @Test public void testNoSSL() throws Exception { + final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", false).build(); + setup(settings); + final RestHelper rh = nonSslRestHelper(); + + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_plugins/_security/whoami").getStatusCode()); + } + + @Test public void testEndpoints() throws Exception { + final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .putList("plugins.security.nodes_dn", "CN=node-*.example.com,OU=SSL,O=Test,L=Test,C=DE").build(); + setup(settings); + final RestHelper rh = restHelper(); + rh.enableHTTPClientSSL = true; + rh.trustHTTPServerCertificate = true; + rh.sendAdminCertificate = false; + + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + + RestHelper.HttpResponse res; + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_plugins/_security/whoami")).getStatusCode()); + + assertContains(res, "*\"dn\":null*"); + + rh.sendAdminCertificate = true; + + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_plugins/_security/whoami")).getStatusCode()); + + assertContains(res, "*\"dn\":\"CN=node-0.example.com*"); + assertContains(res, "*\"is_admin\":false*"); + assertContains(res, "*\"is_node_certificate_request\":true*"); + + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + + rh.keystore = "spock-keystore.jks"; + + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_plugins/_security/whoami")).getStatusCode()); + + assertContains(res, "*\"dn\":\"CN=spock*"); + assertContains(res, "*\"is_admin\":false*"); + assertContains(res, "*\"is_node_certificate_request\":false*"); + + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_UNAUTHORIZED, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_FORBIDDEN, + rh.executePutRequest("_plugins/_security/configupdate?config_types=xxx", "", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + + rh.keystore = "kirk-keystore.jks"; + + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_plugins/_security/whoami")).getStatusCode()); + + assertContains(res, "*\"dn\":\"CN=kirk*"); + assertContains(res, "*\"is_admin\":true*"); + assertContains(res, "*\"is_node_certificate_request\":false*"); + + Assert.assertEquals(HttpStatus.SC_OK, + rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "{}", encodeBasicHeader("nagilum", "nagilum")) + .getStatusCode()); + Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, rh.executePutRequest("_plugins/_security/configupdate", "").getStatusCode()); + Assert.assertEquals(HttpStatus.SC_OK, rh.executePutRequest("_plugins/_security/configupdate?config_types=roles", "").getStatusCode()); + + Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executePutRequest("_plugins/_security/configupdate?config_types=unknown_xxx", "", + encodeBasicHeader("nagilum", "nagilum"))).getStatusCode()); + assertContains(res, "*\"successful\":0*failed_node_exception*"); + + } + +} \ No newline at end of file diff --git a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java index bdbad4fdb5..f8a6834c6f 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminInvalidConfigsTests.java @@ -34,6 +34,7 @@ import java.util.ArrayList; import java.util.List; +import org.opensearch.common.settings.Settings; import org.opensearch.security.tools.SecurityAdmin; import org.apache.http.HttpStatus; import org.junit.Assert; @@ -47,7 +48,12 @@ public class SecurityAdminInvalidConfigsTests extends SingleClusterTest { @Test public void testSecurityAdminDuplicateKey() throws Exception { - setup(); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(settings); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -57,7 +63,7 @@ public void testSecurityAdminDuplicateKey() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-cd"); @@ -68,7 +74,7 @@ public void testSecurityAdminDuplicateKey() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -87,7 +93,7 @@ public void testSecurityAdminDuplicateKeyReload() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-rl"); @@ -97,7 +103,7 @@ public void testSecurityAdminDuplicateKeyReload() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -106,7 +112,12 @@ public void testSecurityAdminDuplicateKeyReload() throws Exception { @Test public void testSecurityAdminDuplicateKeySingleFile() throws Exception { - setup(); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(settings); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -116,7 +127,7 @@ public void testSecurityAdminDuplicateKeySingleFile() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -129,7 +140,7 @@ public void testSecurityAdminDuplicateKeySingleFile() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); @@ -148,7 +159,7 @@ public void testSecurityAdminDuplicateKeyReloadSingleFile() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-rl"); @@ -158,7 +169,7 @@ public void testSecurityAdminDuplicateKeyReloadSingleFile() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); Assert.assertEquals(HttpStatus.SC_OK, rh.executeGetRequest("_opendistro/_security/authinfo?pretty", encodeBasicHeader("nagilum", "nagilum")).getStatusCode()); diff --git a/src/test/java/org/opensearch/security/SecurityAdminMigrationTests.java b/src/test/java/org/opensearch/security/SecurityAdminMigrationTests.java index e7865ad122..c0a3ad00d8 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminMigrationTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminMigrationTests.java @@ -60,7 +60,7 @@ public void testSecurityMigrate() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-migrate"); @@ -107,7 +107,7 @@ public void testSecurityMigrate2() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-cd"); diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index 062c927771..42f02842b5 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -38,7 +38,12 @@ public class SecurityAdminTests extends SingleClusterTest { @Test public void testSecurityAdmin() throws Exception { - setup(Settings.EMPTY, null, Settings.EMPTY, false); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, null, settings, false); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -48,7 +53,7 @@ public void testSecurityAdmin() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-cd"); @@ -59,15 +64,89 @@ public void testSecurityAdmin() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); - HttpResponse res; - - Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); } - + + @Test + public void testSgAdminInvalidCert() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-cd"); + argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_plugins/_security/health?pretty")).getStatusCode()); + + argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"spock-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-cd"); + argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + argsAsList.add("--diagnose"); + argsAsList.add("-nhnv"); + + + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(-1, returnCode); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_plugins/_security/health?pretty")).getStatusCode()); + + argsAsList = new ArrayList<>(); + argsAsList.add("-ts"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"truststore.jks").toFile().getAbsolutePath()); + argsAsList.add("-ks"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"node-0-keystore.jks").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-cn"); + argsAsList.add(clusterInfo.clustername); + argsAsList.add("-cd"); + argsAsList.add(new File("src/test/resources/").getAbsolutePath()); + argsAsList.add("-nhnv"); + + returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(-1, returnCode); + + Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_plugins/_security/health?pretty")).getStatusCode()); + } + @Test public void testSecurityAdminV6Update() throws Exception { - setup(Settings.EMPTY, null, Settings.EMPTY, false); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, null, settings, false); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -77,7 +156,7 @@ public void testSecurityAdminV6Update() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-cd"); @@ -88,10 +167,9 @@ public void testSecurityAdminV6Update() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); - HttpResponse res; - - Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); + RestHelper rh = restHelper(); + + Assert.assertEquals(HttpStatus.SC_SERVICE_UNAVAILABLE, rh.executeGetRequest("_opendistro/_security/health?pretty").getStatusCode()); //System.out.println(res.getBody()); //assertContains(res, "*UP*"); //assertContains(res, "*strict*"); @@ -100,7 +178,12 @@ public void testSecurityAdminV6Update() throws Exception { @Test public void testSecurityAdminRegularUpdate() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, null, settings, true); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -110,7 +193,7 @@ public void testSecurityAdminRegularUpdate() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-cd"); @@ -121,7 +204,7 @@ public void testSecurityAdminRegularUpdate() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); @@ -133,7 +216,12 @@ public void testSecurityAdminRegularUpdate() throws Exception { @Test public void testSecurityAdminSingularV7Updates() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true); + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; @@ -143,7 +231,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -162,7 +250,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -181,7 +269,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -194,7 +282,7 @@ public void testSecurityAdminSingularV7Updates() throws Exception { returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); @@ -206,8 +294,13 @@ public void testSecurityAdminSingularV7Updates() throws Exception { @Test public void testSecurityAdminSingularV6Updates() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true); - + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; List argsAsList = new ArrayList<>(); @@ -216,7 +309,7 @@ public void testSecurityAdminSingularV6Updates() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -229,7 +322,7 @@ public void testSecurityAdminSingularV6Updates() throws Exception { Assert.assertNotEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); @@ -241,8 +334,13 @@ public void testSecurityAdminSingularV6Updates() throws Exception { @Test public void testSecurityAdminInvalidYml() throws Exception { - setup(Settings.EMPTY, new DynamicSecurityConfig(), Settings.EMPTY, true); - + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) + .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) + .build(); + setup(Settings.EMPTY, new DynamicSecurityConfig(), settings, true); + final String prefix = getResourceFolder()==null?"":getResourceFolder()+"/"; List argsAsList = new ArrayList<>(); @@ -251,7 +349,7 @@ public void testSecurityAdminInvalidYml() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-f"); @@ -264,7 +362,7 @@ public void testSecurityAdminInvalidYml() throws Exception { int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); Assert.assertNotEquals(0, returnCode); - RestHelper rh = nonSslRestHelper(); + RestHelper rh = restHelper(); HttpResponse res; Assert.assertEquals(HttpStatus.SC_OK, (res = rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); @@ -301,7 +399,7 @@ public void testSecurityAdminReloadInvalidConfig() throws Exception { argsAsList.add("-ks"); argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk-keystore.jks").toFile().getAbsolutePath()); argsAsList.add("-p"); - argsAsList.add(String.valueOf(clusterInfo.nodePort)); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); argsAsList.add("-cn"); argsAsList.add(clusterInfo.clustername); argsAsList.add("-rl"); From 04d5dcb6dd6b93b1cfca55c6c41b889da9ccc1a3 Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Tue, 22 Feb 2022 21:33:48 +0100 Subject: [PATCH 2/6] Fix 'BindException' in some tests Implement getting free port in tests: SinkProviderTLSTest and WebhookAuditLogTest Signed-off-by: rs-eliatra --- .../auditlog/sink/SinkProviderTLSTest.java | 18 ++++++++- .../auditlog/sink/WebhookAuditLogTest.java | 40 +++++++++++++------ 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java index 4f71ca577c..65ef61f35d 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/SinkProviderTLSTest.java @@ -16,7 +16,9 @@ package org.opensearch.security.auditlog.sink; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.ServerSocket; import java.security.KeyStore; import javax.net.ssl.KeyManagerFactory; @@ -59,7 +61,8 @@ public void testTlsConfigurationNoFallback() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - server = ServerBootstrap.bootstrap().setListenerPort(8083).setServerInfo("Test/1.1").setSslContext(createSSLContext()).registerHandler("*", handler).create(); + int port = findFreePort(); + server = ServerBootstrap.bootstrap().setListenerPort(port).setServerInfo("Test/1.1").setSslContext(createSSLContext()).registerHandler("*", handler).create(); server.start(); @@ -71,6 +74,11 @@ public void testTlsConfigurationNoFallback() throws Exception { builder.put("plugins.security.audit.endpoints.endpoint1.config.webhook.ssl.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("auditlog/root-ca.pem")); builder.put("plugins.security.audit.endpoints.endpoint2.config.webhook.ssl.pemtrustedcas_content", FileHelper.loadFile("auditlog/root-ca.pem")); + builder.put("plugins.security.audit.config.webhook.url", "https://localhost:" + port); + builder.put("plugins.security.audit.endpoints.endpoint1.config.webhook.url", "https://localhost:" + port); + builder.put("plugins.security.audit.endpoints.endpoint2.config.webhook.url", "https://localhost:" + port); + + SinkProvider provider = new SinkProvider(builder.build(), null, null, null); WebhookSink defaultSink = (WebhookSink) provider.defaultSink; Assert.assertEquals(true, defaultSink.verifySSL); @@ -141,4 +149,12 @@ private void assertStringContainsAllKeysAndValues(String in) { Assert.assertTrue(in, in.contains("8.8.8.8")); //Assert.assertTrue(in, in.contains("CN=kirk,OU=client,O=client,L=test,C=DE")); } + + private int findFreePort() { + try (ServerSocket serverSocket = new ServerSocket(0)) { + return serverSocket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Failed to find free port", e); + } + } } diff --git a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java index e28f818397..2efe9ad40c 100644 --- a/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java +++ b/src/test/java/org/opensearch/security/auditlog/sink/WebhookAuditLogTest.java @@ -16,7 +16,9 @@ package org.opensearch.security.auditlog.sink; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.ServerSocket; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.security.KeyStore; @@ -222,15 +224,16 @@ public void noServerRunningHttpTest() throws Exception { public void postGetHttpTest() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8080) + .setListenerPort(port) .setServerInfo("Test/1.1") .registerHandler("*", handler) .create(); server.start(); - String url = "http://localhost:8080/endpoint"; + String url = "http://localhost:" + port + "/endpoint"; // SLACK Settings settings = Settings.builder() @@ -327,15 +330,16 @@ public void httpsTestWithoutTLSServer() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8081) + .setListenerPort(port) .setServerInfo("Test/1.1") .registerHandler("*", handler) .create(); server.start(); - String url = "https://localhost:8081/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; Settings settings = Settings.builder() .put("plugins.security.audit.config.webhook.url", url) @@ -363,9 +367,9 @@ public void httpsTestWithoutTLSServer() throws Exception { public void httpsTest() throws Exception { TestHttpHandler handler = new TestHttpHandler(); - + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8090) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -374,7 +378,7 @@ public void httpsTest() throws Exception { server.start(); AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); - String url = "https://localhost:8090/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // try with ssl verification on, no trust ca, must fail Settings settings = Settings.builder() @@ -445,8 +449,8 @@ public void httpsTest() throws Exception { @Test public void httpsTestPemDefault() throws Exception { - final int port = 8088; - TestHttpHandler handler = new TestHttpHandler(); + final int port = findFreePort(); + TestHttpHandler handler = new TestHttpHandler(); server = ServerBootstrap.bootstrap() .setListenerPort(port) @@ -561,9 +565,10 @@ public void httpsTestPemDefault() throws Exception { public void httpsTestPemEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8091) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -573,7 +578,7 @@ public void httpsTestPemEndpoint() throws Exception { AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - String url = "https://localhost:8091/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // test default with filepath handler.reset(); @@ -658,9 +663,10 @@ public void httpsTestPemEndpoint() throws Exception { public void httpsTestPemContentEndpoint() throws Exception { TestHttpHandler handler = new TestHttpHandler(); + int port = findFreePort(); server = ServerBootstrap.bootstrap() - .setListenerPort(8086) + .setListenerPort(port) .setServerInfo("Test/1.1") .setSslContext(createSSLContext()) .registerHandler("*", handler) @@ -670,7 +676,7 @@ public void httpsTestPemContentEndpoint() throws Exception { AuditMessage msg = MockAuditMessageFactory.validAuditMessage(); LoggingSink fallback = new LoggingSink("test", Settings.EMPTY, null, null); - String url = "https://localhost:8086/endpoint"; + String url = "https://localhost:" + port + "/endpoint"; // test with filecontent handler.reset(); @@ -731,4 +737,12 @@ private void assertStringContainsAllKeysAndValues(String in) { Assert.assertTrue(in, in.contains("8.8.8.8")); //Assert.assertTrue(in, in.contains("CN=kirk,OU=client,O=client,L=test,C=DE")); } + + private int findFreePort() { + try (ServerSocket serverSocket = new ServerSocket(0)) { + return serverSocket.getLocalPort(); + } catch (IOException e) { + throw new RuntimeException("Failed to find free port", e); + } + } } From 191bfe97cb605909938b3177dcb691c81a58baf6 Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Thu, 24 Feb 2022 17:25:48 +0100 Subject: [PATCH 3/6] review remarks: rename tests Signed-off-by: rs-eliatra --- src/test/java/org/opensearch/security/SecurityAdminTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index 42f02842b5..43f06f306c 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -70,7 +70,7 @@ public void testSecurityAdmin() throws Exception { } @Test - public void testSgAdminInvalidCert() throws Exception { + public void testSecurityAdminInvalidCert() throws Exception { final Settings settings = Settings.builder() .put("plugins.security.ssl.http.enabled",true) .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) From 426c6bfc8a22786f0eba6028bc018355d7802721 Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Thu, 24 Feb 2022 19:44:20 +0100 Subject: [PATCH 4/6] refactor & fixes: SSLContext configuration Signed-off-by: rs-eliatra --- .../security/tools/SecurityAdmin.java | 100 ++++++------------ 1 file changed, 33 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 9f557e1004..7b6689a7db 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -201,7 +201,6 @@ public static int execute(final String[] args) throws Exception { final HelpFormatter formatter = new HelpFormatter(); Options options = new Options(); options.addOption( "nhnv", "disable-host-name-verification", false, "Disable hostname verification" ); - options.addOption( "nrhn", "disable-resolve-hostname", false, "Disable DNS lookup of hostnames" ); options.addOption(Option.builder("ts").longOpt("truststore").hasArg().argName("file").desc("Path to truststore (JKS/PKCS12 format)").build()); options.addOption(Option.builder("ks").longOpt("keystore").hasArg().argName("file").desc("Path to keystore (JKS/PKCS12 format").build()); options.addOption(Option.builder("tst").longOpt("truststore-type").hasArg().argName("type").desc("JKS or PKCS12, if not given we use the file extension to dectect the type").build()); @@ -237,8 +236,6 @@ public static int execute(final String[] args) throws Exception { options.addOption(Option.builder("key").hasArg().argName("file").desc("Path to the key of admin certificate").build()); options.addOption(Option.builder("keypass").hasArg().argName("password").desc("Password of the key of admin certificate (optional)").build()); - options.addOption(Option.builder("noopenssl").longOpt("no-openssl").desc("Do not use OpenSSL even if available (default: use it if available)").build()); - options.addOption(Option.builder("si").longOpt("show-info").desc("Show system and license info").build()); options.addOption(Option.builder("w").longOpt("whoami").desc("Show information about the used admin certificate").build()); @@ -270,9 +267,7 @@ public static int execute(final String[] args) throws Exception { String kst = null; String tst = null; boolean nhnv = false; - boolean nrhn = false; - boolean sniff = false; - boolean icl = false; + String clustername = "opensearch"; String file = null; String type = null; @@ -291,8 +286,6 @@ public static int execute(final String[] args) throws Exception { boolean acceptRedCluster = false; String keypass = System.getenv(OPENDISTRO_SECURITY_KEYPASS); - boolean useOpenSSLIfAvailable = true; - //boolean simpleAuth = false; String cacert = null; String cert = null; String key = null; @@ -341,10 +334,7 @@ public static int execute(final String[] args) throws Exception { kst = line.getOptionValue("kst", kst); tst = line.getOptionValue("tst", tst); nhnv = line.hasOption("nhnv"); - nrhn = line.hasOption("nrhn"); clustername = line.getOptionValue("cn", clustername); - sniff = line.hasOption("sniff"); - icl = line.hasOption("icl"); file = line.getOptionValue("f", file); type = line.getOptionValue("t", type); retrieve = line.hasOption("r"); @@ -384,9 +374,7 @@ public static int execute(final String[] args) throws Exception { cert = line.getOptionValue("cert"); key = line.getOptionValue("key"); keypass = line.getOptionValue("keypass", keypass); - - useOpenSSLIfAvailable = !line.hasOption("noopenssl"); - + si = line.hasOption("si"); whoami = line.hasOption("w"); @@ -453,12 +441,14 @@ public static int execute(final String[] args) throws Exception { System.out.println(" ... done"); if(ks != null) { + kst = kst==null?(ks.endsWith(".jks")?"JKS":"PKCS12"):kst; if(kspass == null && promptForPassword) { kspass = promptForPassword("Keystore", "kspass", OPENDISTRO_SECURITY_KS_PASS); } } if(ts != null) { + tst = tst==null?(ts.endsWith(".jks")?"JKS":"PKCS12"):tst; if(tspass == null && promptForPassword) { tspass = promptForPassword("Truststore", "tspass", OPENDISTRO_SECURITY_TS_PASS); } @@ -472,7 +462,7 @@ public static int execute(final String[] args) throws Exception { } - final SSLContext sslContext = sslContext(ks, kspass, ts, tspass, cacert, cert, key, keypass, ksAlias); + final SSLContext sslContext = sslContext(ts, tspass, tst, ks, kspass, kst, ksAlias, cacert, cert, key, keypass); try (RestHighLevelClient restHighLevelClient = getRestHighLevelClient(sslContext, nhnv, enabledProtocols, enabledCiphers, hostname, port)) { @@ -1421,45 +1411,55 @@ private static RestHighLevelClient getRestHighLevelClient(SSLContext sslContext, private static SSLContext sslContext( - String ks, - String kspass, + //keystore & trusstore related properties String ts, String tspass, + String trustStoreType, + String ks, + String kspass, + String keyStoreType, + String ksAlias, + //certs related properties String cacert, String cert, String key, - String keypass, - String ksAlias) throws Exception { + String keypass) throws Exception { - KeyStore trustStore = null; - KeyStore keyStore = null; + final SSLContextBuilder sslContextBuilder = SSLContexts.custom(); if (ks != null) { File keyStoreFile = Paths.get(ks).toFile(); - String keyStoreFileName = keyStoreFile.getName(); - String type = getType(keyStoreFileName); + KeyStore keyStore = KeyStore.getInstance(keyStoreType.toUpperCase()); + keyStore.load(new FileInputStream(keyStoreFile), kspass.toCharArray()); + sslContextBuilder.loadKeyMaterial(keyStore, kspass.toCharArray(), (aliases, socket) -> { + if (aliases == null || aliases.isEmpty()) { + return ksAlias; + } - keyStore = KeyStore.getInstance(type.toUpperCase()); - keyStore.load(new FileInputStream(keyStoreFile), kspass == null ? null : kspass.toCharArray()); + if (ksAlias == null || ksAlias.isEmpty()) { + return aliases.keySet().iterator().next(); + } + + return ksAlias; + }); } if (ts != null) { File trustStoreFile = Paths.get(ts).toFile(); - String trustStoreFileName = trustStoreFile.getName(); - - String type = getType(trustStoreFileName); - trustStore = KeyStore.getInstance(type.toUpperCase()); + KeyStore trustStore = KeyStore.getInstance(trustStoreType.toUpperCase()); trustStore.load(new FileInputStream(trustStoreFile), tspass == null ? null : tspass.toCharArray()); + sslContextBuilder.loadTrustMaterial(trustStore, null); } if (cacert != null) { File caCertFile = Paths.get(cacert).toFile(); try (FileInputStream in = new FileInputStream(caCertFile)) { X509Certificate[] certificates = PemKeyReader.loadCertificatesFromStream(in); - trustStore = PemKeyReader.toTruststore("al", certificates); + KeyStore trustStore = PemKeyReader.toTruststore("al", certificates); + sslContextBuilder.loadTrustMaterial(trustStore, null); } catch (FileNotFoundException e) { throw new IllegalArgumentException("Could not find certificate file " + caCertFile, e); } catch (IOException | CertificateException e) { @@ -1467,8 +1467,6 @@ private static SSLContext sslContext( } } - String pass = cert != null ? keypass : kspass; - if (cert != null && key != null) { File certFile = Paths.get(cert).toFile(); X509Certificate[] certificates; @@ -1490,46 +1488,14 @@ private static SSLContext sslContext( throw new IllegalArgumentException("Error while reading certificate key file " + keyFile, e); } - if(pass == null){ - pass = "hardcoded"; - } - keyStore = PemKeyReader.toKeystore("al",pass.toCharArray(), certificates, privateKey); + String alias = "al"; + KeyStore keyStore = PemKeyReader.toKeystore(alias, "changeit".toCharArray(), certificates, privateKey); + sslContextBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray(), (aliases, socket) -> alias); } - - final SSLContextBuilder sslContextBuilder = SSLContexts.custom(); - - - if (trustStore != null) { - sslContextBuilder.loadTrustMaterial(trustStore, null); - } - - if (keyStore != null) { - sslContextBuilder.loadKeyMaterial(keyStore, pass.toCharArray(), (aliases, socket) -> { - if (aliases == null || aliases.isEmpty()) { - return ksAlias; - } - - if (ksAlias == null || ksAlias.isEmpty()) { - return aliases.keySet().iterator().next(); - } - - return ksAlias; - }); - } return sslContextBuilder.build(); } - private static String getType(String trustStoreFileName) { - if (trustStoreFileName.endsWith(".jks")) { - return "JKS"; - } else if (trustStoreFileName.endsWith(".pfx") || trustStoreFileName.endsWith(".p12")) { - return "PKCS12"; - } else { - throw new IllegalArgumentException("Unknwon file type: " + trustStoreFileName); - } - } - private static String responseToString(Response response, boolean prettyJson) { ByteSource byteSource = new ByteSource() { @Override From 95322de7cee5f7a2f98f6dca4b4b6d83929ccf13 Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Wed, 2 Mar 2022 17:57:16 +0100 Subject: [PATCH 5/6] review remarks Signed-off-by: rs-eliatra --- .../java/org/opensearch/security/tools/SecurityAdmin.java | 6 +++--- .../opensearch/security/SecurityAdminIEndpointsTests.java | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 7b6689a7db..aa99de7023 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -209,7 +209,7 @@ public static int execute(final String[] args) throws Exception { options.addOption(Option.builder("kspass").longOpt("keystore-password").hasArg().argName("password").desc("Keystore password").build()); options.addOption(Option.builder("cd").longOpt("configdir").hasArg().argName("directory").desc("Directory for config files").build()); options.addOption(Option.builder("h").longOpt("hostname").hasArg().argName("host").desc("OpenSearch host (default: localhost)").build()); - options.addOption(Option.builder("p").longOpt("port").hasArg().argName("port").desc("OpenSearch transport port (default: 9300)").build()); + options.addOption(Option.builder("p").longOpt("port").hasArg().argName("port").desc("OpenSearch transport port (default: 9200)").build()); options.addOption(Option.builder("cn").longOpt("clustername").hasArg().argName("clustername").desc("Clustername (do not use together with -icl)").build()); options.addOption( "sniff", "enable-sniffing", false, "Enable client.transport.sniff" ); options.addOption( "icl", "ignore-clustername", false, "Ignore clustername (do not use together with -cn)" ); @@ -414,9 +414,9 @@ public static int execute(final String[] args) throws Exception { } - if(port < 9300) { + if(port < 9200) { System.out.println("WARNING: Seems you want connect to the OpenSearch HTTP port."+System.lineSeparator() - + " securityadmin connects on the transport port which is normally 9300."); + + " securityadmin connects on the transport port which is normally 9200."); } System.out.print("Will connect to "+hostname+":"+port); diff --git a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java index 1ac171d4d1..1230b2b669 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminIEndpointsTests.java @@ -15,7 +15,8 @@ public class SecurityAdminIEndpointsTests extends SingleClusterTest { - @Test public void testNoSSL() throws Exception { + @Test + public void testNoSSL() throws Exception { final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", false).build(); setup(settings); final RestHelper rh = nonSslRestHelper(); @@ -30,7 +31,8 @@ public class SecurityAdminIEndpointsTests extends SingleClusterTest { Assert.assertEquals(HttpStatus.SC_FORBIDDEN, rh.executeGetRequest("_plugins/_security/whoami").getStatusCode()); } - @Test public void testEndpoints() throws Exception { + @Test + public void testEndpoints() throws Exception { final Settings settings = Settings.builder().put("plugins.security.ssl.http.enabled", true) .put("plugins.security.ssl.http.keystore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("node-0-keystore.jks")) .put("plugins.security.ssl.http.truststore_filepath", FileHelper.getAbsoluteFilePathFromClassPath("truststore.jks")) @@ -106,4 +108,4 @@ public class SecurityAdminIEndpointsTests extends SingleClusterTest { } -} \ No newline at end of file +} From 8081472d7522eeb6a28af56e259220aa3899392d Mon Sep 17 00:00:00 2001 From: rs-eliatra Date: Wed, 2 Mar 2022 23:03:39 +0100 Subject: [PATCH 6/6] review remarks Signed-off-by: rs-eliatra --- .../java/org/opensearch/security/tools/SecurityAdmin.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index aa99de7023..7188e4c2ea 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -412,12 +412,7 @@ public static int execute(final String[] args) throws Exception { final boolean retVal = Migrater.migrateDirectory(new File(migrateOffline), true); return retVal?0:-1; } - - - if(port < 9200) { - System.out.println("WARNING: Seems you want connect to the OpenSearch HTTP port."+System.lineSeparator() - + " securityadmin connects on the transport port which is normally 9200."); - } + System.out.print("Will connect to "+hostname+":"+port); Socket socket = new Socket();