diff --git a/build.gradle b/build.gradle index 59d9107d1d..08e20f81b2 100644 --- a/build.gradle +++ b/build.gradle @@ -70,56 +70,7 @@ apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.rest-test' apply plugin: 'opensearch.testclusters' -// apply from: 'gradle/formatting.gradle' - -spotless { - java { - // Normally this isn't necessary, but we have Java sources in - // non-standard places - target '**/com/amazon/dlic/**/*.java' - target '**/com/amazon/security/**/*.java' - target '**/test/**/*.java' - target '**/integrationTest/**/*.java' - - removeUnusedImports() - eclipse().configFile rootProject.file('formatter/formatterConfig.xml') - trimTrailingWhitespace() - endWithNewline(); - - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - - custom 'Refuse wildcard imports', { - // Wildcard imports can't be resolved; fail the build - if (it =~ /\s+import .*\*;/) { - throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") - } - } - - // See DEVELOPER_GUIDE.md for details of when to enable this. - if (System.getProperty('spotless.paddedcell') != null) { - paddedCell() - } - } - format 'misc', { - target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' - - trimTrailingWhitespace() - endWithNewline() - } - format('javaFoo', JavaExtension) { - - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - target '**/*.java' - targetExclude '**/com/amazon/dlic/**/*.java' - targetExclude '**/com/amazon/security/**/*.java' - targetExclude '**/test/**/*.java' - targetExclude '**/integrationTest/**/*.java' - - trimTrailingWhitespace() - endWithNewline(); - } -} +apply from: 'gradle/formatting.gradle' licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') diff --git a/gradle/formatting.gradle b/gradle/formatting.gradle index 40ae51afb1..de52b51c83 100644 --- a/gradle/formatting.gradle +++ b/gradle/formatting.gradle @@ -1,95 +1,26 @@ -/* -* SPDX-License-Identifier: Apache-2.0 -* -* The OpenSearch Contributors require contributions made to -* this file be licensed under the Apache-2.0 license or a -* compatible open source license. -* -* Modifications Copyright OpenSearch Contributors. See -* GitHub history for details. -*/ - -/* - * Licensed to Elasticsearch under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import org.opensearch.gradle.BuildPlugin - -/* - * This script plugin configures formatting for Java source using Spotless - * for Gradle. Since the act of formatting existing source can interfere - * with developers' workflows, we don't automatically format all code - * (yet). Instead, we maintain a list of projects that are excluded from - * formatting, until we reach a point where we can comfortably format them - * in one go without too much disruption. - * - * Any new sub-projects must not be added to the exclusions list! - * - * To perform a reformat, run: - * - * ./gradlew spotlessApply - * - * To check the current format, run: - * - * ./gradlew spotlessJavaCheck - * - * This is also carried out by the `precommit` task. - * - * For more about Spotless, see: - * - * https://github.com/diffplug/spotless/tree/master/plugin-gradle - */ - -org.opensearch.gradle.BuildPlugin { - plugins.withType(BuildPlugin).whenPluginAdded { - project.apply plugin: "com.diffplug.spotless" - - spotless { - java { - // Normally this isn't necessary, but we have Java sources in - // non-standard places - target '**/*.java' - - removeUnusedImports() - eclipse().configFile rootProject.file('buildSrc/formatterConfig.xml') - trimTrailingWhitespace() - endWithNewline() - - custom 'Refuse wildcard imports', { - // Wildcard imports can't be resolved; fail the build - if (it =~ /\s+import .*\*;/) { - throw new AssertionError("Do not use wildcard imports. 'spotlessApply' cannot resolve this issue.") - } - } - - // See DEVELOPER_GUIDE.md for details of when to enable this. - if (System.getProperty('spotless.paddedcell') != null) { - paddedCell() - } - } - format 'misc', { - target '*.md', '*.gradle', '**/*.yaml', '**/*.yml', '**/*.svg' - - trimTrailingWhitespace() - endWithNewline() +allprojects { + project.apply plugin: "com.diffplug.spotless" + spotless { + java { + // Normally this isn't necessary, but we have Java sources in + // non-standard places + target '**/*.java' + + removeUnusedImports() + eclipse().configFile rootProject.file('formatter/formatterConfig.xml') + trimTrailingWhitespace() + endWithNewline(); + + // See DEVELOPER_GUIDE.md for details of when to enable this. + if (System.getProperty('spotless.paddedcell') != null) { + paddedCell() } } + format 'misc', { + target '*.md', '*.gradle', '**/*.json', '**/*.yaml', '**/*.yml', '**/*.svg' - precommit.dependsOn 'spotlessJavaCheck' + trimTrailingWhitespace() + endWithNewline() + } } } diff --git a/src/main/java/org/opensearch/security/DefaultObjectMapper.java b/src/main/java/org/opensearch/security/DefaultObjectMapper.java index 774af04bfa..fb3385629b 100644 --- a/src/main/java/org/opensearch/security/DefaultObjectMapper.java +++ b/src/main/java/org/opensearch/security/DefaultObjectMapper.java @@ -57,7 +57,7 @@ public class DefaultObjectMapper { static { objectMapper.setSerializationInclusion(Include.NON_NULL); - //objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + // objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); defaulOmittingObjectMapper.setSerializationInclusion(Include.NON_DEFAULT); defaulOmittingObjectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION); @@ -75,24 +75,31 @@ public static boolean getOrDefault(Map properties, String key, b if (value == null) { return defaultValue; } else if (value instanceof Boolean) { - return (boolean)value; + return (boolean) value; } else if (value instanceof String) { - String text = ((String)value).trim(); + String text = ((String) value).trim(); if ("true".equals(text) || "True".equals(text)) { return true; } if ("false".equals(text) || "False".equals(text)) { return false; } - throw InvalidFormatException.from(null, - "Cannot deserialize value of type 'boolean' from String \"" + text + "\": only \"true\" or \"false\" recognized)", - null, Boolean.class); + throw InvalidFormatException.from( + null, + "Cannot deserialize value of type 'boolean' from String \"" + text + "\": only \"true\" or \"false\" recognized)", + null, + Boolean.class + ); } - throw MismatchedInputException.from(null, Boolean.class, "Cannot deserialize instance of 'boolean' out of '" + value + "' (Property: " + key + ")"); + throw MismatchedInputException.from( + null, + Boolean.class, + "Cannot deserialize instance of 'boolean' out of '" + value + "' (Property: " + key + ")" + ); } public static T getOrDefault(Map properties, String key, T defaultValue) { - T value = (T)properties.get(key); + T value = (T) properties.get(key); return value != null ? value : defaultValue; } @@ -172,7 +179,7 @@ public static String writeValueAsString(Object value, boolean omitDefaults) thro return AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public String run() throws Exception { - return (omitDefaults?defaulOmittingObjectMapper:objectMapper).writeValueAsString(value); + return (omitDefaults ? defaulOmittingObjectMapper : objectMapper).writeValueAsString(value); } }); } catch (final PrivilegedActionException e) { @@ -229,12 +236,11 @@ public static TypeFactory getTypeFactory() { } public static Set getFields(Class cls) { - return objectMapper - .getSerializationConfig() - .introspect(getTypeFactory().constructType(cls)) - .findProperties() - .stream() - .map(BeanPropertyDefinition::getName) - .collect(ImmutableSet.toImmutableSet()); + return objectMapper.getSerializationConfig() + .introspect(getTypeFactory().constructType(cls)) + .findProperties() + .stream() + .map(BeanPropertyDefinition::getName) + .collect(ImmutableSet.toImmutableSet()); } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 89e8ec31ac..e4ca1050d6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -244,7 +244,8 @@ public void close() throws IOException { private final SslExceptionHandler evaluateSslExceptionHandler() { if (client || disabled || SSLConfig.isSslOnlyMode()) { - return new SslExceptionHandler(){}; + return new SslExceptionHandler() { + }; } return Objects.requireNonNull(sslExceptionHandler); @@ -274,7 +275,9 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) if (disabled) { this.sslCertReloadEnabled = false; - log.warn("OpenSearch Security plugin installed but disabled. This can expose your configuration (including passwords) to the public."); + log.warn( + "OpenSearch Security plugin installed but disabled. This can expose your configuration (including passwords) to the public." + ); return; } @@ -284,7 +287,6 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) return; } - demoCertHashes.add("54a92508de7a39d06242a0ffbf59414d7eb478633c719e6af03938daf6de8a1a"); demoCertHashes.add("742e4659c79d7cad89ea86aab70aea490f23bbfc7e72abd5f0a5d3fb4c84d212"); demoCertHashes.add("db1264612891406639ecd25c894f256b7c5a6b7e1d9054cbe37b77acd2ddd913"); @@ -294,7 +296,7 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) demoCertHashes.add("7a355f42c90e7543a267fbe3976c02f619036f5a34ce712995a22b342d83c3ce"); demoCertHashes.add("a9b5eca1399ec8518081c0d4a21a34eec4589087ce64c04fb01a488f9ad8edc9"); - //new certs 04/2018 + // new certs 04/2018 demoCertHashes.add("d14aefe70a592d7a29e14f3ff89c3d0070c99e87d21776aa07d333ee877e758f"); demoCertHashes.add("54a70016e0837a2b0c5658d1032d7ca32e432c62c55f01a2bf5adcb69a0a7ba9"); demoCertHashes.add("bdc141ab2272c779d0f242b79063152c49e1b06a2af05e0fd90d505f2b44d5f5"); @@ -310,7 +312,7 @@ public OpenSearchSecurityPlugin(final Settings settings, final Path configPath) AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { - if(Security.getProvider("BC") == null) { + if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } return null; @@ -322,18 +324,18 @@ public Object run() { deprecationLogger.deprecate("Setting {} is ignored.", advancedModulesEnabledKey); } - log.info("Clustername: {}", settings.get("cluster.name","opensearch")); + log.info("Clustername: {}", settings.get("cluster.name", "opensearch")); if (!transportSSLEnabled && !SSLConfig.isSslOnlyMode()) { - throw new IllegalStateException(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED+" must be set to 'true'"); + throw new IllegalStateException(SSLConfigConstants.SECURITY_SSL_TRANSPORT_ENABLED + " must be set to 'true'"); } - if(!client) { + if (!client) { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); - if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); } catch (Exception e) { @@ -346,9 +348,9 @@ public List run() { } }); - if(filesWithWrongPermissions != null && filesWithWrongPermissions.size() > 0) { - for(final Path p: filesWithWrongPermissions) { - if(Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { + if (filesWithWrongPermissions != null && filesWithWrongPermissions.size() > 0) { + for (final Path p : filesWithWrongPermissions) { + if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { log.warn("Directory {} has insecure file permissions (should be 0700)", p); } else { log.warn("File {} has insecure file permissions (should be 0600)", p); @@ -357,13 +359,13 @@ public List run() { } } - if(!client && !settings.getAsBoolean(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false)) { - //check for demo certificates + if (!client && !settings.getAsBoolean(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false)) { + // check for demo certificates final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); - if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); } catch (Exception e) { @@ -376,11 +378,13 @@ public List run() { } }); - if(files != null) { + if (files != null) { demoCertHashes.retainAll(files); - if(!demoCertHashes.isEmpty()) { - log.error("Demo certificates found but "+ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES+" is set to false."); - throw new RuntimeException("Demo certificates found "+demoCertHashes); + if (!demoCertHashes.isEmpty()) { + log.error( + "Demo certificates found but " + ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES + " is set to false." + ); + throw new RuntimeException("Demo certificates found " + demoCertHashes); } } else { throw new RuntimeException("Unable to look for demo certificates"); @@ -391,22 +395,22 @@ public List run() { private String sha256(Path p) { - if(!Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) { + if (!Files.isRegularFile(p, LinkOption.NOFOLLOW_LINKS)) { return ""; } - if(!Files.isReadable(p)) { - log.debug("Unreadable file "+p+" found"); + if (!Files.isReadable(p)) { + log.debug("Unreadable file " + p + " found"); return ""; } try { MessageDigest digester = MessageDigest.getInstance("SHA256"); final String hash = org.bouncycastle.util.encoders.Hex.toHexString(digester.digest(Files.readAllBytes(p))); - log.debug(hash +" :: "+p); + log.debug(hash + " :: " + p); return hash; } catch (Exception e) { - throw new OpenSearchSecurityException("Unable to digest file "+p, e); + throw new OpenSearchSecurityException("Unable to digest file " + p, e); } } @@ -416,79 +420,133 @@ private boolean checkFilePermissions(final Path p) { return false; } - Set perms; try { perms = Files.getPosixFilePermissions(p, LinkOption.NOFOLLOW_LINKS); } catch (Exception e) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Cannot determine posix file permissions for {} due to {}", p, e); } - //ignore, can happen on windows + // ignore, can happen on windows return false; } - if(Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { + if (Files.isDirectory(p, LinkOption.NOFOLLOW_LINKS)) { if (perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { // no x for others must be set return true; } } else { - if (perms.contains(PosixFilePermission.OWNER_EXECUTE) || perms.contains(PosixFilePermission.GROUP_EXECUTE) - || perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { + if (perms.contains(PosixFilePermission.OWNER_EXECUTE) + || perms.contains(PosixFilePermission.GROUP_EXECUTE) + || perms.contains(PosixFilePermission.OTHERS_EXECUTE)) { // no x must be set return true; } } - if (perms.contains(PosixFilePermission.OTHERS_READ) || perms.contains(PosixFilePermission.OTHERS_WRITE)) { // no permissions for "others" allowed return true; } - //if (perms.contains(PosixFilePermission.GROUP_READ) || perms.contains(PosixFilePermission.GROUP_WRITE)) { - // // no permissions for "group" allowed - // return true; - //} + // if (perms.contains(PosixFilePermission.GROUP_READ) || perms.contains(PosixFilePermission.GROUP_WRITE)) { + // // no permissions for "group" allowed + // return true; + // } return false; } - @Override - public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { final List handlers = new ArrayList(1); if (!client && !disabled) { - handlers.addAll(super.getRestHandlers(settings, restController, clusterSettings, indexScopedSettings, settingsFilter, indexNameExpressionResolver, nodesInCluster)); + handlers.addAll( + super.getRestHandlers( + settings, + restController, + clusterSettings, + indexScopedSettings, + settingsFilter, + indexNameExpressionResolver, + nodesInCluster + ) + ); - if(!SSLConfig.isSslOnlyMode()) { - handlers.add(new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool))); + if (!SSLConfig.isSslOnlyMode()) { + handlers.add( + new SecurityInfoAction(settings, restController, Objects.requireNonNull(evaluator), Objects.requireNonNull(threadPool)) + ); handlers.add(new SecurityHealthAction(settings, restController, Objects.requireNonNull(backendRegistry))); - 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)); + 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 + ) + ); handlers.addAll( - SecurityRestApiActions.getHandler( - settings, - configPath, - restController, - localClient, - adminDns, - cr, cs, principalExtractor, - evaluator, - threadPool, - Objects.requireNonNull(auditLog), sks, - Objects.requireNonNull(userService), - sslCertReloadEnabled) + SecurityRestApiActions.getHandler( + settings, + configPath, + restController, + localClient, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + Objects.requireNonNull(auditLog), + sks, + Objects.requireNonNull(userService), + sslCertReloadEnabled + ) ); log.debug("Added {} rest handler(s)", handlers.size()); } @@ -500,7 +558,7 @@ public List getRestHandlers(Settings settings, RestController restC @Override public UnaryOperator getRestHandlerWrapper(final ThreadContext threadContext) { - if(client || disabled || SSLConfig.isSslOnlyMode()) { + if (client || disabled || SSLConfig.isSslOnlyMode()) { return (rh) -> rh; } @@ -510,7 +568,7 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre @Override public List> getActions() { List> actions = new ArrayList<>(1); - if(!disabled && !SSLConfig.isSslOnlyMode()) { + if (!disabled && !SSLConfig.isSslOnlyMode()) { actions.add(new ActionHandler<>(ConfigUpdateAction.INSTANCE, TransportConfigUpdateAction.class)); actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class)); } @@ -519,7 +577,7 @@ public UnaryOperator getRestHandlerWrapper(final ThreadContext thre @Override public void onIndexModule(IndexModule indexModule) { - //called for every index! + // called for every index! if (!disabled && !client && !SSLConfig.isSslOnlyMode()) { log.debug("Handle auditLog {} for onIndexModule() of index {}", auditLog.getClass(), indexModule.getIndex().getName()); @@ -527,8 +585,19 @@ public void onIndexModule(IndexModule indexModule) { final ComplianceIndexingOperationListener ciol = new ComplianceIndexingOperationListenerImpl(auditLog); indexModule.addIndexOperationListener(ciol); - indexModule.setReaderWrapper(indexService -> new SecurityFlsDlsIndexSearcherWrapper(indexService, settings, adminDns, cs, auditLog, ciol, evaluator, salt)); - indexModule.forceQueryCacheProvider((indexSettings,nodeCache)->new QueryCache() { + indexModule.setReaderWrapper( + indexService -> new SecurityFlsDlsIndexSearcherWrapper( + indexService, + settings, + adminDns, + cs, + auditLog, + ciol, + evaluator, + salt + ) + ); + indexModule.forceQueryCacheProvider((indexSettings, nodeCache) -> new QueryCache() { @Override public Index index() { @@ -547,17 +616,21 @@ public void clear(String reason) { @Override public Weight doCache(Weight weight, QueryCachingPolicy policy) { - final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); - if(SecurityUtils.evalMap(allowedFlsFields, index().getName()) != null) { + if (SecurityUtils.evalMap(allowedFlsFields, index().getName()) != null) { return weight; } else { - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); - if(SecurityUtils.evalMap(maskedFieldsMap, index().getName()) != null) { + if (SecurityUtils.evalMap(maskedFieldsMap, index().getName()) != null) { return weight; } else { return nodeCache.doCache(weight, policy); @@ -577,28 +650,34 @@ public void onPreQueryPhase(SearchContext context) { @Override public void onNewReaderContext(ReaderContext readerContext) { final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()); - if (Origin.LOCAL.toString().equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) + if (Origin.LOCAL.toString() + .equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) ) { readerContext.putInContext("_opendistro_security_scroll_auth_local", Boolean.TRUE); } else { - readerContext.putInContext("_opendistro_security_scroll_auth", threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)); + readerContext.putInContext( + "_opendistro_security_scroll_auth", + threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) + ); } } @Override public void onNewScrollContext(ReaderContext readerContext) { final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()); - if (Origin.LOCAL.toString().equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) + if (Origin.LOCAL.toString() + .equals(threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadPool.getThreadContext())) ) { readerContext.putInContext("_opendistro_security_scroll_auth_local", Boolean.TRUE); } else { - readerContext.putInContext("_opendistro_security_scroll_auth", threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)); + readerContext.putInContext( + "_opendistro_security_scroll_auth", + threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) + ); } } @@ -609,8 +688,7 @@ public void validateReaderContext(ReaderContext readerContext, TransportRequest final Object _user = readerContext.getFromContext("_opendistro_security_scroll_auth"); if (_user != null && (_user instanceof User)) { final User scrollUser = (User) _user; - final User currentUser = threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User currentUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); if (!scrollUser.equals(currentUser)) { auditLog.logMissingPrivileges(SearchScrollAction.NAME, transportRequest, null); log.error("Wrong user {} in reader context, expected {}", scrollUser, currentUser); @@ -631,8 +709,10 @@ public void onQueryPhase(SearchContext searchContext, long tookInNanos) { return; } - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String maskedEval = SecurityUtils.evalMap(maskedFieldsMap, indexModule.getIndex().getName()); if (maskedEval != null) { final Set mf = maskedFieldsMap.get(maskedEval); @@ -662,8 +742,12 @@ public List getTransportInterceptors(NamedWriteableRegistr interceptors.add(new TransportInterceptor() { @Override - public TransportRequestHandler interceptHandler(String action, String executor, - boolean forceExecution, TransportRequestHandler actualHandler) { + public TransportRequestHandler interceptHandler( + String action, + String executor, + boolean forceExecution, + TransportRequestHandler actualHandler + ) { return new TransportRequestHandler() { @@ -681,8 +765,13 @@ public AsyncSender interceptSender(AsyncSender sender) { return new AsyncSender() { @Override - public void sendRequest(Connection connection, String action, - TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { + public void sendRequest( + Connection connection, + String action, + TransportRequest request, + TransportRequestOptions options, + TransportResponseHandler handler + ) { si.sendRequestDecorate(sender, connection, action, request, options, handler); } }; @@ -694,73 +783,148 @@ public void sendRequest(Connection connection, Str } @Override - public Map> getTransports(Settings settings, ThreadPool threadPool, PageCacheRecycler pageCacheRecycler, - CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { + public Map> getTransports( + Settings settings, + ThreadPool threadPool, + PageCacheRecycler pageCacheRecycler, + CircuitBreakerService circuitBreakerService, + NamedWriteableRegistry namedWriteableRegistry, + NetworkService networkService + ) { Map> transports = new HashMap>(); - if(SSLConfig.isSslOnlyMode()) { - return super.getTransports(settings, threadPool, pageCacheRecycler, circuitBreakerService, namedWriteableRegistry, networkService); + if (SSLConfig.isSslOnlyMode()) { + return super.getTransports( + settings, + threadPool, + pageCacheRecycler, + circuitBreakerService, + namedWriteableRegistry, + networkService + ); } if (transportSSLEnabled) { - transports.put("org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport", - () -> new SecuritySSLNettyTransport(settings, Version.CURRENT, threadPool, networkService, pageCacheRecycler, - namedWriteableRegistry, circuitBreakerService, sks, evaluateSslExceptionHandler(), sharedGroupFactory, SSLConfig)); + transports.put( + "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport", + () -> new SecuritySSLNettyTransport( + settings, + Version.CURRENT, + threadPool, + networkService, + pageCacheRecycler, + namedWriteableRegistry, + circuitBreakerService, + sks, + evaluateSslExceptionHandler(), + sharedGroupFactory, + SSLConfig + ) + ); } return transports; } @Override - public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, - PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, - NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { + public Map> getHttpTransports( + Settings settings, + ThreadPool threadPool, + BigArrays bigArrays, + PageCacheRecycler pageCacheRecycler, + CircuitBreakerService circuitBreakerService, + NamedXContentRegistry xContentRegistry, + NetworkService networkService, + Dispatcher dispatcher, + ClusterSettings clusterSettings + ) { - if(SSLConfig.isSslOnlyMode()) { - return super.getHttpTransports(settings, threadPool, bigArrays, pageCacheRecycler, circuitBreakerService, xContentRegistry, - networkService, dispatcher, clusterSettings); + if (SSLConfig.isSslOnlyMode()) { + return super.getHttpTransports( + settings, + threadPool, + bigArrays, + pageCacheRecycler, + circuitBreakerService, + xContentRegistry, + networkService, + dispatcher, + clusterSettings + ); } - if(!disabled) { + if (!disabled) { if (!client && httpSSLEnabled) { - final ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher(threadPool.getThreadContext(), dispatcher, - settings, configPath, evaluateSslExceptionHandler()); - //TODO close odshst - final SecurityHttpServerTransport odshst = new SecurityHttpServerTransport(settings, networkService, bigArrays, - threadPool, sks, evaluateSslExceptionHandler(), xContentRegistry, validatingDispatcher, clusterSettings, sharedGroupFactory); + final ValidatingDispatcher validatingDispatcher = new ValidatingDispatcher( + threadPool.getThreadContext(), + dispatcher, + settings, + configPath, + evaluateSslExceptionHandler() + ); + // TODO close odshst + final SecurityHttpServerTransport odshst = new SecurityHttpServerTransport( + settings, + networkService, + bigArrays, + threadPool, + sks, + evaluateSslExceptionHandler(), + xContentRegistry, + validatingDispatcher, + clusterSettings, + sharedGroupFactory + ); - return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", - () -> odshst); + return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", () -> odshst); } else if (!client) { - return Collections.singletonMap("org.opensearch.security.http.SecurityHttpServerTransport", - () -> new SecurityNonSslHttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher, clusterSettings, sharedGroupFactory)); + return Collections.singletonMap( + "org.opensearch.security.http.SecurityHttpServerTransport", + () -> new SecurityNonSslHttpServerTransport( + settings, + networkService, + bigArrays, + threadPool, + xContentRegistry, + dispatcher, + clusterSettings, + sharedGroupFactory + ) + ); } } return Collections.emptyMap(); } - - @Override - public Collection createComponents(Client localClient, ClusterService clusterService, ThreadPool threadPool, - ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, - Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { + public Collection createComponents( + Client localClient, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { SSLConfig.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); - if(SSLConfig.isSslOnlyMode()) { + if (SSLConfig.isSslOnlyMode()) { return super.createComponents( - localClient, - clusterService, - threadPool, - resourceWatcherService, - scriptService, - xContentRegistry, - environment, - nodeEnvironment, - namedWriteableRegistry, - indexNameExpressionResolver, - repositoriesServiceSupplier + localClient, + clusterService, + threadPool, + resourceWatcherService, + scriptService, + xContentRegistry, + environment, + nodeEnvironment, + namedWriteableRegistry, + indexNameExpressionResolver, + repositoriesServiceSupplier ); } @@ -774,7 +938,7 @@ public Collection createComponents(Client localClient, ClusterService cl return components; } - //Register opensearch dynamic settings + // Register opensearch dynamic settings transportPassiveAuthSetting.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); final ClusterInfoHolder cih = new ClusterInfoHolder(); @@ -787,8 +951,10 @@ public Collection createComponents(Client localClient, ClusterService cl final String DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS = DefaultInterClusterRequestEvaluator.class.getName(); InterClusterRequestEvaluator interClusterRequestEvaluator = new DefaultInterClusterRequestEvaluator(settings); - final String className = settings.get(ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, - DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS); + final String className = settings.get( + ConfigConstants.SECURITY_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, + DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS + ); log.debug("Using {} as intercluster request evaluator class", className); if (!DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { interClusterRequestEvaluator = ReflectionHelper.instantiateInterClusterRequestEvaluator(className, settings); @@ -802,7 +968,14 @@ public Collection createComponents(Client localClient, ClusterService cl auditLog = new NullAuditLog(); privilegesInterceptor = new PrivilegesInterceptor(resolver, clusterService, localClient, threadPool); } else { - dlsFlsValve = new DlsFlsValveImpl(settings, localClient, clusterService, resolver, xContentRegistry, threadPool.getThreadContext()); + dlsFlsValve = new DlsFlsValveImpl( + settings, + localClient, + clusterService, + resolver, + xContentRegistry, + threadPool.getThreadContext() + ); auditLog = new AuditLogImpl(settings, configPath, localClient, threadPool, resolver, clusterService, environment); privilegesInterceptor = new PrivilegesInterceptorImpl(resolver, clusterService, localClient, threadPool); } @@ -822,21 +995,39 @@ public Collection createComponents(Client localClient, ClusterService cl // DLS-FLS is enabled if not client and not disabled and not SSL only. final boolean dlsFlsEnabled = !SSLConfig.isSslOnlyMode(); - evaluator = new PrivilegesEvaluator(clusterService, threadPool, cr, resolver, auditLog, - settings, privilegesInterceptor, cih, irr, dlsFlsEnabled, namedXContentRegistry.get()); + evaluator = new PrivilegesEvaluator( + clusterService, + threadPool, + cr, + resolver, + auditLog, + settings, + privilegesInterceptor, + cih, + irr, + dlsFlsEnabled, + namedXContentRegistry.get() + ); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); final String principalExtractorClass = settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_PRINCIPAL_EXTRACTOR_CLASS, null); - if(principalExtractorClass == null) { + if (principalExtractorClass == null) { principalExtractor = new DefaultPrincipalExtractor(); } else { principalExtractor = ReflectionHelper.instantiatePrincipalExtractor(principalExtractorClass); } - securityRestHandler = new SecurityRestFilter(backendRegistry, auditLog, threadPool, - principalExtractor, settings, configPath, compatConfig); + securityRestHandler = new SecurityRestFilter( + backendRegistry, + auditLog, + threadPool, + principalExtractor, + settings, + configPath, + compatConfig + ); final DynamicConfigFactory dcf = new DynamicConfigFactory(cr, settings, configPath, localClient, threadPool, cih); dcf.registerDCFListener(backendRegistry); @@ -852,12 +1043,24 @@ public Collection createComponents(Client localClient, ClusterService cl cr.setDynamicConfigFactory(dcf); - si = new SecurityInterceptor(settings, threadPool, backendRegistry, auditLog, principalExtractor, - interClusterRequestEvaluator, cs, Objects.requireNonNull(sslExceptionHandler), Objects.requireNonNull(cih), SSLConfig); + si = new SecurityInterceptor( + settings, + threadPool, + backendRegistry, + auditLog, + principalExtractor, + interClusterRequestEvaluator, + cs, + Objects.requireNonNull(sslExceptionHandler), + Objects.requireNonNull(cih), + SSLConfig + ); components.add(principalExtractor); - // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires security index to be accessible which means - // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence the base values from opensearch.yml + // NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires + // security index to be accessible which means + // communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence + // the base values from opensearch.yml // is used to first establish trust between same cluster nodes and there after dynamic config is loaded if enabled. if (DEFAULT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS.equals(className)) { DefaultInterClusterRequestEvaluator e = (DefaultInterClusterRequestEvaluator) interClusterRequestEvaluator; @@ -873,7 +1076,6 @@ public Collection createComponents(Client localClient, ClusterService cl components.add(dcf); components.add(userService); - return components; } @@ -881,7 +1083,7 @@ public Collection createComponents(Client localClient, ClusterService cl @Override public Settings additionalSettings() { - if(disabled) { + if (disabled) { return Settings.EMPTY; } @@ -889,12 +1091,13 @@ public Settings additionalSettings() { builder.put(super.additionalSettings()); - if(!SSLConfig.isSslOnlyMode()){ + if (!SSLConfig.isSslOnlyMode()) { builder.put(NetworkModule.TRANSPORT_TYPE_KEY, "org.opensearch.security.ssl.http.netty.SecuritySSLNettyTransport"); builder.put(NetworkModule.HTTP_TYPE_KEY, "org.opensearch.security.http.SecurityHttpServerTransport"); } return builder.build(); } + @Override public List> getSettings() { List> settings = new ArrayList>(); @@ -907,80 +1110,246 @@ public List> getSettings() { settings.add(SecuritySettings.LEGACY_OPENDISTRO_SSL_DUAL_MODE_SETTING); // Protected index settings - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT, Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT, + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); // System index settings - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT, Property.NodeScope, Property.Filtered, Property.Final)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT, Function.identity(), Property.NodeScope, Property.Filtered, Property.Final)); - - if(!SSLConfig.isSslOnlyMode()) { - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT, + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT, + Function.identity(), + Property.NodeScope, + Property.Filtered, + Property.Final + ) + ); + + if (!SSLConfig.isSslOnlyMode()) { + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.simpleString(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+".", Property.NodeScope)); //not filtered here + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + ".", Property.NodeScope)); // not filtered + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_OID, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_CERT_INTERCLUSTER_REQUEST_EVALUATOR_CLASS, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList(), Function.identity(), Property.NodeScope) + );// not filtered here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));//not filtered here + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_NODES_DN_DYNAMIC_CONFIG_ENABLED, false, Property.NodeScope));// not + // filtered + // here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, - Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, - Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + Property.NodeScope, + Property.Filtered + ) + ); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLED, false, Property.NodeScope, Property.Filtered)); settings.add(Setting.intSetting(ConfigConstants.SECURITY_CACHE_TTL_MINUTES, 60, 0, Property.NodeScope, Property.Filtered)); - //Security - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+".", Property.NodeScope)); //not filtered here + // Security + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ADVANCED_MODULES_ENABLED, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_UNSAFE_DEMOCERTIFICATES, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_DFM_EMPTY_OVERRIDES_ALL, false, Property.NodeScope, Property.Filtered) + ); + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + ".", Property.NodeScope)); // not + // filtered + // here settings.add(Setting.simpleString(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_DISABLE_ENVVAR_REPLACEMENT, false, Property.NodeScope, Property.Filtered) + ); // Security - Audit settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, Property.NodeScope, Property.Filtered)); settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES + ".", Property.NodeScope)); - settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + ".", Property.NodeScope)); + settings.add(Setting.groupSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + ".", Property.NodeScope)); settings.add(Setting.intSetting(ConfigConstants.SECURITY_AUDIT_THREADPOOL_SIZE, 10, Property.NodeScope, Property.Filtered)); - settings.add(Setting.intSetting(ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, 100*1000, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.intSetting( + ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, + 100 * 1000, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, true, Property.NodeScope, Property.Filtered) + ); final List disabledCategories = new ArrayList(2); disabledCategories.add("AUTHENTICATED"); disabledCategories.add("GRANTED_PRIVILEGES"); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, disabledCategories, Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, disabledCategories, Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, + disabledCategories, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, + disabledCategories, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here final List ignoredUsers = new ArrayList(2); ignoredUsers.add("kibanaserver"); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, ignoredUsers, Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, true, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, + ignoredUsers, + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, + true, + Property.NodeScope, + Property.Filtered + ) + ); - final BiFunction> boolSettingNodeScopeFiltered = (String keyWithNamespace, Boolean value) -> Setting.boolSetting(keyWithNamespace, value, Property.NodeScope, Property.Filtered); + final BiFunction> boolSettingNodeScopeFiltered = ( + String keyWithNamespace, + Boolean value) -> Setting.boolSetting(keyWithNamespace, value, Property.NodeScope, Property.Filtered); Arrays.stream(FilterEntries.values()).map(filterEntry -> { - switch(filterEntry) { + switch (filterEntry) { case DISABLE_REST_CATEGORIES: case DISABLE_TRANSPORT_CATEGORIES: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), disabledCategories, Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + disabledCategories, + Function.identity(), + Property.NodeScope + ); case IGNORE_REQUESTS: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), Collections.emptyList(), Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ); case IGNORE_USERS: - return Setting.listSetting(filterEntry.getKeyWithNamespace(), ignoredUsers, Function.identity(), Property.NodeScope); + return Setting.listSetting( + filterEntry.getKeyWithNamespace(), + ignoredUsers, + Function.identity(), + Property.NodeScope + ); // All boolean settings with default of true case ENABLE_REST: case ENABLE_TRANSPORT: @@ -995,100 +1364,416 @@ public List> getSettings() { } }).forEach(settings::add); - // Security - Audit - Sink - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, + Property.NodeScope, + Property.Filtered + ) + ); // External OpenSearch - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS, Lists.newArrayList("localhost:9200"), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here - settings.add(Setting.listSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, Collections.emptyList(), Function.identity(), Property.NodeScope));//not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS, + Lists.newArrayList("localhost:9200"), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + );// not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + );// not filtered here // Webhooks - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, + true, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, + Property.NodeScope, + Property.Filtered + ) + ); // Log4j - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LOGGER_NAME, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LEVEL, Property.NodeScope, Property.Filtered)); - + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LOGGER_NAME, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_LOG4J_LEVEL, + Property.NodeScope, + Property.Filtered + ) + ); // Kerberos settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_KRB5_FILEPATH, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_KEYTAB_FILEPATH, Property.NodeScope, Property.Filtered) + ); settings.add(Setting.simpleString(ConfigConstants.SECURITY_KERBEROS_ACCEPTOR_PRINCIPAL, Property.NodeScope, Property.Filtered)); - // OpenSearch Security - REST API - settings.add(Setting.listSetting(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.groupSetting(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".", Property.NodeScope)); settings.add(Setting.boolSetting(ConfigConstants.SECURITY_RESTAPI_ADMIN_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered)); - settings.add(Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.simpleString(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.simpleString( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, + Property.NodeScope, + Property.Filtered + ) + ); settings.add( - Setting.intSetting( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, - -1, -1, Property.NodeScope, Property.Filtered) + Setting.intSetting(ConfigConstants.SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, -1, -1, Property.NodeScope, Property.Filtered) ); settings.add( - Setting.simpleString( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - PasswordValidator.ScoreStrength.STRONG.name(), - PasswordValidator.ScoreStrength::fromConfiguration, - Property.NodeScope, Property.Filtered - ) + Setting.simpleString( + ConfigConstants.SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + PasswordValidator.ScoreStrength.STRONG.name(), + PasswordValidator.ScoreStrength::fromConfiguration, + Property.NodeScope, + Property.Filtered + ) ); // Compliance - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.listSetting(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.listSetting(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList(), Function.identity(), Property.NodeScope)); //not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.listSetting( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.listSetting( + ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, + Collections.emptyList(), + Function.identity(), + Property.NodeScope + ) + ); // not filtered here settings.add(Setting.simpleString(ConfigConstants.SECURITY_COMPLIANCE_SALT, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); settings.add(transportPassiveAuthSetting.getDynamicSetting()); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, false, Property.NodeScope, - Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, + false, + Property.NodeScope, + Property.Filtered + ) + ); - //compat - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, false, Property.NodeScope, Property.Filtered)); + // compat + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, + false, + Property.NodeScope, + Property.Filtered + ) + ); // system integration - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered)); - settings.add(Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false, Property.NodeScope, Property.Filtered)); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_INJECT_ADMIN_USER_ENABLED, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_ALLOW_NOW_IN_DLS, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, + false, + Property.NodeScope, + Property.Filtered + ) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting(ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered) + ); + settings.add( + Setting.boolSetting( + ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, + false, + Property.NodeScope, + Property.Filtered + ) + ); } return settings; @@ -1098,7 +1783,7 @@ public List> getSettings() { public List getSettingsFilter() { List settingsFilter = new ArrayList<>(); - if(disabled) { + if (disabled) { return settingsFilter; } settingsFilter.add("opendistro_security.*"); @@ -1109,16 +1794,16 @@ public List getSettingsFilter() { @Override public void onNodeStarted() { log.info("Node started"); - if(!SSLConfig.isSslOnlyMode() && !client && !disabled) { + if (!SSLConfig.isSslOnlyMode() && !client && !disabled) { cr.initOnNodeStart(); } final Set securityModules = ReflectionHelper.getModulesLoaded(); log.info("{} OpenSearch Security modules loaded so far: {}", securityModules.size(), securityModules); } - //below is a hack because it seems not possible to access RepositoriesService from a non guice class - //the way of how deguice is organized is really a mess - hope this can be fixed in later versions - //TODO check if this could be removed + // below is a hack because it seems not possible to access RepositoriesService from a non guice class + // the way of how deguice is organized is really a mess - hope this can be fixed in later versions + // TODO check if this could be removed @Override public Collection> getGuiceServiceClasses() { @@ -1138,8 +1823,10 @@ public Function> getFieldFilter() { if (threadPool == null) { return field -> true; } - final Map> allowedFlsFields = (Map>) HeaderHelper - .deserializeSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); final String eval = SecurityUtils.evalMap(allowedFlsFields, index); @@ -1151,7 +1838,6 @@ public Function> getFieldFilter() { final Set includesSet = new HashSet<>(includesExcludes.size()); final Set excludesSet = new HashSet<>(includesExcludes.size()); - for (final String incExc : includesExcludes) { final char firstChar = incExc.charAt(0); @@ -1175,14 +1861,17 @@ public Function> getFieldFilter() { @Override public Collection getSystemIndexDescriptors(Settings settings) { - final String indexPattern = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + final String indexPattern = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(indexPattern, "Security index"); return Collections.singletonList(systemIndexDescriptor); } private static String handleKeyword(final String field) { - if(field != null && field.endsWith(KEYWORD)) { - return field.substring(0, field.length()-KEYWORD.length()); + if (field != null && field.endsWith(KEYWORD)) { + return field.substring(0, field.length() - KEYWORD.length()); } return field; } @@ -1198,8 +1887,13 @@ public static class GuiceHolder implements LifecycleComponent { private static ExtensionsManager extensionsManager; @Inject - public GuiceHolder(final RepositoriesService repositoriesService, - final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, ExtensionsManager extensionsManager) { + public GuiceHolder( + final RepositoriesService repositoriesService, + final TransportService remoteClusterService, + IndicesService indicesService, + PitService pitService, + ExtensionsManager extensionsManager + ) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; @@ -1220,16 +1914,18 @@ public static IndicesService getIndicesService() { return indicesService; } - public static PitService getPitService() { return pitService; } + public static PitService getPitService() { + return pitService; + } // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions - public static ExtensionsManager getExtensionsManager() { return extensionsManager; } + public static ExtensionsManager getExtensionsManager() { + return extensionsManager; + } // CS-ENFORCE-SINGLE - @Override - public void close() { - } + public void close() {} @Override public State lifecycleState() { @@ -1237,20 +1933,16 @@ public State lifecycleState() { } @Override - public void addLifecycleListener(LifecycleListener listener) { - } + public void addLifecycleListener(LifecycleListener listener) {} @Override - public void removeLifecycleListener(LifecycleListener listener) { - } + public void removeLifecycleListener(LifecycleListener listener) {} @Override - public void start() { - } + public void start() {} @Override - public void stop() { - } + public void stop() {} } } 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 0e6944e9c4..0ac4459102 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateNodeResponse.java @@ -58,7 +58,7 @@ public static ConfigUpdateNodeResponse readNodeResponse(StreamInput in) throws I } public String[] getUpdatedConfigTypes() { - return updatedConfigTypes==null?null:Arrays.copyOf(updatedConfigTypes, updatedConfigTypes.length); + return updatedConfigTypes == null ? null : Arrays.copyOf(updatedConfigTypes, updatedConfigTypes.length); } public String getMessage() { @@ -81,7 +81,7 @@ public String toString() { 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("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/ConfigUpdateRequest.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java index aeb92fa057..5310c497a2 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequest.java @@ -47,8 +47,8 @@ public ConfigUpdateRequest() { } public ConfigUpdateRequest(String[] configTypes) { - this(); - setConfigTypes(configTypes); + this(); + setConfigTypes(configTypes); } @Override diff --git a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java index d33c0fa7a6..edfe1f10bb 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateRequestBuilder.java @@ -30,8 +30,10 @@ import org.opensearch.action.support.nodes.NodesOperationRequestBuilder; import org.opensearch.client.OpenSearchClient; -public class ConfigUpdateRequestBuilder extends -NodesOperationRequestBuilder { +public class ConfigUpdateRequestBuilder extends NodesOperationRequestBuilder< + ConfigUpdateRequest, + ConfigUpdateResponse, + ConfigUpdateRequestBuilder> { protected ConfigUpdateRequestBuilder(OpenSearchClient client, ActionType action) { super(client, action, new ConfigUpdateRequest()); 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 3a57ca4144..fd3176c016 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java +++ b/src/main/java/org/opensearch/security/action/configupdate/ConfigUpdateResponse.java @@ -57,15 +57,15 @@ public void writeNodesTo(final StreamOutput out, List 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(); + @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; - } + return builder; + } } diff --git a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java index c5c60cf8a6..9a8cc22624 100644 --- a/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/action/configupdate/TransportConfigUpdateAction.java @@ -49,9 +49,11 @@ import org.opensearch.transport.TransportRequest; import org.opensearch.transport.TransportService; -public class TransportConfigUpdateAction -extends -TransportNodesAction { +public class TransportConfigUpdateAction extends TransportNodesAction< + ConfigUpdateRequest, + ConfigUpdateResponse, + TransportConfigUpdateAction.NodeConfigUpdateRequest, + ConfigUpdateNodeResponse> { protected Logger logger = LogManager.getLogger(getClass()); private final Provider backendRegistry; @@ -59,13 +61,27 @@ public class TransportConfigUpdateAction private DynamicConfigFactory dynamicConfigFactory; @Inject - public TransportConfigUpdateAction(final Settings settings, - final ThreadPool threadPool, final ClusterService clusterService, final TransportService transportService, - final ConfigurationRepository configurationRepository, final ActionFilters actionFilters, - Provider backendRegistry, DynamicConfigFactory dynamicConfigFactory) { - super(ConfigUpdateAction.NAME, threadPool, clusterService, transportService, actionFilters, - ConfigUpdateRequest::new, TransportConfigUpdateAction.NodeConfigUpdateRequest::new, - ThreadPool.Names.MANAGEMENT, ConfigUpdateNodeResponse.class); + public TransportConfigUpdateAction( + final Settings settings, + final ThreadPool threadPool, + final ClusterService clusterService, + final TransportService transportService, + final ConfigurationRepository configurationRepository, + final ActionFilters actionFilters, + Provider backendRegistry, + DynamicConfigFactory dynamicConfigFactory + ) { + super( + ConfigUpdateAction.NAME, + threadPool, + clusterService, + transportService, + actionFilters, + ConfigUpdateRequest::new, + TransportConfigUpdateAction.NodeConfigUpdateRequest::new, + ThreadPool.Names.MANAGEMENT, + ConfigUpdateNodeResponse.class + ); this.configurationRepository = configurationRepository; this.backendRegistry = backendRegistry; @@ -76,7 +92,7 @@ public static class NodeConfigUpdateRequest extends TransportRequest { ConfigUpdateRequest request; - public NodeConfigUpdateRequest(StreamInput in) throws IOException{ + public NodeConfigUpdateRequest(StreamInput in) throws IOException { super(in); request = new ConfigUpdateRequest(in); } @@ -98,8 +114,11 @@ protected ConfigUpdateNodeResponse newNodeResponse(StreamInput in) throws IOExce } @Override - protected ConfigUpdateResponse newResponse(ConfigUpdateRequest request, List responses, - List failures) { + protected ConfigUpdateResponse newResponse( + ConfigUpdateRequest request, + List responses, + List failures + ) { return new ConfigUpdateResponse(this.clusterService.getClusterName(), responses, failures); } diff --git a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java index 901800f0a2..bd3ecf46a2 100644 --- a/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/action/whoami/TransportWhoAmIAction.java @@ -40,17 +40,20 @@ import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; -public class TransportWhoAmIAction -extends -HandledTransportAction { +public class TransportWhoAmIAction extends HandledTransportAction { private final AdminDNs adminDNs; private final ThreadPool threadPool; @Inject - public TransportWhoAmIAction(final Settings settings, - final ThreadPool threadPool, final ClusterService clusterService, final TransportService transportService, - final AdminDNs adminDNs, final ActionFilters actionFilters) { + public TransportWhoAmIAction( + final Settings settings, + final ThreadPool threadPool, + final ClusterService clusterService, + final TransportService transportService, + final AdminDNs adminDNs, + final ActionFilters actionFilters + ) { super(WhoAmIAction.NAME, transportService, actionFilters, WhoAmIRequest::new); @@ -58,15 +61,16 @@ public TransportWhoAmIAction(final Settings settings, this.threadPool = threadPool; } - @Override protected void doExecute(Task task, WhoAmIRequest request, ActionListener listener) { final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final String dn = user==null?threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL):user.getName(); + final String dn = user == null + ? threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL) + : user.getName(); final boolean isAdmin = adminDNs.isAdminDN(dn); - final boolean isAuthenticated = isAdmin?true: user != null; - final boolean isNodeCertificateRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()) || - HeaderHelper.isTrustedClusterRequest(threadPool.getThreadContext()); + final boolean isAuthenticated = isAdmin ? true : user != null; + final boolean isNodeCertificateRequest = HeaderHelper.isInterClusterRequest(threadPool.getThreadContext()) + || HeaderHelper.isTrustedClusterRequest(threadPool.getThreadContext()); listener.onResponse(new WhoAmIResponse(dn, isAdmin, isAuthenticated, isNodeCertificateRequest)); diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java index 7e22cc000d..229ba57ed9 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIRequestBuilder.java @@ -32,8 +32,7 @@ import org.opensearch.client.ClusterAdminClient; import org.opensearch.client.OpenSearchClient; -public class WhoAmIRequestBuilder extends -ActionRequestBuilder { +public class WhoAmIRequestBuilder extends ActionRequestBuilder { public WhoAmIRequestBuilder(final ClusterAdminClient client) throws IOException { this(client, WhoAmIAction.INSTANCE); } diff --git a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java index 2b25344f76..3e9d74fe25 100644 --- a/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java +++ b/src/main/java/org/opensearch/security/action/whoami/WhoAmIResponse.java @@ -50,7 +50,6 @@ public WhoAmIResponse(String dn, boolean isAdmin, boolean isAuthenticated, boole this.isNodeCertificateRequest = isNodeCertificateRequest; } - public WhoAmIResponse() { super(); this.dn = null; @@ -106,6 +105,6 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws @Override public String toString() { - return Strings.toString(XContentType.JSON,this, true, true); + return Strings.toString(XContentType.JSON, this, true, true); } } diff --git a/src/main/java/org/opensearch/security/auditlog/AuditLog.java b/src/main/java/org/opensearch/security/auditlog/AuditLog.java index 128944e387..3ac7e095aa 100644 --- a/src/main/java/org/opensearch/security/auditlog/AuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/AuditLog.java @@ -43,30 +43,38 @@ public interface AuditLog extends Closeable { - //login + // login void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request); + void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request); - //privs + // privs void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request); + void logGrantedPrivileges(String effectiveUser, RestRequest request); + void logMissingPrivileges(String privilege, TransportRequest request, Task task); + void logGrantedPrivileges(String privilege, TransportRequest request, Task task); // index event requests void logIndexEvent(String privilege, TransportRequest request, Task task); - //spoof + // spoof void logBadHeaders(TransportRequest request, String action, Task task); + void logBadHeaders(RestRequest request); void logSecurityIndexAttempt(TransportRequest request, String action, Task task); void logSSLException(TransportRequest request, Throwable t, String action, Task task); + void logSSLException(RestRequest request, Throwable t); void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues); + void logDocumentWritten(ShardId shardId, GetResult originalIndex, Index currentIndex, IndexResult result); + void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result); // compliance config @@ -76,10 +84,14 @@ public interface AuditLog extends Closeable { void setConfig(AuditConfig auditConfig); public enum Origin { - REST, TRANSPORT, LOCAL + REST, + TRANSPORT, + LOCAL } public enum Operation { - CREATE, UPDATE, DELETE + CREATE, + UPDATE, + DELETE } } diff --git a/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java b/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java index 3421bc4a4f..942f06804f 100644 --- a/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java +++ b/src/main/java/org/opensearch/security/auditlog/AuditLogSslExceptionHandler.java @@ -32,7 +32,7 @@ import org.opensearch.tasks.Task; import org.opensearch.transport.TransportRequest; -public class AuditLogSslExceptionHandler implements SslExceptionHandler{ +public class AuditLogSslExceptionHandler implements SslExceptionHandler { private final AuditLog auditLog; @@ -44,14 +44,14 @@ public AuditLogSslExceptionHandler(final AuditLog auditLog) { @Override public void logError(Throwable t, RestRequest request, int type) { switch (type) { - case 0: - auditLog.logSSLException(request, t); - break; - case 1: - auditLog.logBadHeaders(request); - break; - default: - break; + case 0: + auditLog.logSSLException(request, t); + break; + case 1: + auditLog.logBadHeaders(request); + break; + default: + break; } } @@ -67,18 +67,18 @@ public void logError(Throwable t, boolean isRest) { @Override public void logError(Throwable t, TransportRequest request, String action, Task task, int type) { switch (type) { - case 0: - if(t instanceof OpenSearchException) { - auditLog.logMissingPrivileges(action, request, task); - } else { - auditLog.logSSLException(request, t, action, task); - } - break; - case 1: - auditLog.logBadHeaders(request, action, task); - break; - default: - break; + case 0: + if (t instanceof OpenSearchException) { + auditLog.logMissingPrivileges(action, request, task); + } else { + auditLog.logSSLException(request, t, action, task); + } + break; + case 1: + auditLog.logBadHeaders(request, action, task); + break; + default: + break; } } diff --git a/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java b/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java index ced4d0aae6..7fe3324d2e 100644 --- a/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/NullAuditLog.java @@ -45,82 +45,82 @@ public class NullAuditLog implements AuditLog { @Override public void close() throws IOException { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logIndexEvent(String privilege, TransportRequest request, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logBadHeaders(TransportRequest request, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logBadHeaders(RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logSSLException(RestRequest request, Throwable t) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentWritten(ShardId shardId, GetResult originalIndex, Index currentIndex, IndexResult result) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { - //noop, intentionally left empty + // noop, intentionally left empty } @Override diff --git a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java index 1152abe89e..f6f9a42e87 100644 --- a/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java +++ b/src/main/java/org/opensearch/security/auditlog/config/AuditConfig.java @@ -108,10 +108,7 @@ public ComplianceConfig getCompliance() { } @VisibleForTesting - public AuditConfig( - final boolean auditLogEnabled, - final Filter filter, - final ComplianceConfig compliance) { + public AuditConfig(final boolean auditLogEnabled, final Filter filter, final ComplianceConfig compliance) { this.auditLogEnabled = auditLogEnabled; this.filter = filter != null ? filter : Filter.DEFAULT; this.compliance = compliance != null ? compliance : ComplianceConfig.DEFAULT; @@ -147,16 +144,18 @@ public static class Filter { private final Set disabledTransportCategories; @VisibleForTesting - Filter(final boolean isRestApiAuditEnabled, - final boolean isTransportApiAuditEnabled, - final boolean resolveBulkRequests, - final boolean logRequestBody, - final boolean resolveIndices, - final boolean excludeSensitiveHeaders, - final Set ignoredAuditUsers, - final Set ignoredAuditRequests, - final Set disabledRestCategories, - final Set disabledTransportCategories) { + Filter( + final boolean isRestApiAuditEnabled, + final boolean isTransportApiAuditEnabled, + final boolean resolveBulkRequests, + final boolean logRequestBody, + final boolean resolveIndices, + final boolean excludeSensitiveHeaders, + final Set ignoredAuditUsers, + final Set ignoredAuditRequests, + final Set disabledRestCategories, + final Set disabledTransportCategories + ) { this.isRestApiAuditEnabled = isRestApiAuditEnabled; this.isTransportApiAuditEnabled = isTransportApiAuditEnabled; this.resolveBulkRequests = resolveBulkRequests; @@ -179,22 +178,29 @@ public enum FilterEntries { RESOLVE_INDICES("resolve_indices", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES), EXCLUDE_SENSITIVE_HEADERS("exclude_sensitive_headers", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS), DISABLE_REST_CATEGORIES("disabled_rest_categories", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES), - DISABLE_TRANSPORT_CATEGORIES("disabled_transport_categories", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES), + DISABLE_TRANSPORT_CATEGORIES( + "disabled_transport_categories", + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES + ), IGNORE_USERS("ignore_users", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS), IGNORE_REQUESTS("ignore_requests", ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS); private final String key; private final String legacyKeyWithNamespace; + FilterEntries(final String entryKey, final String legacyKeyWithNamespace) { this.key = entryKey; this.legacyKeyWithNamespace = legacyKeyWithNamespace; } + public String getKey() { return this.key; } + public String getKeyWithNamespace() { - return SECURITY_AUDIT_CONFIG_DEFAULT + "."+ this.key; + return SECURITY_AUDIT_CONFIG_DEFAULT + "." + this.key; } + public String getLegacyKeyWithNamespace() { return this.legacyKeyWithNamespace; } @@ -204,7 +210,14 @@ public String getLegacyKeyWithNamespace() { @VisibleForTesting public static Filter from(Map properties) throws JsonProcessingException { if (!FIELDS.containsAll(properties.keySet())) { - throw new UnrecognizedPropertyException(null, "Unrecognized field(s) present in the input data for audit filter config", null, Filter.class, null, null); + throw new UnrecognizedPropertyException( + null, + "Unrecognized field(s) present in the input data for audit filter config", + null, + Filter.class, + null, + null + ); } final boolean isRestApiAuditEnabled = getOrDefault(properties, FilterEntries.ENABLE_REST.getKey(), true); @@ -213,22 +226,39 @@ public static Filter from(Map properties) throws JsonProcessingE final boolean logRequestBody = getOrDefault(properties, FilterEntries.LOG_REQUEST_BODY.getKey(), true); final boolean resolveIndices = getOrDefault(properties, FilterEntries.RESOLVE_INDICES.getKey(), true); final boolean excludeSensitiveHeaders = getOrDefault(properties, FilterEntries.EXCLUDE_SENSITIVE_HEADERS.getKey(), true); - final Set disabledRestCategories = AuditCategory.parse(getOrDefault(properties, FilterEntries.DISABLE_REST_CATEGORIES.getKey(), ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set disabledTransportCategories = AuditCategory.parse(getOrDefault(properties, FilterEntries.DISABLE_TRANSPORT_CATEGORIES.getKey(), ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set ignoredAuditUsers = ImmutableSet.copyOf(getOrDefault(properties, FilterEntries.IGNORE_USERS.getKey(), DEFAULT_IGNORED_USERS)); - final Set ignoreAuditRequests = ImmutableSet.copyOf(getOrDefault(properties, FilterEntries.IGNORE_REQUESTS.getKey(), Collections.emptyList())); + final Set disabledRestCategories = AuditCategory.parse( + getOrDefault( + properties, + FilterEntries.DISABLE_REST_CATEGORIES.getKey(), + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set disabledTransportCategories = AuditCategory.parse( + getOrDefault( + properties, + FilterEntries.DISABLE_TRANSPORT_CATEGORIES.getKey(), + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set ignoredAuditUsers = ImmutableSet.copyOf( + getOrDefault(properties, FilterEntries.IGNORE_USERS.getKey(), DEFAULT_IGNORED_USERS) + ); + final Set ignoreAuditRequests = ImmutableSet.copyOf( + getOrDefault(properties, FilterEntries.IGNORE_REQUESTS.getKey(), Collections.emptyList()) + ); return new Filter( - isRestApiAuditEnabled, - isTransportAuditEnabled, - resolveBulkRequests, - logRequestBody, - resolveIndices, - excludeSensitiveHeaders, - ignoredAuditUsers, - ignoreAuditRequests, - disabledRestCategories, - disabledTransportCategories); + isRestApiAuditEnabled, + isTransportAuditEnabled, + resolveBulkRequests, + logRequestBody, + resolveIndices, + excludeSensitiveHeaders, + ignoredAuditUsers, + ignoreAuditRequests, + disabledRestCategories, + disabledTransportCategories + ); } @@ -244,34 +274,52 @@ public static Filter from(Settings settings) { final boolean logRequestBody = fromSettingBoolean(settings, FilterEntries.LOG_REQUEST_BODY, true); final boolean resolveIndices = fromSettingBoolean(settings, FilterEntries.RESOLVE_INDICES, true); final boolean excludeSensitiveHeaders = fromSettingBoolean(settings, FilterEntries.EXCLUDE_SENSITIVE_HEADERS, true); - final Set disabledRestCategories = AuditCategory.parse(fromSettingStringSet(settings, FilterEntries.DISABLE_REST_CATEGORIES, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); - final Set disabledTransportCategories = AuditCategory.parse(fromSettingStringSet(settings, FilterEntries.DISABLE_TRANSPORT_CATEGORIES, ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT)); + final Set disabledRestCategories = AuditCategory.parse( + fromSettingStringSet( + settings, + FilterEntries.DISABLE_REST_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); + final Set disabledTransportCategories = AuditCategory.parse( + fromSettingStringSet( + settings, + FilterEntries.DISABLE_TRANSPORT_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_DISABLED_CATEGORIES_DEFAULT + ) + ); final Set ignoredAuditUsers = fromSettingStringSet(settings, FilterEntries.IGNORE_USERS, DEFAULT_IGNORED_USERS); final Set ignoreAuditRequests = fromSettingStringSet(settings, FilterEntries.IGNORE_REQUESTS, Collections.emptyList()); - return new Filter(isRestApiAuditEnabled, - isTransportAuditEnabled, - resolveBulkRequests, - logRequestBody, - resolveIndices, - excludeSensitiveHeaders, - ignoredAuditUsers, - ignoreAuditRequests, - disabledRestCategories, - disabledTransportCategories); + return new Filter( + isRestApiAuditEnabled, + isTransportAuditEnabled, + resolveBulkRequests, + logRequestBody, + resolveIndices, + excludeSensitiveHeaders, + ignoredAuditUsers, + ignoreAuditRequests, + disabledRestCategories, + disabledTransportCategories + ); } static boolean fromSettingBoolean(final Settings settings, FilterEntries filterEntry, final boolean defaultValue) { - return settings.getAsBoolean(filterEntry.getKeyWithNamespace(), settings.getAsBoolean(filterEntry.getLegacyKeyWithNamespace(), defaultValue)); + return settings.getAsBoolean( + filterEntry.getKeyWithNamespace(), + settings.getAsBoolean(filterEntry.getLegacyKeyWithNamespace(), defaultValue) + ); } static Set fromSettingStringSet(final Settings settings, FilterEntries filterEntry, final List defaultValue) { final String defaultDetectorValue = "__DEFAULT_DETECTION__"; final Set stringSetOfKey = ConfigConstants.getSettingAsSet( - settings, - filterEntry.getKeyWithNamespace(), - ImmutableList.of(defaultDetectorValue), - true); + settings, + filterEntry.getKeyWithNamespace(), + ImmutableList.of(defaultDetectorValue), + true + ); final boolean foundDefault = stringSetOfKey.stream().anyMatch(defaultDetectorValue::equals); if (!foundDefault) { @@ -279,11 +327,7 @@ static Set fromSettingStringSet(final Settings settings, FilterEntries f } // Fallback to the legacy keyname - return ConfigConstants.getSettingAsSet( - settings, - filterEntry.getLegacyKeyWithNamespace(), - defaultValue, - true); + return ConfigConstants.getSettingAsSet(settings, filterEntry.getLegacyKeyWithNamespace(), defaultValue, true); } /** @@ -400,18 +444,28 @@ public void log(Logger logger) { @Override public String toString() { - return "Filter{" + - "isRestApiAuditEnabled=" + isRestApiAuditEnabled + - ", disabledRestCategories=" + disabledRestCategories + - ", isTransportApiAuditEnabled=" + isTransportApiAuditEnabled + - ", disabledTransportCategories=" + disabledTransportCategories + - ", resolveBulkRequests=" + resolveBulkRequests + - ", logRequestBody=" + logRequestBody + - ", resolveIndices=" + resolveIndices + - ", excludeSensitiveHeaders=" + excludeSensitiveHeaders + - ", ignoredAuditUsers=" + ignoredAuditUsersMatcher + - ", ignoreAuditRequests=" + ignoredAuditRequestsMatcher + - '}'; + return "Filter{" + + "isRestApiAuditEnabled=" + + isRestApiAuditEnabled + + ", disabledRestCategories=" + + disabledRestCategories + + ", isTransportApiAuditEnabled=" + + isTransportApiAuditEnabled + + ", disabledTransportCategories=" + + disabledTransportCategories + + ", resolveBulkRequests=" + + resolveBulkRequests + + ", logRequestBody=" + + logRequestBody + + ", resolveIndices=" + + resolveIndices + + ", excludeSensitiveHeaders=" + + excludeSensitiveHeaders + + ", ignoredAuditUsers=" + + ignoredAuditUsersMatcher + + ", ignoreAuditRequests=" + + ignoredAuditRequestsMatcher + + '}'; } } @@ -419,39 +473,36 @@ public String toString() { * List of keys that are deprecated */ public static final List DEPRECATED_KEYS = ImmutableList.of( - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, - ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_REST, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_REST_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_ENABLE_TRANSPORT, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_CONFIG_DISABLED_TRANSPORT_CATEGORIES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_LOG_REQUEST_BODY, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_INDICES, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_EXCLUDE_SENSITIVE_HEADERS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_RESOLVE_BULK_REQUESTS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_IGNORE_REQUESTS, + ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES ); public static Set getDeprecatedKeys(final Settings settings) { - return AuditConfig.DEPRECATED_KEYS - .stream() - .filter(settings::hasValue) - .collect(Collectors.toSet()); + return AuditConfig.DEPRECATED_KEYS.stream().filter(settings::hasValue).collect(Collectors.toSet()); } public static final Set FIELD_PATHS = Sets.union( - Utils.generateFieldResourcePaths(AuditConfig.FIELDS, "/"), - Sets.union( - Utils.generateFieldResourcePaths(Filter.FIELDS, "/audit/"), - Utils.generateFieldResourcePaths(ComplianceConfig.FIELDS, "/compliance/") - ) + Utils.generateFieldResourcePaths(AuditConfig.FIELDS, "/"), + Sets.union( + Utils.generateFieldResourcePaths(Filter.FIELDS, "/audit/"), + Utils.generateFieldResourcePaths(ComplianceConfig.FIELDS, "/compliance/") + ) ); } diff --git a/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java b/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java index a0ef7937ff..a8c44e0cee 100644 --- a/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java +++ b/src/main/java/org/opensearch/security/auditlog/config/ThreadPoolConfig.java @@ -27,7 +27,9 @@ public ThreadPoolConfig(int threadPoolSize, int threadPoolMaxQueueLen) { } if (threadPoolMaxQueueLen <= 0) { - throw new IllegalArgumentException("Incorrect thread pool queue length: " + threadPoolMaxQueueLen + " configured for audit logging."); + throw new IllegalArgumentException( + "Incorrect thread pool queue length: " + threadPoolMaxQueueLen + " configured for audit logging." + ); } this.threadPoolSize = threadPoolSize; @@ -44,7 +46,10 @@ public int getThreadPoolMaxQueueLen() { public static ThreadPoolConfig getConfig(Settings settings) { int threadPoolSize = settings.getAsInt(ConfigConstants.SECURITY_AUDIT_THREADPOOL_SIZE, DEFAULT_THREAD_POOL_SIZE); - int threadPoolMaxQueueLen = settings.getAsInt(ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, DEFAULT_THREAD_POOL_MAX_QUEUE_LEN); + int threadPoolMaxQueueLen = settings.getAsInt( + ConfigConstants.SECURITY_AUDIT_THREADPOOL_MAX_QUEUE_LEN, + DEFAULT_THREAD_POOL_MAX_QUEUE_LEN + ); return new ThreadPoolConfig(threadPoolSize, threadPoolMaxQueueLen); } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java index e4aa062641..e14d5b17a9 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AbstractAuditLog.java @@ -75,7 +75,6 @@ import static org.opensearch.core.xcontent.DeprecationHandler.THROW_UNSUPPORTED_OPERATION; - public abstract class AbstractAuditLog implements AuditLog { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -100,13 +99,22 @@ public abstract class AbstractAuditLog implements AuditLog { writeClasses.add(DeleteRequest.class.getSimpleName()); } - protected AbstractAuditLog(Settings settings, final ThreadPool threadPool, final IndexNameExpressionResolver resolver, final ClusterService clusterService, final Environment environment) { + protected AbstractAuditLog( + Settings settings, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Environment environment + ) { super(); this.threadPool = threadPool; this.settings = settings; this.resolver = resolver; this.clusterService = clusterService; - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.environment = environment; } @@ -133,7 +141,7 @@ public ComplianceConfig getComplianceConfig() { @Override public void logFailedLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.FAILED_LOGIN, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.FAILED_LOGIN, effectiveUser, request)) { return; } @@ -151,7 +159,7 @@ public void logFailedLogin(String effectiveUser, boolean securityadmin, String i @Override public void logSucceededLogin(String effectiveUser, boolean securityadmin, String initiatingUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.AUTHENTICATED, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.AUTHENTICATED, effectiveUser, request)) { return; } @@ -167,7 +175,7 @@ public void logSucceededLogin(String effectiveUser, boolean securityadmin, Strin @Override public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.MISSING_PRIVILEGES, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.MISSING_PRIVILEGES, effectiveUser, request)) { return; } @@ -181,7 +189,7 @@ public void logMissingPrivileges(String privilege, String effectiveUser, RestReq @Override public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - if(!checkRestFilter(AuditCategory.GRANTED_PRIVILEGES, effectiveUser, request)) { + if (!checkRestFilter(AuditCategory.GRANTED_PRIVILEGES, effectiveUser, request)) { return; } @@ -196,14 +204,35 @@ public void logGrantedPrivileges(String effectiveUser, RestRequest request) { public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { final String action = null; - if(!checkTransportFilter(AuditCategory.MISSING_PRIVILEGES, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.MISSING_PRIVILEGES, privilege, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.MISSING_PRIVILEGES, getOrigin(), action, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.MISSING_PRIVILEGES, + getOrigin(), + action, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -212,21 +241,42 @@ public void logMissingPrivileges(String privilege, TransportRequest request, Tas public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { final String action = null; - if(!checkTransportFilter(AuditCategory.GRANTED_PRIVILEGES, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.GRANTED_PRIVILEGES, privilege, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.GRANTED_PRIVILEGES, getOrigin(), action, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.GRANTED_PRIVILEGES, + getOrigin(), + action, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @Override public void logIndexEvent(String privilege, TransportRequest request, Task task) { - if(!checkTransportFilter(AuditCategory.INDEX_EVENT, privilege, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.INDEX_EVENT, privilege, getUser(), request)) { return; } // log only cluster admin action @@ -234,7 +284,28 @@ public void logIndexEvent(String privilege, TransportRequest request, Task task) return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.INDEX_EVENT, getOrigin(), null, privilege, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); + final List msgs = RequestResolver.resolve( + AuditCategory.INDEX_EVENT, + getOrigin(), + null, + privilege, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); msgs.forEach(this::save); } @@ -242,14 +313,35 @@ public void logIndexEvent(String privilege, TransportRequest request, Task task) @Override public void logBadHeaders(TransportRequest request, String action, Task task) { - if(!checkTransportFilter(AuditCategory.BAD_HEADERS, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.BAD_HEADERS, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.BAD_HEADERS, getOrigin(), action, null, getUser(), null, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.BAD_HEADERS, + getOrigin(), + action, + null, + getUser(), + null, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -257,7 +349,7 @@ public void logBadHeaders(TransportRequest request, String action, Task task) { @Override public void logBadHeaders(RestRequest request) { - if(!checkRestFilter(AuditCategory.BAD_HEADERS, getUser(), request)) { + if (!checkRestFilter(AuditCategory.BAD_HEADERS, getUser(), request)) { return; } @@ -273,14 +365,35 @@ public void logBadHeaders(RestRequest request) { @Override public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { - if(!checkTransportFilter(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, getOrigin(), action, null, getUser(), false, null, remoteAddress, request, getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), null); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT, + getOrigin(), + action, + null, + getUser(), + false, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + null + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -288,16 +401,36 @@ public void logSecurityIndexAttempt(TransportRequest request, String action, Tas @Override public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - if(!checkTransportFilter(AuditCategory.SSL_EXCEPTION, action, getUser(), request)) { + if (!checkTransportFilter(AuditCategory.SSL_EXCEPTION, action, getUser(), request)) { return; } final TransportAddress remoteAddress = getRemoteAddress(); - final List msgs = RequestResolver.resolve(AuditCategory.SSL_EXCEPTION, Origin.TRANSPORT, action, null, getUser(), false, null, remoteAddress, request, - getThreadContextHeaders(), task, resolver, clusterService, settings, auditConfigFilter.shouldLogRequestBody(), auditConfigFilter.shouldResolveIndices(), auditConfigFilter.shouldResolveBulkRequests(), securityIndex, auditConfigFilter.shouldExcludeSensitiveHeaders(), t); - - for(AuditMessage msg: msgs) { + final List msgs = RequestResolver.resolve( + AuditCategory.SSL_EXCEPTION, + Origin.TRANSPORT, + action, + null, + getUser(), + false, + null, + remoteAddress, + request, + getThreadContextHeaders(), + task, + resolver, + clusterService, + settings, + auditConfigFilter.shouldLogRequestBody(), + auditConfigFilter.shouldResolveIndices(), + auditConfigFilter.shouldResolveBulkRequests(), + securityIndex, + auditConfigFilter.shouldExcludeSensitiveHeaders(), + t + ); + + for (AuditMessage msg : msgs) { save(msg); } } @@ -305,7 +438,7 @@ public void logSSLException(TransportRequest request, Throwable t, String action @Override public void logSSLException(RestRequest request, Throwable t) { - if(!checkRestFilter(AuditCategory.SSL_EXCEPTION, getUser(), request)) { + if (!checkRestFilter(AuditCategory.SSL_EXCEPTION, getUser(), request)) { return; } @@ -322,36 +455,39 @@ public void logSSLException(RestRequest request, Throwable t) { @Override public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { final ComplianceConfig complianceConfig = getComplianceConfig(); - if(complianceConfig == null || !complianceConfig.readHistoryEnabledForIndex(index)) { + if (complianceConfig == null || !complianceConfig.readHistoryEnabledForIndex(index)) { return; } - final String initiatingRequestClass = threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER); + final String initiatingRequestClass = threadPool.getThreadContext() + .getHeader(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER); - if(initiatingRequestClass != null && writeClasses.contains(initiatingRequestClass)) { + if (initiatingRequestClass != null && writeClasses.contains(initiatingRequestClass)) { return; } - AuditCategory category = securityIndex.equals(index)? AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ: AuditCategory.COMPLIANCE_DOC_READ; + AuditCategory category = securityIndex.equals(index) + ? AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ + : AuditCategory.COMPLIANCE_DOC_READ; String effectiveUser = getUser(); - if(!checkComplianceFilter(category, effectiveUser, getOrigin(), complianceConfig)) { + if (!checkComplianceFilter(category, effectiveUser, getOrigin(), complianceConfig)) { return; } - if(fieldNameValues != null && !fieldNameValues.isEmpty()) { + if (fieldNameValues != null && !fieldNameValues.isEmpty()) { AuditMessage msg = new AuditMessage(category, clusterService, getOrigin(), null); TransportAddress remoteAddress = getRemoteAddress(); msg.addRemoteAddress(remoteAddress); msg.addEffectiveUser(effectiveUser); - msg.addIndices(new String[]{index}); - msg.addResolvedIndices(new String[]{index}); + msg.addIndices(new String[] { index }); + msg.addResolvedIndices(new String[] { index }); msg.addShardId(shardId); - //msg.addIsAdminDn(securityadmin); + // msg.addIsAdminDn(securityadmin); msg.addId(id); try { - if(complianceConfig.shouldLogReadMetadataOnly()) { + if (complianceConfig.shouldLogReadMetadataOnly()) { try { XContentBuilder builder = XContentBuilder.builder(JsonXContent.jsonXContent); builder.startObject(); @@ -363,10 +499,19 @@ public void logDocumentRead(String index, String id, ShardId shardId, Map map = fieldNameValues.entrySet().stream() - .collect(Collectors.toMap(entry -> "id", entry -> new String(BaseEncoding.base64().decode(((Entry) entry).getValue()), StandardCharsets.UTF_8))); + Map map = fieldNameValues.entrySet() + .stream() + .collect( + Collectors.toMap( + entry -> "id", + entry -> new String( + BaseEncoding.base64().decode(((Entry) entry).getValue()), + StandardCharsets.UTF_8 + ) + ) + ); msg.addSecurityConfigMapToRequestBody(Utils.convertJsonToxToStructuredMap(map.get("id")), id); } catch (Exception e) { msg.addSecurityConfigMapToRequestBody(fieldNameValues, id); @@ -376,7 +521,7 @@ public void logDocumentRead(String index, String id, ShardId shardId, Map(XContentType.JSON, currentIndex.source()), id); - } + if (!complianceConfig.shouldLogWriteMetadataOnly()) { + if (securityIndex.equals(shardId.getIndexName())) { + // current source, normally not null or empty + try ( + XContentParser parser = XContentHelper.createParser( + NamedXContentRegistry.EMPTY, + THROW_UNSUPPORTED_OPERATION, + currentIndex.source(), + XContentType.JSON + ) + ) { + Object base64 = parser.map().values().iterator().next(); + if (base64 instanceof String) { + msg.addSecurityConfigContentToRequestBody( + new String(BaseEncoding.base64().decode((String) base64), StandardCharsets.UTF_8), + id + ); + } else { + msg.addSecurityConfigTupleToRequestBody( + new Tuple(XContentType.JSON, currentIndex.source()), + id + ); + } } catch (Exception e) { log.error(e.toString()); } - //if we want to have msg.ComplianceWritePreviousSource we need to do the same as above + // if we want to have msg.ComplianceWritePreviousSource we need to do the same as above } else { - //previous source, can be null if document is a new one - //msg.ComplianceWritePreviousSource(new Tuple(XContentType.JSON, originalResult.internalSourceRef())); + // previous source, can be null if document is a new one + // msg.ComplianceWritePreviousSource(new Tuple(XContentType.JSON, + // originalResult.internalSourceRef())); - //current source, normally not null or empty + // current source, normally not null or empty msg.addTupleToRequestBody(new Tuple(XContentType.JSON, currentIndex.source())); } } - save(msg); } @@ -488,7 +670,9 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu String effectiveUser = getUser(); final ComplianceConfig complianceConfig = getComplianceConfig(); - if (complianceConfig == null || !complianceConfig.isEnabled() || !checkComplianceFilter(AuditCategory.COMPLIANCE_DOC_WRITE, effectiveUser, getOrigin(), complianceConfig)) { + if (complianceConfig == null + || !complianceConfig.isEnabled() + || !checkComplianceFilter(AuditCategory.COMPLIANCE_DOC_WRITE, effectiveUser, getOrigin(), complianceConfig)) { return; } @@ -496,8 +680,8 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu TransportAddress remoteAddress = getRemoteAddress(); msg.addRemoteAddress(remoteAddress); msg.addEffectiveUser(effectiveUser); - msg.addIndices(new String[]{shardId.getIndexName()}); - msg.addResolvedIndices(new String[]{shardId.getIndexName()}); + msg.addIndices(new String[] { shardId.getIndexName() }); + msg.addResolvedIndices(new String[] { shardId.getIndexName() }); msg.addId(delete.id()); msg.addShardId(shardId); msg.addComplianceDocVersion(result.getVersion()); @@ -509,7 +693,11 @@ public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult resu protected void logExternalConfig() { final ComplianceConfig complianceConfig = getComplianceConfig(); - if (complianceConfig == null || !complianceConfig.isEnabled() || !complianceConfig.shouldLogExternalConfig() || !checkComplianceFilter(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, null, getOrigin(), complianceConfig) || externalConfigLogged.getAndSet(true)) { + if (complianceConfig == null + || !complianceConfig.isEnabled() + || !complianceConfig.shouldLogExternalConfig() + || !checkComplianceFilter(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, null, getOrigin(), complianceConfig) + || externalConfigLogged.getAndSet(true)) { return; } @@ -536,7 +724,7 @@ public Map run() { } }); - final String sha256 = DigestUtils.sha256Hex(configAsMap.toString()+envAsMap.toString()+propsAsMap.toString()); + final String sha256 = DigestUtils.sha256Hex(configAsMap.toString() + envAsMap.toString() + propsAsMap.toString()); AuditMessage msg = new AuditMessage(AuditCategory.COMPLIANCE_EXTERNAL_CONFIG, clusterService, null, null); try (XContentBuilder builder = XContentBuilder.builder(XContentType.JSON.xContent())) { @@ -551,50 +739,54 @@ public Map run() { builder.close(); msg.addUnescapedJsonToRequestBody(Strings.toString(builder)); } catch (Exception e) { - log.error("Unable to build message",e); + log.error("Unable to build message", e); } Map paths = new HashMap(); - for(String key: settings.keySet()) { - if(key.startsWith("opendistro_security") && - (key.contains("filepath") || key.contains("file_path"))) { + for (String key : settings.keySet()) { + if (key.startsWith("opendistro_security") && (key.contains("filepath") || key.contains("file_path"))) { String value = settings.get(key); - if(value != null && !value.isEmpty()) { - Path path = value.startsWith("/")?Paths.get(value):environment.configDir().resolve(value); + if (value != null && !value.isEmpty()) { + Path path = value.startsWith("/") ? Paths.get(value) : environment.configDir().resolve(value); paths.put(key, path); } } } msg.addFileInfos(paths); - save(msg); } private Origin getOrigin() { String origin = (String) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); - if(origin == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) != null) { + if (origin == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) != null) { origin = threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER); } - return origin == null?null:Origin.valueOf(origin); + return origin == null ? null : Origin.valueOf(origin); } private TransportAddress getRemoteAddress() { TransportAddress address = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - if(address == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) != null) { - address = new TransportAddress((InetSocketAddress) Base64Helper.deserializeObject(threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER))); + if (address == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) != null) { + address = new TransportAddress( + (InetSocketAddress) Base64Helper.deserializeObject( + threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) + ) + ); } return address; } private String getUser() { User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) != null) { - user = (User) Base64Helper.deserializeObject(threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER)); + if (user == null && threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) != null) { + user = (User) Base64Helper.deserializeObject( + threadPool.getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) + ); } - return user==null?null:user.getName(); + return user == null ? null : user.getName(); } private Map getThreadContextHeaders() { @@ -605,15 +797,20 @@ private Map getThreadContextHeaders() { boolean checkTransportFilter(final AuditCategory category, final String action, final String effectiveUser, TransportRequest request) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("Check category:{}, action:{}, effectiveUser:{}, request:{}", category, action, effectiveUser, request==null?null:request.getClass().getSimpleName()); + log.trace( + "Check category:{}, action:{}, effectiveUser:{}, request:{}", + category, + action, + effectiveUser, + request == null ? null : request.getClass().getSimpleName() + ); } - if (!auditConfigFilter.isTransportApiAuditEnabled()) { return false; } - //skip internals + // skip internals if (action != null && action.startsWith("internal:")) { return false; } @@ -627,10 +824,12 @@ boolean checkTransportFilter(final AuditCategory category, final String action, return false; } - if (request != null && (auditConfigFilter.isRequestAuditDisabled(action) || auditConfigFilter.isRequestAuditDisabled(request.getClass().getSimpleName()))) { + if (request != null + && (auditConfigFilter.isRequestAuditDisabled(action) + || auditConfigFilter.isRequestAuditDisabled(request.getClass().getSimpleName()))) { if (isTraceEnabled) { - log.trace("Skipped audit log message because request {} is ignored", action+"#"+request.getClass().getSimpleName()); + log.trace("Skipped audit log message because request {} is ignored", action + "#" + request.getClass().getSimpleName()); } return false; @@ -645,29 +844,33 @@ boolean checkTransportFilter(final AuditCategory category, final String action, return false; } - - //skip internal:* - //check transport audit enabled - //check category enabled - //check action - //check ignoreAuditUsers + // skip internal:* + // check transport audit enabled + // check category enabled + // check action + // check ignoreAuditUsers } - private boolean checkComplianceFilter(final AuditCategory category, final String effectiveUser, Origin origin, ComplianceConfig complianceConfig) { + private boolean checkComplianceFilter( + final AuditCategory category, + final String effectiveUser, + Origin origin, + ComplianceConfig complianceConfig + ) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("Check for COMPLIANCE category:{}, effectiveUser:{}, origin: {}", category, effectiveUser, origin); } - if(origin == Origin.LOCAL && effectiveUser == null && category != AuditCategory.COMPLIANCE_EXTERNAL_CONFIG) { + if (origin == Origin.LOCAL && effectiveUser == null && category != AuditCategory.COMPLIANCE_EXTERNAL_CONFIG) { if (isTraceEnabled) { log.trace("Skipped compliance log message because of null user and local origin"); } return false; } - if(category == AuditCategory.COMPLIANCE_DOC_READ || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ) { + if (category == AuditCategory.COMPLIANCE_DOC_READ || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_READ) { if (effectiveUser != null && complianceConfig.isComplianceReadAuditDisabled(effectiveUser)) { @@ -678,7 +881,7 @@ private boolean checkComplianceFilter(final AuditCategory category, final String } } - if(category == AuditCategory.COMPLIANCE_DOC_WRITE || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE) { + if (category == AuditCategory.COMPLIANCE_DOC_WRITE || category == AuditCategory.COMPLIANCE_INTERNAL_CONFIG_WRITE) { if (effectiveUser != null && complianceConfig.isComplianceWriteAuditDisabled(effectiveUser)) { if (isTraceEnabled) { @@ -695,7 +898,12 @@ private boolean checkComplianceFilter(final AuditCategory category, final String boolean checkRestFilter(final AuditCategory category, final String effectiveUser, RestRequest request) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("Check for REST category:{}, effectiveUser:{}, request:{}", category, effectiveUser, request==null?null:request.path()); + log.trace( + "Check for REST category:{}, effectiveUser:{}, request:{}", + category, + effectiveUser, + request == null ? null : request.path() + ); } if (!auditConfigFilter.isRestApiAuditEnabled()) { @@ -729,13 +937,11 @@ boolean checkRestFilter(final AuditCategory category, final String effectiveUser return false; } - - //check rest audit enabled - //check category enabled - //check action - //check ignoreAuditUsers + // check rest audit enabled + // check category enabled + // check action + // check ignoreAuditUsers } - protected abstract void save(final AuditMessage msg); } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java index 20f8b4e777..caf6938b14 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditCategory.java @@ -33,13 +33,8 @@ public enum AuditCategory { COMPLIANCE_INTERNAL_CONFIG_WRITE; public static Set parse(final Collection categories) { - if (categories.isEmpty()) - return Collections.emptySet(); + if (categories.isEmpty()) return Collections.emptySet(); - return categories - .stream() - .map(String::toUpperCase) - .map(AuditCategory::valueOf) - .collect(ImmutableSet.toImmutableSet()); + return categories.stream().map(String::toUpperCase).map(AuditCategory::valueOf).collect(ImmutableSet.toImmutableSet()); } } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java index be5068fc14..a09c6a694e 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditLogImpl.java @@ -40,54 +40,58 @@ public final class AuditLogImpl extends AbstractAuditLog { - private final AuditMessageRouter messageRouter; - private final Settings settings; - private final boolean messageRouterEnabled; - private volatile boolean enabled; - private final Thread shutdownHook; - - public AuditLogImpl(final Settings settings, - final Path configPath, - final Client clientProvider, - final ThreadPool threadPool, - final IndexNameExpressionResolver resolver, - final ClusterService clusterService) { - this(settings, configPath, clientProvider, threadPool, resolver, clusterService, null); - } + private final AuditMessageRouter messageRouter; + private final Settings settings; + private final boolean messageRouterEnabled; + private volatile boolean enabled; + private final Thread shutdownHook; + + public AuditLogImpl( + final Settings settings, + final Path configPath, + final Client clientProvider, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService + ) { + this(settings, configPath, clientProvider, threadPool, resolver, clusterService, null); + } @SuppressWarnings("removal") - public AuditLogImpl(final Settings settings, - final Path configPath, - final Client clientProvider, - final ThreadPool threadPool, - final IndexNameExpressionResolver resolver, - final ClusterService clusterService, - final Environment environment) { - super(settings, threadPool, resolver, clusterService, environment); - this.settings = settings; - this.messageRouter = new AuditMessageRouter(settings, clientProvider, threadPool, configPath); - this.messageRouterEnabled = this.messageRouter.isEnabled(); - - log.info("Message routing enabled: {}", this.messageRouterEnabled); - - SpecialPermission.check(); - shutdownHook = AccessController.doPrivileged((PrivilegedAction) this::addShutdownHook); - log.debug("Shutdown hook {} registered", shutdownHook); - } - - @Subscribe - public void setConfig(final AuditConfig auditConfig) { - enabled = auditConfig.isEnabled() && messageRouterEnabled; - onAuditConfigFilterChanged(auditConfig.getFilter()); - onComplianceConfigChanged(auditConfig.getCompliance()); - } - - @Override - protected void enableRoutes() { - if (messageRouterEnabled) { - messageRouter.enableRoutes(settings); - } - } + public AuditLogImpl( + final Settings settings, + final Path configPath, + final Client clientProvider, + final ThreadPool threadPool, + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Environment environment + ) { + super(settings, threadPool, resolver, clusterService, environment); + this.settings = settings; + this.messageRouter = new AuditMessageRouter(settings, clientProvider, threadPool, configPath); + this.messageRouterEnabled = this.messageRouter.isEnabled(); + + log.info("Message routing enabled: {}", this.messageRouterEnabled); + + SpecialPermission.check(); + shutdownHook = AccessController.doPrivileged((PrivilegedAction) this::addShutdownHook); + log.debug("Shutdown hook {} registered", shutdownHook); + } + + @Subscribe + public void setConfig(final AuditConfig auditConfig) { + enabled = auditConfig.isEnabled() && messageRouterEnabled; + onAuditConfigFilterChanged(auditConfig.getFilter()); + onComplianceConfigChanged(auditConfig.getCompliance()); + } + + @Override + protected void enableRoutes() { + if (messageRouterEnabled) { + messageRouter.enableRoutes(settings); + } + } private Thread addShutdownHook() { Thread shutdownHook = new Thread(() -> messageRouter.close()); @@ -119,123 +123,123 @@ public void close() throws IOException { } } - @Override - protected void save(final AuditMessage msg) { - if (enabled) { - messageRouter.route(msg); - } - } - - @Override - public void logFailedLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { - if (enabled) { - super.logFailedLogin(effectiveUser, securityAdmin, initiatingUser, request); - } - } - - @Override - public void logSucceededLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { - if (enabled) { - super.logSucceededLogin(effectiveUser, securityAdmin, initiatingUser, request); - } - } - - @Override - public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { - if (enabled) { - super.logMissingPrivileges(privilege, effectiveUser, request); - } - } - - @Override - public void logGrantedPrivileges(String effectiveUser, RestRequest request) { - if (enabled) { - super.logGrantedPrivileges(effectiveUser, request); - } - } - - @Override - public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logMissingPrivileges(privilege, request, task); - } - } - - @Override - public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logGrantedPrivileges(privilege, request, task); - } - } - - @Override - public void logIndexEvent(String privilege, TransportRequest request, Task task) { - if (enabled) { - super.logIndexEvent(privilege, request, task); - } - } - - @Override - public void logBadHeaders(TransportRequest request, String action, Task task) { - if (enabled) { - super.logBadHeaders(request, action, task); - } - } - - @Override - public void logBadHeaders(RestRequest request) { - if (enabled) { - super.logBadHeaders(request); - } - } - - @Override - public void logSecurityIndexAttempt (TransportRequest request, String action, Task task) { - if (enabled) { - super.logSecurityIndexAttempt(request, action, task); - } - } - - @Override - public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { - if (enabled) { - super.logSSLException(request, t, action, task); - } - } - - @Override - public void logSSLException(RestRequest request, Throwable t) { - if (enabled) { - super.logSSLException(request, t); - } - } - - @Override - public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { - if (enabled) { - super.logDocumentRead(index, id, shardId, fieldNameValues); - } - } - - @Override - public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index currentIndex, IndexResult result) { - if (enabled) { - super.logDocumentWritten(shardId, originalResult, currentIndex, result); - } - } - - @Override - public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { - if (enabled) { - super.logDocumentDeleted(shardId, delete, result); - } - } - - @Override - protected void logExternalConfig() { - if (enabled) { - super.logExternalConfig(); - } - } + @Override + protected void save(final AuditMessage msg) { + if (enabled) { + messageRouter.route(msg); + } + } + + @Override + public void logFailedLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { + if (enabled) { + super.logFailedLogin(effectiveUser, securityAdmin, initiatingUser, request); + } + } + + @Override + public void logSucceededLogin(String effectiveUser, boolean securityAdmin, String initiatingUser, RestRequest request) { + if (enabled) { + super.logSucceededLogin(effectiveUser, securityAdmin, initiatingUser, request); + } + } + + @Override + public void logMissingPrivileges(String privilege, String effectiveUser, RestRequest request) { + if (enabled) { + super.logMissingPrivileges(privilege, effectiveUser, request); + } + } + + @Override + public void logGrantedPrivileges(String effectiveUser, RestRequest request) { + if (enabled) { + super.logGrantedPrivileges(effectiveUser, request); + } + } + + @Override + public void logMissingPrivileges(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logMissingPrivileges(privilege, request, task); + } + } + + @Override + public void logGrantedPrivileges(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logGrantedPrivileges(privilege, request, task); + } + } + + @Override + public void logIndexEvent(String privilege, TransportRequest request, Task task) { + if (enabled) { + super.logIndexEvent(privilege, request, task); + } + } + + @Override + public void logBadHeaders(TransportRequest request, String action, Task task) { + if (enabled) { + super.logBadHeaders(request, action, task); + } + } + + @Override + public void logBadHeaders(RestRequest request) { + if (enabled) { + super.logBadHeaders(request); + } + } + + @Override + public void logSecurityIndexAttempt(TransportRequest request, String action, Task task) { + if (enabled) { + super.logSecurityIndexAttempt(request, action, task); + } + } + + @Override + public void logSSLException(TransportRequest request, Throwable t, String action, Task task) { + if (enabled) { + super.logSSLException(request, t, action, task); + } + } + + @Override + public void logSSLException(RestRequest request, Throwable t) { + if (enabled) { + super.logSSLException(request, t); + } + } + + @Override + public void logDocumentRead(String index, String id, ShardId shardId, Map fieldNameValues) { + if (enabled) { + super.logDocumentRead(index, id, shardId, fieldNameValues); + } + } + + @Override + public void logDocumentWritten(ShardId shardId, GetResult originalResult, Index currentIndex, IndexResult result) { + if (enabled) { + super.logDocumentWritten(shardId, originalResult, currentIndex, result); + } + } + + @Override + public void logDocumentDeleted(ShardId shardId, Delete delete, DeleteResult result) { + if (enabled) { + super.logDocumentDeleted(shardId, delete, result); + } + } + + @Override + protected void logExternalConfig() { + if (enabled) { + super.logExternalConfig(); + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java index 8931d44690..9b1d70d41c 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/AuditMessage.java @@ -55,13 +55,14 @@ public final class AuditMessage { - //clustername and cluster uuid + // clustername and cluster uuid private static final WildcardMatcher AUTHORIZATION_HEADER = WildcardMatcher.from("Authorization", false); private static final String SENSITIVE_KEY = "password"; private static final String SENSITIVE_REPLACEMENT_VALUE = "__SENSITIVE__"; - private static final Pattern SENSITIVE_PATHS = - Pattern.compile( "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/api/(account.*|internalusers.*|user.*)"); + private static final Pattern SENSITIVE_PATHS = Pattern.compile( + "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/api/(account.*|internalusers.*|user.*)" + ); @VisibleForTesting public static final Pattern BCRYPT_HASH = Pattern.compile("\\$2[ayb]\\$.{56}"); @@ -93,8 +94,8 @@ public final class AuditMessage { public static final String TRANSPORT_REQUEST_HEADERS = "audit_transport_headers"; public static final String ID = "audit_trace_doc_id"; - //public static final String TYPES = "audit_trace_doc_types"; - //public static final String SOURCE = "audit_trace_doc_source"; + // public static final String TYPES = "audit_trace_doc_types"; + // public static final String SOURCE = "audit_trace_doc_source"; public static final String INDICES = "audit_trace_indices"; public static final String SHARD_ID = "audit_trace_shard_id"; public static final String RESOLVED_INDICES = "audit_trace_resolved_indices"; @@ -111,8 +112,8 @@ public final class AuditMessage { public static final String COMPLIANCE_DIFF_CONTENT = "audit_compliance_diff_content"; public static final String COMPLIANCE_FILE_INFOS = "audit_compliance_file_infos"; - //public static final String COMPLIANCE_DIFF_STORED_IS_NOOP = "audit_compliance_diff_stored_is_noop"; - //public static final String COMPLIANCE_STORED_FIELDS_CONTENT = "audit_compliance_stored_fields_content"; + // public static final String COMPLIANCE_DIFF_STORED_IS_NOOP = "audit_compliance_diff_stored_is_noop"; + // public static final String COMPLIANCE_STORED_FIELDS_CONTENT = "audit_compliance_stored_fields_content"; public static final String REQUEST_LAYER = "audit_request_layer"; @@ -135,11 +136,11 @@ public AuditMessage(final AuditCategory msgCategory, final ClusterService cluste auditInfo.put(NODE_NAME, Objects.requireNonNull(clusterService).localNode().getName()); auditInfo.put(CLUSTER_NAME, Objects.requireNonNull(clusterService).getClusterName().value()); - if(origin != null) { + if (origin != null) { auditInfo.put(ORIGIN, origin); } - if(layer != null) { + if (layer != null) { auditInfo.put(REQUEST_LAYER, layer); } } @@ -197,25 +198,25 @@ void addSecurityConfigWriteDiffSource(final String diff, final String id) { addComplianceWriteDiffSource(redactSecurityConfigContent(diff, id)); } -// public void addComplianceWriteStoredFields0(String diff) { -// if (diff != null && !diff.isEmpty()) { -// auditInfo.put(COMPLIANCE_STORED_FIELDS_CONTENT, diff); -// //auditInfo.put(COMPLIANCE_DIFF_STORED_IS_NOOP, false); -// } -// } + // public void addComplianceWriteStoredFields0(String diff) { + // if (diff != null && !diff.isEmpty()) { + // auditInfo.put(COMPLIANCE_STORED_FIELDS_CONTENT, diff); + // //auditInfo.put(COMPLIANCE_DIFF_STORED_IS_NOOP, false); + // } + // } public void addTupleToRequestBody(Tuple xContentTuple) { if (xContentTuple != null) { try { auditInfo.put(REQUEST_BODY, XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1())); } catch (Exception e) { - auditInfo.put(REQUEST_BODY, "ERROR: Unable to convert to json because of "+e.toString()); + auditInfo.put(REQUEST_BODY, "ERROR: Unable to convert to json because of " + e.toString()); } } } public void addMapToRequestBody(Map map) { - if(map != null) { + if (map != null) { auditInfo.put(REQUEST_BODY, Utils.convertStructuredMapToJson(map)); } } @@ -289,10 +290,10 @@ public void addType(String type) { public void addFileInfos(Map paths) { if (paths != null && !paths.isEmpty()) { List infos = new ArrayList<>(); - for(Entry path: paths.entrySet()) { + for (Entry path : paths.entrySet()) { try { - if(Files.isReadable(path.getValue())) { + if (Files.isReadable(path.getValue())) { final String chcksm = DigestUtils.sha256Hex(Files.readAllBytes(path.getValue())); FileTime lm = Files.getLastModifiedTime(path.getValue(), LinkOption.NOFOLLOW_LINKS); Map innerInfos = new HashMap<>(); @@ -303,7 +304,7 @@ public void addFileInfos(Map paths) { infos.add(innerInfos); } } catch (Throwable e) { - //ignore non readable files + // ignore non readable files } } auditInfo.put(COMPLIANCE_FILE_INFOS, infos); @@ -330,29 +331,29 @@ public void addResolvedIndices(String[] resolvedIndices) { } public void addTaskId(long id) { - auditInfo.put(TASK_ID, auditInfo.get(NODE_ID)+":"+id); + auditInfo.put(TASK_ID, auditInfo.get(NODE_ID) + ":" + id); } public void addShardId(ShardId id) { - if(id != null) { + if (id != null) { auditInfo.put(SHARD_ID, id.getId()); } - } + } public void addTaskParentId(String id) { - if(id != null) { + if (id != null) { auditInfo.put(TASK_PARENT_ID, id); } } - public void addRestParams(Map params) { - if(params != null && !params.isEmpty()) { + public void addRestParams(Map params) { + if (params != null && !params.isEmpty()) { auditInfo.put(REST_REQUEST_PARAMS, new HashMap<>(params)); } } - public void addRestHeaders(Map> headers, boolean excludeSensitiveHeaders) { - if(headers != null && !headers.isEmpty()) { + public void addRestHeaders(Map> headers, boolean excludeSensitiveHeaders) { + if (headers != null && !headers.isEmpty()) { final Map> headersClone = new HashMap<>(headers); if (excludeSensitiveHeaders) { headersClone.keySet().removeIf(AUTHORIZATION_HEADER); @@ -377,10 +378,11 @@ void addRestRequestInfo(final RestRequest request, final AuditConfig.Filter filt if (filter.shouldLogRequestBody() && request.hasContentOrSourceParam()) { try { final Tuple xContentTuple = request.contentOrSourceParam(); - final String requestBody = XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1()); - if (path != null && requestBody != null - && SENSITIVE_PATHS.matcher(path).matches() - && requestBody.contains(SENSITIVE_KEY)) { + final String requestBody = XContentHelper.convertToJson(xContentTuple.v2(), false, xContentTuple.v1()); + if (path != null + && requestBody != null + && SENSITIVE_PATHS.matcher(path).matches() + && requestBody.contains(SENSITIVE_KEY)) { auditInfo.put(REQUEST_BODY, SENSITIVE_REPLACEMENT_VALUE); } else { auditInfo.put(REQUEST_BODY, requestBody); @@ -392,8 +394,8 @@ void addRestRequestInfo(final RestRequest request, final AuditConfig.Filter filt } } - public void addTransportHeaders(Map headers, boolean excludeSensitiveHeaders) { - if(headers != null && !headers.isEmpty()) { + public void addTransportHeaders(Map headers, boolean excludeSensitiveHeaders) { + if (headers != null && !headers.isEmpty()) { final Map headersClone = new HashMap<>(headers); if (excludeSensitiveHeaders) { headersClone.keySet().removeIf(AUTHORIZATION_HEADER); @@ -403,7 +405,7 @@ public void addTransportHeaders(Map headers, boolean excludeSensi } public void addComplianceOperation(Operation op) { - if(op != null) { + if (op != null) { auditInfo.put(COMPLIANCE_OPERATION, op); } } @@ -413,7 +415,7 @@ public void addComplianceDocVersion(long version) { } public Map getAsMap() { - return new HashMap<>(this.auditInfo); + return new HashMap<>(this.auditInfo); } public String getInitiatingUser() { @@ -432,9 +434,9 @@ public RestRequest.Method getRequestMethod() { return (RestRequest.Method) this.auditInfo.get(REST_REQUEST_METHOD); } - public AuditCategory getCategory() { - return msgCategory; - } + public AuditCategory getCategory() { + return msgCategory; + } public Origin getOrigin() { return (Origin) this.auditInfo.get(ORIGIN); @@ -460,14 +462,14 @@ public String getDocId() { return (String) this.auditInfo.get(ID); } - @Override - public String toString() { - try { - return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); - } catch (final IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } + @Override + public String toString() { + try { + return org.opensearch.common.Strings.toString(JsonXContent.contentBuilder().map(getAsMap())); + } catch (final IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } public String toPrettyString() { try { @@ -477,34 +479,34 @@ public String toPrettyString() { } } - public String toText() { - StringBuilder builder = new StringBuilder(); - for (Entry entry : getAsMap().entrySet()) { - addIfNonEmpty(builder, entry.getKey(), stringOrNull(entry.getValue())); - } - return builder.toString(); - } - - public final String toJson() { - return this.toString(); - } - - public String toUrlParameters() { - URIBuilder builder = new URIBuilder(); - for (Entry entry : getAsMap().entrySet()) { - builder.addParameter(entry.getKey(), stringOrNull(entry.getValue())); - } - return builder.toString(); - } - - protected static void addIfNonEmpty(StringBuilder builder, String key, String value) { - if (!Strings.isEmpty(value)) { - if (builder.length() > 0) { - builder.append("\n"); - } - builder.append(key).append(": ").append(value); - } - } + public String toText() { + StringBuilder builder = new StringBuilder(); + for (Entry entry : getAsMap().entrySet()) { + addIfNonEmpty(builder, entry.getKey(), stringOrNull(entry.getValue())); + } + return builder.toString(); + } + + public final String toJson() { + return this.toString(); + } + + public String toUrlParameters() { + URIBuilder builder = new URIBuilder(); + for (Entry entry : getAsMap().entrySet()) { + builder.addParameter(entry.getKey(), stringOrNull(entry.getValue())); + } + return builder.toString(); + } + + protected static void addIfNonEmpty(StringBuilder builder, String key, String value) { + if (!Strings.isEmpty(value)) { + if (builder.length() > 0) { + builder.append("\n"); + } + builder.append(key).append(": ").append(value); + } + } private String currentTime() { DateTime dt = new DateTime(DateTimeZone.UTC); @@ -517,7 +519,7 @@ private String formatTime(long epoch) { } protected String stringOrNull(Object object) { - if(object == null) { + if (object == null) { return null; } diff --git a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java index 5b4881d1eb..ecf7a2bd36 100644 --- a/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java +++ b/src/main/java/org/opensearch/security/auditlog/impl/RequestResolver.java @@ -64,54 +64,56 @@ public final class RequestResolver { private static final Logger log = LogManager.getLogger(RequestResolver.class); public static List resolve( - final AuditCategory category, - final Origin origin, - final String action, - final String privilege, - final String effectiveUser, - final Boolean securityadmin, - final String initiatingUser, - final TransportAddress remoteAddress, - final TransportRequest request, - final Map headers, - final Task task, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final Settings settings, - final boolean logRequestBody, - final boolean resolveIndices, - final boolean resolveBulk, - final String securityIndex, - final boolean excludeSensitiveHeaders, - final Throwable exception) { - - if(resolveBulk && request instanceof BulkShardRequest) { + final AuditCategory category, + final Origin origin, + final String action, + final String privilege, + final String effectiveUser, + final Boolean securityadmin, + final String initiatingUser, + final TransportAddress remoteAddress, + final TransportRequest request, + final Map headers, + final Task task, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final Settings settings, + final boolean logRequestBody, + final boolean resolveIndices, + final boolean resolveBulk, + final String securityIndex, + final boolean excludeSensitiveHeaders, + final Throwable exception + ) { + + if (resolveBulk && request instanceof BulkShardRequest) { final BulkItemRequest[] innerRequests = ((BulkShardRequest) request).items(); final List messages = new ArrayList(innerRequests.length); - for(BulkItemRequest ar: innerRequests) { + for (BulkItemRequest ar : innerRequests) { final DocWriteRequest innerRequest = ar.request(); final AuditMessage msg = resolveInner( - category, - effectiveUser, - securityadmin, - initiatingUser, - remoteAddress, - action, - privilege, - origin, - innerRequest, - headers, - task, - resolver, - cs, - settings, - logRequestBody, - resolveIndices, - securityIndex, - excludeSensitiveHeaders, - exception); - msg.addShardId(((BulkShardRequest) request).shardId()); + category, + effectiveUser, + securityadmin, + initiatingUser, + remoteAddress, + action, + privilege, + origin, + innerRequest, + headers, + task, + resolver, + cs, + settings, + logRequestBody, + resolveIndices, + securityIndex, + excludeSensitiveHeaders, + exception + ); + msg.addShardId(((BulkShardRequest) request).shardId()); messages.add(msg); } @@ -119,17 +121,18 @@ public static List resolve( return messages; } - if(request instanceof BulkShardRequest) { + if (request instanceof BulkShardRequest) { - if(category != AuditCategory.FAILED_LOGIN - && category != AuditCategory.MISSING_PRIVILEGES - && category != AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT) { + if (category != AuditCategory.FAILED_LOGIN + && category != AuditCategory.MISSING_PRIVILEGES + && category != AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT) { return Collections.emptyList(); } } - return Collections.singletonList(resolveInner( + return Collections.singletonList( + resolveInner( category, effectiveUser, securityadmin, @@ -148,29 +151,32 @@ public static List resolve( resolveIndices, securityIndex, excludeSensitiveHeaders, - exception)); + exception + ) + ); } - - private static AuditMessage resolveInner(final AuditCategory category, - final String effectiveUser, - final Boolean securityadmin, - final String initiatingUser, - final TransportAddress remoteAddress, - final String action, - final String priv, - final Origin origin, - final Object request, - final Map headers, - final Task task, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final Settings settings, - final boolean logRequestBody, - final boolean resolveIndices, - final String securityIndex, - final boolean excludeSensitiveHeaders, - final Throwable exception) { + private static AuditMessage resolveInner( + final AuditCategory category, + final String effectiveUser, + final Boolean securityadmin, + final String initiatingUser, + final TransportAddress remoteAddress, + final String action, + final String priv, + final Origin origin, + final Object request, + final Map headers, + final Task task, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final Settings settings, + final boolean logRequestBody, + final boolean resolveIndices, + final String securityIndex, + final boolean excludeSensitiveHeaders, + final Throwable exception + ) { final AuditMessage msg = new AuditMessage(category, cs, origin, Origin.TRANSPORT); msg.addInitiatingUser(initiatingUser); @@ -178,11 +184,11 @@ private static AuditMessage resolveInner(final AuditCategory category, msg.addRemoteAddress(remoteAddress); msg.addAction(action); - if(request != null) { + if (request != null) { msg.addRequestType(request.getClass().getSimpleName()); } - if(securityadmin != null) { + if (securityadmin != null) { msg.addIsAdminDn(securityadmin); } @@ -190,14 +196,14 @@ private static AuditMessage resolveInner(final AuditCategory category, msg.addPrivilege(priv); msg.addTransportHeaders(headers, excludeSensitiveHeaders); - if(task != null) { + if (task != null) { msg.addTaskId(task.getId()); - if(task.getParentTaskId() != null && task.getParentTaskId().isSet()) { + if (task.getParentTaskId() != null && task.getParentTaskId().isSet()) { msg.addTaskParentId(task.getParentTaskId().toString()); } } - //attempt to resolve indices/types/id/source + // attempt to resolve indices/types/id/source if (request instanceof MultiGetRequest.Item) { final MultiGetRequest.Item item = (MultiGetRequest.Item) request; final String[] indices = arrayOrEmpty(item.indices()); @@ -221,7 +227,7 @@ private static AuditMessage resolveInner(final AuditCategory category, } else if (request instanceof DeleteIndexRequest) { final DeleteIndexRequest dir = (DeleteIndexRequest) request; final String[] indices = arrayOrEmpty(dir.indices()); - //dir id alle id's beim schreiben protokolloieren + // dir id alle id's beim schreiben protokolloieren addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); } else if (request instanceof IndexRequest) { final IndexRequest ir = (IndexRequest) request; @@ -229,7 +235,19 @@ private static AuditMessage resolveInner(final AuditCategory category, final String id = ir.id(); msg.addShardId(ir.shardId()); msg.addId(id); - addIndicesSourceSafe(msg, indices, resolver, cs, ir.getContentType(), ir.source(), settings, resolveIndices, logRequestBody, true, securityIndex); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + ir.getContentType(), + ir.source(), + settings, + resolveIndices, + logRequestBody, + true, + securityIndex + ); } else if (request instanceof DeleteRequest) { final DeleteRequest dr = (DeleteRequest) request; final String[] indices = arrayOrEmpty(dr.indices()); @@ -243,10 +261,10 @@ private static AuditMessage resolveInner(final AuditCategory category, final String id = ur.id(); msg.addId(id); addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); - if(logRequestBody) { + if (logRequestBody) { if (ur.doc() != null) { - msg.addTupleToRequestBody(ur.doc() == null ? null :convertSource(ur.doc().getContentType(), ur.doc().source())); + msg.addTupleToRequestBody(ur.doc() == null ? null : convertSource(ur.doc().getContentType(), ur.doc().source())); } if (ur.script() != null) { @@ -263,10 +281,22 @@ private static AuditMessage resolveInner(final AuditCategory category, final SearchRequest sr = (SearchRequest) request; final String[] indices = arrayOrEmpty(sr.indices()); - Map sourceAsMap = sr.source() == null? null:Utils.convertJsonToxToStructuredMap(sr.source()); - addIndicesSourceSafe(msg, indices, resolver, cs, XContentType.JSON, sourceAsMap, settings, resolveIndices, logRequestBody, false, securityIndex); + Map sourceAsMap = sr.source() == null ? null : Utils.convertJsonToxToStructuredMap(sr.source()); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + XContentType.JSON, + sourceAsMap, + settings, + resolveIndices, + logRequestBody, + false, + securityIndex + ); } else if (request instanceof ClusterUpdateSettingsRequest) { - if(logRequestBody) { + if (logRequestBody) { final ClusterUpdateSettingsRequest cusr = (ClusterUpdateSettingsRequest) request; final Settings persistentSettings = cusr.persistentSettings(); final Settings transientSettings = cusr.transientSettings(); @@ -276,31 +306,42 @@ private static AuditMessage resolveInner(final AuditCategory category, builder = XContentFactory.jsonBuilder(); builder.startObject(); - if(persistentSettings != null) { + if (persistentSettings != null) { builder.field("persistent_settings", Utils.convertJsonToxToStructuredMap(persistentSettings)); } - if(transientSettings != null) { + if (transientSettings != null) { builder.field("transient_settings", Utils.convertJsonToxToStructuredMap(persistentSettings)); } builder.endObject(); - msg.addUnescapedJsonToRequestBody(builder == null?null:Strings.toString(builder)); + msg.addUnescapedJsonToRequestBody(builder == null ? null : Strings.toString(builder)); } catch (IOException e) { log.error(e.toString()); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } - - } + } } else if (request instanceof ReindexRequest) { final IndexRequest ir = ((ReindexRequest) request).getDestination(); final String[] indices = arrayOrEmpty(ir.indices()); final String id = ir.id(); msg.addShardId(ir.shardId()); msg.addId(id); - addIndicesSourceSafe(msg, indices, resolver, cs, ir.getContentType(), ir.source(), settings, resolveIndices, logRequestBody, true, securityIndex); + addIndicesSourceSafe( + msg, + indices, + resolver, + cs, + ir.getContentType(), + ir.source(), + settings, + resolveIndices, + logRequestBody, + true, + securityIndex + ); } else if (request instanceof DeleteByQueryRequest) { final DeleteByQueryRequest ir = (DeleteByQueryRequest) request; final String[] indices = arrayOrEmpty(ir.indices()); @@ -315,18 +356,18 @@ private static AuditMessage resolveInner(final AuditCategory category, String[] indices = new String[0]; msg.addIndices(indices); - if(ci != null) { - indices = new String[]{ci.getName()}; + if (ci != null) { + indices = new String[] { ci.getName() }; } - if(logRequestBody) { + if (logRequestBody) { msg.addUnescapedJsonToRequestBody(pr.source()); } - if(resolveIndices) { + if (resolveIndices) { msg.addResolvedIndices(indices); } - } else if (request instanceof IndicesRequest) { //less specific + } else if (request instanceof IndicesRequest) { // less specific final IndicesRequest ir = (IndicesRequest) request; final String[] indices = arrayOrEmpty(ir.indices()); addIndicesSourceSafe(msg, indices, resolver, cs, null, null, settings, resolveIndices, logRequestBody, false, securityIndex); @@ -335,66 +376,70 @@ private static AuditMessage resolveInner(final AuditCategory category, return msg; } - private static void addIndicesSourceSafe(final AuditMessage msg, - final String[] indices, - final IndexNameExpressionResolver resolver, - final ClusterService cs, - final XContentType xContentType, - final Object source, - final Settings settings, - boolean resolveIndices, - final boolean addSource, - final boolean sourceIsSensitive, - final String securityIndex) { - - if(addSource) { + private static void addIndicesSourceSafe( + final AuditMessage msg, + final String[] indices, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final XContentType xContentType, + final Object source, + final Settings settings, + boolean resolveIndices, + final boolean addSource, + final boolean sourceIsSensitive, + final String securityIndex + ) { + + if (addSource) { resolveIndices = true; } - final String[] _indices = indices == null?new String[0]:indices; + final String[] _indices = indices == null ? new String[0] : indices; msg.addIndices(_indices); final Set allIndices; - if(resolveIndices) { - final String[] resolvedIndices = (resolver==null)?new String[0]:resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), indices); + if (resolveIndices) { + final String[] resolvedIndices = (resolver == null) + ? new String[0] + : resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), indices); msg.addResolvedIndices(resolvedIndices); - allIndices = new HashSet(resolvedIndices.length+_indices.length); + allIndices = new HashSet(resolvedIndices.length + _indices.length); allIndices.addAll(Arrays.asList(_indices)); allIndices.addAll(Arrays.asList(resolvedIndices)); - if(allIndices.contains("_all")) { - allIndices.add("*"); //TODO: maybe replace allIndices instead of add? + if (allIndices.contains("_all")) { + allIndices.add("*"); // TODO: maybe replace allIndices instead of add? } } else { allIndices = new HashSet(_indices.length); allIndices.addAll(Arrays.asList(_indices)); - if(allIndices.contains("_all")) { - allIndices.add("*"); //TODO: maybe replace allIndices instead of add? + if (allIndices.contains("_all")) { + allIndices.add("*"); // TODO: maybe replace allIndices instead of add? } } final WildcardMatcher allIndicesMatcher = WildcardMatcher.from(allIndices); - if(addSource) { - if(sourceIsSensitive && source != null) { - if(!allIndicesMatcher.test(securityIndex)) { - if(source instanceof BytesReference) { - msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); + if (addSource) { + if (sourceIsSensitive && source != null) { + if (!allIndicesMatcher.test(securityIndex)) { + if (source instanceof BytesReference) { + msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); } else { msg.addMapToRequestBody((Map) source); } } - } else if(source != null) { - if(source instanceof BytesReference) { + } else if (source != null) { + if (source instanceof BytesReference) { msg.addTupleToRequestBody(convertSource(xContentType, (BytesReference) source)); - } else { - msg.addMapToRequestBody((Map) source); - } + } else { + msg.addMapToRequestBody((Map) source); + } } } } private static Tuple convertSource(XContentType type, BytesReference bytes) { - if(type == null) { + if (type == null) { type = XContentType.JSON; } @@ -402,11 +447,11 @@ private static Tuple convertSource(XContentType ty } private static String[] arrayOrEmpty(String[] array) { - if(array == null) { + if (array == null) { return new String[0]; } - if(array.length == 1 && array[0] == null) { + if (array.length == 1 && array[0] == null) { return new String[0]; } diff --git a/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java b/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java index bbea998b8d..494d67aba6 100644 --- a/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java +++ b/src/main/java/org/opensearch/security/auditlog/routing/AsyncStoragePool.java @@ -24,68 +24,75 @@ import org.opensearch.security.auditlog.sink.AuditLogSink; public class AsyncStoragePool { - private static final Logger log = LogManager.getLogger(AsyncStoragePool.class); - private final ExecutorService pool; - private final ThreadPoolConfig threadPoolConfig; + private static final Logger log = LogManager.getLogger(AsyncStoragePool.class); + private final ExecutorService pool; + private final ThreadPoolConfig threadPoolConfig; - public AsyncStoragePool(final ThreadPoolConfig threadPoolConfig) { - this.threadPoolConfig = threadPoolConfig; - this.pool = createExecutor(threadPoolConfig); - } + public AsyncStoragePool(final ThreadPoolConfig threadPoolConfig) { + this.threadPoolConfig = threadPoolConfig; + this.pool = createExecutor(threadPoolConfig); + } - public ThreadPoolConfig getConfig() { - return this.threadPoolConfig; - } + public ThreadPoolConfig getConfig() { + return this.threadPoolConfig; + } - public void submit(AuditMessage message, AuditLogSink sink) { - try { - pool.submit(() -> { - sink.store(message); - if (log.isTraceEnabled()) { - log.trace("stored on delegate {} asynchronously", sink.getClass().getSimpleName()); - } - }); - } catch (Exception ex) { - log.error("Could not submit audit message {} to thread pool for delegate '{}' due to '{}'", message, sink.getClass().getSimpleName(), ex.getMessage()); - if (sink.getFallbackSink() != null) { - sink.getFallbackSink().store(message); - } - } - } + public void submit(AuditMessage message, AuditLogSink sink) { + try { + pool.submit(() -> { + sink.store(message); + if (log.isTraceEnabled()) { + log.trace("stored on delegate {} asynchronously", sink.getClass().getSimpleName()); + } + }); + } catch (Exception ex) { + log.error( + "Could not submit audit message {} to thread pool for delegate '{}' due to '{}'", + message, + sink.getClass().getSimpleName(), + ex.getMessage() + ); + if (sink.getFallbackSink() != null) { + sink.getFallbackSink().store(message); + } + } + } - private static ThreadPoolExecutor createExecutor(final ThreadPoolConfig config) { - if (log.isDebugEnabled()) { - log.debug("Create new executor with threadPoolSize: {} and maxQueueLen: {}", - config.getThreadPoolSize(), - config.getThreadPoolMaxQueueLen()); - } - return new ThreadPoolExecutor( - config.getThreadPoolSize(), - config.getThreadPoolSize(), - 0L, - TimeUnit.MILLISECONDS, - new LinkedBlockingQueue<>(config.getThreadPoolMaxQueueLen())); - } + private static ThreadPoolExecutor createExecutor(final ThreadPoolConfig config) { + if (log.isDebugEnabled()) { + log.debug( + "Create new executor with threadPoolSize: {} and maxQueueLen: {}", + config.getThreadPoolSize(), + config.getThreadPoolMaxQueueLen() + ); + } + return new ThreadPoolExecutor( + config.getThreadPoolSize(), + config.getThreadPoolSize(), + 0L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(config.getThreadPoolMaxQueueLen()) + ); + } - public void close() { + public void close() { - if (pool != null) { - pool.shutdown(); // Disable new tasks from being submitted + if (pool != null) { + pool.shutdown(); // Disable new tasks from being submitted - try { - // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { - pool.shutdownNow(); // Cancel currently executing tasks - // Wait a while for tasks to respond to being cancelled - if (!pool.awaitTermination(60, TimeUnit.SECONDS)) - log.error("Pool did not terminate"); - } - } catch (InterruptedException ie) { - // (Re-)Cancel if current thread also interrupted - pool.shutdownNow(); - // Preserve interrupt status - Thread.currentThread().interrupt(); - } - } - } + try { + // Wait a while for existing tasks to terminate + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { + pool.shutdownNow(); // Cancel currently executing tasks + // Wait a while for tasks to respond to being cancelled + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) log.error("Pool did not terminate"); + } + } catch (InterruptedException ie) { + // (Re-)Cancel if current thread also interrupted + pool.shutdownNow(); + // Preserve interrupt status + Thread.currentThread().interrupt(); + } + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java b/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java index a6a6f29f23..af120daf35 100644 --- a/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java +++ b/src/main/java/org/opensearch/security/auditlog/routing/AuditMessageRouter.java @@ -106,9 +106,12 @@ public final void enableRoutes(Settings settings) { if (categorySinks != null) { return; } - Map routesConfiguration = Utils.convertJsonToxToStructuredMap(settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES)); + Map routesConfiguration = Utils.convertJsonToxToStructuredMap( + settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ROUTES) + ); EnumSet presentAuditCategory = EnumSet.noneOf(AuditCategory.class); - categorySinks = routesConfiguration.entrySet().stream() + categorySinks = routesConfiguration.entrySet() + .stream() .peek(entry -> log.trace("Setting up routes for endpoint {}, configuration is {}", entry.getKey(), entry.getValue())) .map(entry -> { String categoryName = entry.getKey(); @@ -116,9 +119,16 @@ public final void enableRoutes(Settings settings) { // first set up all configured routes. We do it this way so category names are case insensitive // and we can warn if a non-existing category has been detected. AuditCategory auditCategory = AuditCategory.valueOf(categoryName.toUpperCase()); - return Maps.immutableEntry(auditCategory, createSinksForCategory(auditCategory, (Map>)entry.getValue())); + return Maps.immutableEntry( + auditCategory, + createSinksForCategory(auditCategory, (Map>) entry.getValue()) + ); } catch (IllegalArgumentException e) { - log.error("Invalid category '{}' found in routing configuration. Must be one of: {}", categoryName, AuditCategory.values()); + log.error( + "Invalid category '{}' found in routing configuration. Must be one of: {}", + categoryName, + AuditCategory.values() + ); return null; } }) @@ -138,12 +148,7 @@ public final void enableRoutes(Settings settings) { } return false; }) - .collect( - Maps.toImmutableEnumMap( - Map.Entry::getKey, - Map.Entry::getValue - ) - ); + .collect(Maps.toImmutableEnumMap(Map.Entry::getKey, Map.Entry::getValue)); // for all non-configured categories we automatically set up the default endpoint log.warn("No endpoint configured for categories {}, using default endpoint", EnumSet.complementOf(presentAuditCategory)); diff --git a/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java b/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java index 2f2c4da5b0..a482b81c29 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/AuditLogSink.java @@ -38,7 +38,7 @@ public abstract class AuditLogSink { protected AuditLogSink(String name, Settings settings, String settingsPrefix, AuditLogSink fallbackSink) { this.name = name.toLowerCase(); - this.settings = Objects.requireNonNull(settings); + this.settings = Objects.requireNonNull(settings); this.settingsPrefix = settingsPrefix; this.fallbackSink = fallbackSink; @@ -51,34 +51,34 @@ public boolean isHandlingBackpressure() { } public String getName() { - return name; + return name; } public AuditLogSink getFallbackSink() { - return fallbackSink; + return fallbackSink; } public final void store(AuditMessage msg) { - if (!doStoreWithRetry(msg) && !fallbackSink.doStoreWithRetry(msg)) { - System.err.println(msg.toPrettyString()); - } + if (!doStoreWithRetry(msg) && !fallbackSink.doStoreWithRetry(msg)) { + System.err.println(msg.toPrettyString()); + } } private boolean doStoreWithRetry(AuditMessage msg) { - //retryCount of 0 means no retry (which is: try exactly once) - delayMs is ignored - //retryCount of 1 means: try and if this fails wait delayMs and try once again + // retryCount of 0 means no retry (which is: try exactly once) - delayMs is ignored + // retryCount of 1 means: try and if this fails wait delayMs and try once again - if(doStore(msg)) { + if (doStore(msg)) { return true; } final boolean isDebugEnabled = log.isDebugEnabled(); - for(int i=0; i DEFAULT_TLS_PROTOCOLS = Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1"}); - // config in opensearch.yml - private final String index; - private final String type; - private final HttpClient client; - private List servers; - private DateTimeFormatter indexPattern; + private static final List DEFAULT_TLS_PROTOCOLS = Arrays.asList(new String[] { "TLSv1.2", "TLSv1.1" }); + // config in opensearch.yml + private final String index; + private final String type; + private final HttpClient client; + private List servers; + private DateTimeFormatter indexPattern; static final String PKCS12 = "PKCS12"; - public ExternalOpenSearchSink(final String name, final Settings settings, final String settingPrefix, final Path configPath, AuditLogSink fallbackSink) throws Exception { - - super(name, settings, settingPrefix, fallbackSink); - Settings sinkSettings = settings.getAsSettings(settingPrefix); - servers = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS); - if (servers == null || servers.size() == 0) { - log.error("No http endpoints configured for external OpenSearch endpoint '{}', falling back to localhost.", name); - servers = Collections.singletonList("localhost:9200"); - } + public ExternalOpenSearchSink( + final String name, + final Settings settings, + final String settingPrefix, + final Path configPath, + AuditLogSink fallbackSink + ) throws Exception { + + super(name, settings, settingPrefix, fallbackSink); + Settings sinkSettings = settings.getAsSettings(settingPrefix); + servers = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_HTTP_ENDPOINTS); + if (servers == null || servers.size() == 0) { + log.error("No http endpoints configured for external OpenSearch endpoint '{}', falling back to localhost.", name); + servers = Collections.singletonList("localhost:9200"); + } - this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); + this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); - try { + try { this.indexPattern = DateTimeFormat.forPattern(index); } catch (IllegalArgumentException e) { - log.debug("Unable to parse index pattern due to {}. " - + "If you have no date pattern configured you can safely ignore this message", e.getMessage()); + log.debug( + "Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", + e.getMessage() + ); } - this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); - final boolean verifyHostnames = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, true); - final boolean enableSsl = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false); - final boolean enableSslClientAuth = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH , ConfigConstants.OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT); - final String user = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME); - final String password = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD); - - final HttpClientBuilder builder = HttpClient.builder(servers.toArray(new String[0])); - - if (enableSsl) { - - final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, null) != null - || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, null) != null; - - KeyStore effectiveTruststore; - KeyStore effectiveKeystore; - char[] effectiveKeyPassword; - String effectiveKeyAlias; - - final boolean isDebugEnabled = log.isDebugEnabled(); - - if(pem) { - X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, sinkSettings)); - - if(trustCertificates == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH); - trustCertificates = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, settings, configPath, true)); + this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); + final boolean verifyHostnames = sinkSettings.getAsBoolean( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_VERIFY_HOSTNAMES, + true + ); + final boolean enableSsl = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL, false); + final boolean enableSslClientAuth = sinkSettings.getAsBoolean( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLE_SSL_CLIENT_AUTH, + ConfigConstants.OPENDISTRO_SECURITY_AUDIT_SSL_ENABLE_SSL_CLIENT_AUTH_DEFAULT + ); + final String user = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_USERNAME); + final String password = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PASSWORD); + + final HttpClientBuilder builder = HttpClient.builder(servers.toArray(new String[0])); + + if (enableSsl) { + + final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, null) != null + || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, null) != null; + + KeyStore effectiveTruststore; + KeyStore effectiveKeystore; + char[] effectiveKeyPassword; + String effectiveKeyAlias; + + final boolean isDebugEnabled = log.isDebugEnabled(); + + if (pem) { + X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_CONTENT, sinkSettings) + ); + + if (trustCertificates == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH); + trustCertificates = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMTRUSTEDCAS_FILEPATH, + settings, + configPath, + true + ) + ); } - //for client authentication - X509Certificate[] authenticationCertificate = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, sinkSettings)); - - if(authenticationCertificate == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH); - authenticationCertificate = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, settings, configPath, enableSslClientAuth)); + // for client authentication + X509Certificate[] authenticationCertificate = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_CONTENT, sinkSettings) + ); + + if (authenticationCertificate == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH); + authenticationCertificate = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMCERT_FILEPATH, + settings, + configPath, + enableSslClientAuth + ) + ); } - PrivateKey authenticationKey = PemKeyReader.loadKeyFromStream(sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, sinkSettings)); - - if(authenticationKey == null) { - String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH); - authenticationKey = PemKeyReader.loadKeyFromFile(sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), PemKeyReader.resolve(path, ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, settings, configPath, enableSslClientAuth)); + PrivateKey authenticationKey = PemKeyReader.loadKeyFromStream( + sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_CONTENT, sinkSettings) + ); + + if (authenticationKey == null) { + String path = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH); + authenticationKey = PemKeyReader.loadKeyFromFile( + sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_PASSWORD), + PemKeyReader.resolve( + path, + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_PEMKEY_FILEPATH, + settings, + configPath, + enableSslClientAuth + ) + ); } effectiveKeyPassword = PemKeyReader.randomChars(12); effectiveKeyAlias = "al"; effectiveTruststore = PemKeyReader.toTruststore(effectiveKeyAlias, trustCertificates); - effectiveKeystore = PemKeyReader.toKeystore(effectiveKeyAlias, effectiveKeyPassword, authenticationCertificate, authenticationKey); + effectiveKeystore = PemKeyReader.toKeystore( + effectiveKeyAlias, + effectiveKeyPassword, + authenticationCertificate, + authenticationKey + ); if (isDebugEnabled) { - log.debug("Use PEM to secure communication with auditlog server (client auth is {})", authenticationKey!=null); + log.debug("Use PEM to secure communication with auditlog server (client auth is {})", authenticationKey != null); } } else { - final KeyStore trustStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, true) - , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); - - //for client authentication - final KeyStore keyStore = PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, settings, configPath, enableSslClientAuth) - , SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE)); - final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD); - effectiveKeyPassword = keyStorePassword==null||keyStorePassword.isEmpty()?null:keyStorePassword.toCharArray(); + final KeyStore trustStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, true), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); + + // for client authentication + final KeyStore keyStore = PemKeyReader.loadKeyStore( + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_FILEPATH, + settings, + configPath, + enableSslClientAuth + ), + SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting(settings, SSLConfigConstants.DEFAULT_STORE_PASSWORD), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_KEYSTORE_TYPE) + ); + final String keyStorePassword = SECURITY_SSL_TRANSPORT_KEYSTORE_PASSWORD.getSetting( + settings, + SSLConfigConstants.DEFAULT_STORE_PASSWORD + ); + effectiveKeyPassword = keyStorePassword == null || keyStorePassword.isEmpty() ? null : keyStorePassword.toCharArray(); effectiveKeyAlias = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS, null); - if(enableSslClientAuth && effectiveKeyAlias == null) { - throw new IllegalArgumentException(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS+" not given"); + if (enableSslClientAuth && effectiveKeyAlias == null) { + throw new IllegalArgumentException(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_JKS_CERT_ALIAS + " not given"); } effectiveTruststore = trustStore; effectiveKeystore = keyStore; if (isDebugEnabled) { - log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", keyStore!=null); - log.debug("keyStoreAlias: {}", effectiveKeyAlias); + log.debug("Use Trust-/Keystore to secure communication with LDAP server (client auth is {})", keyStore != null); + log.debug("keyStoreAlias: {}", effectiveKeyAlias); } } - final List enabledCipherSuites = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, null); - final List enabledProtocols = sinkSettings.getAsList(ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, DEFAULT_TLS_PROTOCOLS); + final List enabledCipherSuites = sinkSettings.getAsList( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_CIPHERS, + null + ); + final List enabledProtocols = sinkSettings.getAsList( + ConfigConstants.SECURITY_AUDIT_EXTERNAL_OPENSEARCH_ENABLED_SSL_PROTOCOLS, + DEFAULT_TLS_PROTOCOLS + ); - builder.setSupportedCipherSuites(enabledCipherSuites==null?null:enabledCipherSuites.toArray(new String[0])); + builder.setSupportedCipherSuites(enabledCipherSuites == null ? null : enabledCipherSuites.toArray(new String[0])); builder.setSupportedProtocols(enabledProtocols.toArray(new String[0])); - builder.enableSsl(effectiveTruststore, verifyHostnames); //trust all aliases + builder.enableSsl(effectiveTruststore, verifyHostnames); // trust all aliases if (enableSslClientAuth) { builder.setPkiCredentials(effectiveKeystore, effectiveKeyPassword, effectiveKeyAlias); } - } - - if (user != null && password != null) { - builder.setBasicCredentials(user, password); - } - - client = builder.build(); - } - - @Override - public void close() throws IOException { - if (client != null) { - client.close(); - } - } - - public boolean doStore(final AuditMessage msg) { - try { - boolean successful = client.index(msg.toString(), getExpandedIndexName(indexPattern, index), type, true); - if (!successful) { - log.error("Unable to send audit log {} to one of these servers: {}", msg, servers); - } - return successful; - } catch (Exception e) { - log.error("Unable to send audit log {} due to", msg, e); - return false; - } - } + } + + if (user != null && password != null) { + builder.setBasicCredentials(user, password); + } + + client = builder.build(); + } + + @Override + public void close() throws IOException { + if (client != null) { + client.close(); + } + } + + public boolean doStore(final AuditMessage msg) { + try { + boolean successful = client.index(msg.toString(), getExpandedIndexName(indexPattern, index), type, true); + if (!successful) { + log.error("Unable to send audit log {} to one of these servers: {}", msg, servers); + } + return successful; + } catch (Exception e) { + log.error("Unable to send audit log {} due to", msg, e); + return false; + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java b/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java index 9b7be332b6..4a4af8d1fe 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/InternalOpenSearchSink.java @@ -30,53 +30,68 @@ public final class InternalOpenSearchSink extends AuditLogSink { - private final Client clientProvider; - final String index; - final String type; - private DateTimeFormatter indexPattern; - private final ThreadPool threadPool; + private final Client clientProvider; + final String index; + final String type; + private DateTimeFormatter indexPattern; + private final ThreadPool threadPool; - public InternalOpenSearchSink(final String name, final Settings settings, final String settingsPrefix, final Path configPath, final Client clientProvider, ThreadPool threadPool, AuditLogSink fallbackSink) { - super(name, settings, settingsPrefix, fallbackSink); - this.clientProvider = clientProvider; - Settings sinkSettings = getSinkSettings(settingsPrefix); + public InternalOpenSearchSink( + final String name, + final Settings settings, + final String settingsPrefix, + final Path configPath, + final Client clientProvider, + ThreadPool threadPool, + AuditLogSink fallbackSink + ) { + super(name, settings, settingsPrefix, fallbackSink); + this.clientProvider = clientProvider; + Settings sinkSettings = getSinkSettings(settingsPrefix); - this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); - this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); + this.index = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd"); + this.type = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_OPENSEARCH_TYPE, null); - this.threadPool = threadPool; - try { - this.indexPattern = DateTimeFormat.forPattern(index); - } catch (IllegalArgumentException e) { - log.debug("Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", e.getMessage()); - } - } + this.threadPool = threadPool; + try { + this.indexPattern = DateTimeFormat.forPattern(index); + } catch (IllegalArgumentException e) { + log.debug( + "Unable to parse index pattern due to {}. " + "If you have no date pattern configured you can safely ignore this message", + e.getMessage() + ); + } + } - @Override - public void close() throws IOException { + @Override + public void close() throws IOException { - } + } - public boolean doStore(final AuditMessage msg) { + public boolean doStore(final AuditMessage msg) { - if (Boolean.parseBoolean((String) HeaderHelper.getSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER))) { - if (log.isTraceEnabled()) { - log.trace("audit log of audit log will not be executed"); - } - return true; - } + if (Boolean.parseBoolean( + (String) HeaderHelper.getSafeFromHeader(threadPool.getThreadContext(), ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER) + )) { + if (log.isTraceEnabled()) { + log.trace("audit log of audit log will not be executed"); + } + return true; + } - try (StoredContext ctx = threadPool.getThreadContext().stashContext()) { - try { - final IndexRequestBuilder irb = clientProvider.prepareIndex(getExpandedIndexName(indexPattern, index)).setRefreshPolicy(RefreshPolicy.IMMEDIATE).setSource(msg.getAsMap()); - threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); - irb.setTimeout(TimeValue.timeValueMinutes(1)); - irb.execute().actionGet(); - return true; - } catch (final Exception e) { - log.error("Unable to index audit log {} due to", msg, e); - return false; - } - } - } + try (StoredContext ctx = threadPool.getThreadContext().stashContext()) { + try { + final IndexRequestBuilder irb = clientProvider.prepareIndex(getExpandedIndexName(indexPattern, index)) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .setSource(msg.getAsMap()); + threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); + irb.setTimeout(TimeValue.timeValueMinutes(1)); + irb.execute().actionGet(); + return true; + } catch (final Exception e) { + log.error("Unable to index audit log {} due to", msg, e); + return false; + } + } + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java b/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java index a94a17ff03..e67ed66549 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/KafkaSink.java @@ -32,41 +32,41 @@ public class KafkaSink extends AuditLogSink { - private final String[] mandatoryProperties = new String []{"bootstrap_servers","topic_name"}; - private boolean valid = true; - private Producer producer; - private String topicName; + private final String[] mandatoryProperties = new String[] { "bootstrap_servers", "topic_name" }; + private boolean valid = true; + private Producer producer; + private String topicName; @SuppressWarnings("removal") - public KafkaSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { - super(name, settings, settingsPrefix, fallbackSink); + public KafkaSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { + super(name, settings, settingsPrefix, fallbackSink); - Settings sinkSettings = settings.getAsSettings(settingsPrefix); - checkMandatorySinkSettings(sinkSettings); + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + checkMandatorySinkSettings(sinkSettings); - if (!valid) { - log.error("Failed to configure Kafka producer, please check the logfile."); - return; - } + if (!valid) { + log.error("Failed to configure Kafka producer, please check the logfile."); + return; + } final Properties producerProps = new Properties(); - for(String key: sinkSettings.names()) { - if(!key.equals("topic_name")) { + for (String key : sinkSettings.names()) { + if (!key.equals("topic_name")) { producerProps.put(key.replace('_', '.'), sinkSettings.get(key)); } } - producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName()); - producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); - topicName = sinkSettings.get("topic_name"); + producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName()); + producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + topicName = sinkSettings.get("topic_name"); - //map path of - //ssl.keystore.location - //ssl.truststore.location - //sasl.kerberos.kinit.cmd + // map path of + // ssl.keystore.location + // ssl.truststore.location + // sasl.kerberos.kinit.cmd - final SecurityManager sm = System.getSecurityManager(); + final SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new SpecialPermission()); @@ -84,49 +84,49 @@ public KafkaProducer run() throws Exception { this.valid = false; } - } + } - @Override - protected boolean doStore(AuditMessage msg) { - if (!valid || producer == null) { - return false; - } + @Override + protected boolean doStore(AuditMessage msg) { + if (!valid || producer == null) { + return false; + } - ProducerRecord data = new ProducerRecord(topicName, msg.toJson()); - producer.send(data, new Callback() { + ProducerRecord data = new ProducerRecord(topicName, msg.toJson()); + producer.send(data, new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { - if(exception == null) { - //log trace? - } else { - log.error("Could not store message on Kafka topic {}", topicName, exception); - fallbackSink.store(msg); - } + if (exception == null) { + // log trace? + } else { + log.error("Could not store message on Kafka topic {}", topicName, exception); + fallbackSink.store(msg); + } } }); - return true; - } - - @Override - public boolean isHandlingBackpressure() { - return true; - } - - private void checkMandatorySinkSettings(Settings sinkSettings) { - for(String mandatory: mandatoryProperties) { - String value = sinkSettings.get(mandatory); - if (value == null || value.length() == 0) { - log.error("No value for {} provided in configuration, this endpoint will not work.", value); - this.valid = false; - } - } - } + return true; + } + + @Override + public boolean isHandlingBackpressure() { + return true; + } + + private void checkMandatorySinkSettings(Settings sinkSettings) { + for (String mandatory : mandatoryProperties) { + String value = sinkSettings.get(mandatory); + if (value == null || value.length() == 0) { + log.error("No value for {} provided in configuration, this endpoint will not work.", value); + this.valid = false; + } + } + } @Override public void close() throws IOException { - if(producer != null) { + if (producer != null) { valid = false; producer.close(); } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java index 8794b90f41..f01043fa21 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/Log4JSink.java @@ -27,18 +27,18 @@ public final class Log4JSink extends AuditLogSink { public Log4JSink(final String name, final Settings settings, final String settingsPrefix, AuditLogSink fallbackSink) { super(name, settings, settingsPrefix, fallbackSink); - loggerName = settings.get( settingsPrefix + ".log4j.logger_name","sgaudit"); + loggerName = settings.get(settingsPrefix + ".log4j.logger_name", "sgaudit"); auditLogger = LogManager.getLogger(loggerName); - logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level","INFO").toUpperCase()); + logLevel = Level.toLevel(settings.get(settingsPrefix + ".log4j.level", "INFO").toUpperCase()); enabled = auditLogger.isEnabled(logLevel); } public boolean isHandlingBackpressure() { - return !enabled; //no submit to thread pool if not enabled + return !enabled; // no submit to thread pool if not enabled } public boolean doStore(final AuditMessage msg) { - if(enabled) { + if (enabled) { auditLogger.log(logLevel, msg.toJson()); } return true; diff --git a/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java b/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java index 740e7a4459..2981fa995d 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/NoopSink.java @@ -27,7 +27,7 @@ public boolean isHandlingBackpressure() { @Override public boolean doStore(final AuditMessage msg) { - //do nothing + // do nothing return true; } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java b/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java index 270d106d2d..894c9162dd 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/SinkProvider.java @@ -27,147 +27,171 @@ public class SinkProvider { - protected final Logger log = LogManager.getLogger(this.getClass()); - private static final String FALLBACKSINK_NAME = "fallback"; - private static final String DEFAULTSINK_NAME = "default"; - private final Client clientProvider; - private final ThreadPool threadPool; - private final Path configPath; - private final Settings settings; - final Map allSinks = new HashMap<>(); - AuditLogSink defaultSink; - AuditLogSink fallbackSink; - - public SinkProvider(final Settings settings, final Client clientProvider, ThreadPool threadPool, final Path configPath) { - this.settings = settings; - this.clientProvider = clientProvider; - this.threadPool = threadPool; - this.configPath = configPath; - - // fall back sink, make sure we don't lose messages - String fallbackConfigPrefix = ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + FALLBACKSINK_NAME; - Settings fallbackSinkSettings = settings.getAsSettings(fallbackConfigPrefix); - if(!fallbackSinkSettings.isEmpty()) { - this.fallbackSink = createSink(FALLBACKSINK_NAME, fallbackSinkSettings.get("type"), settings, fallbackConfigPrefix+".config"); - } - - // make sure we always have a fallback to write to - if (this.fallbackSink == null) { - this.fallbackSink = new DebugSink(FALLBACKSINK_NAME, settings, null); - } - - allSinks.put(FALLBACKSINK_NAME, this.fallbackSink); - - // create default sink - defaultSink = this.createSink(DEFAULTSINK_NAME, settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT), settings, ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT); - if (defaultSink == null) { - log.error("Default endpoint could not be created, auditlog will not work properly."); - return; - } - - allSinks.put(DEFAULTSINK_NAME, defaultSink); - - // create all other sinks - Map sinkSettingsMap = Utils.convertJsonToxToStructuredMap(settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS)); - - for (Entry sinkEntry : sinkSettingsMap.entrySet()) { - String sinkName = sinkEntry.getKey(); - // do not create fallback twice - if(sinkName.equalsIgnoreCase(FALLBACKSINK_NAME)) { - continue; - } - String type = settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName).get("type"); - if (type == null) { - log.error("No type defined for endpoint {}.", sinkName); - continue; - } - AuditLogSink sink = createSink(sinkName, type, this.settings, ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName + ".config"); - if (sink == null) { - log.error("Endpoint '{}' could not be created, check log file for further information.", sinkName); - continue; - } - allSinks.put(sinkName.toLowerCase(), sink); - if (log.isDebugEnabled()) { - log.debug("sink '{}' created successfully.", sinkName); - } - } - } - - public AuditLogSink getSink(String sinkName) { - return allSinks.get(sinkName.toLowerCase()); - } - - public AuditLogSink getDefaultSink() { - return defaultSink; - } - - public void close() { - for (AuditLogSink sink : allSinks.values()) { - close(sink); - } - } - - protected void close(AuditLogSink sink) { - try { - log.info("Closing {}", sink.getClass().getSimpleName()); - sink.close(); - } catch (Exception ex) { - log.info("Could not close sink '{}' due to '{}'", sink.getClass().getSimpleName(), ex.getMessage()); - } - } - - private final AuditLogSink createSink(final String name, final String type, final Settings settings, final String settingsPrefix) { - AuditLogSink sink = null; - if (type != null) { - switch (type.toLowerCase()) { - case "internal_opensearch": - sink = new InternalOpenSearchSink(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); - break; - case "external_opensearch": - try { - sink = new ExternalOpenSearchSink(name, settings, settingsPrefix, configPath, fallbackSink); - } catch (Exception e) { - log.error("Audit logging unavailable: Unable to setup HttpOpenSearchAuditLog due to", e); - } - break; - case "webhook": - try { - sink = new WebhookSink(name, settings, settingsPrefix, configPath, fallbackSink); - } catch (Exception e1) { - log.error("Audit logging unavailable: Unable to setup WebhookAuditLog due to", e1); - } - break; - case "debug": - sink = new DebugSink(name, settings, fallbackSink); - break; - case "noop": - sink = new NoopSink(name, settings, fallbackSink); - break; - case "log4j": - sink = new Log4JSink(name, settings, settingsPrefix, fallbackSink); - break; - case "kafka": - sink = new KafkaSink(name, settings, settingsPrefix, fallbackSink); - break; - default: - try { - Class delegateClass = Class.forName(type); - if (AuditLogSink.class.isAssignableFrom(delegateClass)) { - try { - sink = (AuditLogSink) delegateClass.getConstructor(String.class, Settings.class, String.class, Path.class, Client.class, ThreadPool.class, AuditLogSink.class).newInstance(name, settings, settingsPrefix, configPath, - clientProvider, threadPool, fallbackSink); - } catch (Throwable e) { - sink = (AuditLogSink) delegateClass.getConstructor(String.class, Settings.class, String.class, AuditLogSink.class).newInstance(name, settings, settingsPrefix, fallbackSink); - } - } else { - log.error("Audit logging unavailable: '{}' is not a subclass of {}", type, AuditLogSink.class.getSimpleName()); - } - } catch (Throwable e) { // we need really catch a Throwable here! - log.error("Audit logging unavailable: Cannot instantiate object of class {} due to ", type, e); - } - } - } - return sink; - } + protected final Logger log = LogManager.getLogger(this.getClass()); + private static final String FALLBACKSINK_NAME = "fallback"; + private static final String DEFAULTSINK_NAME = "default"; + private final Client clientProvider; + private final ThreadPool threadPool; + private final Path configPath; + private final Settings settings; + final Map allSinks = new HashMap<>(); + AuditLogSink defaultSink; + AuditLogSink fallbackSink; + + public SinkProvider(final Settings settings, final Client clientProvider, ThreadPool threadPool, final Path configPath) { + this.settings = settings; + this.clientProvider = clientProvider; + this.threadPool = threadPool; + this.configPath = configPath; + + // fall back sink, make sure we don't lose messages + String fallbackConfigPrefix = ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + FALLBACKSINK_NAME; + Settings fallbackSinkSettings = settings.getAsSettings(fallbackConfigPrefix); + if (!fallbackSinkSettings.isEmpty()) { + this.fallbackSink = createSink(FALLBACKSINK_NAME, fallbackSinkSettings.get("type"), settings, fallbackConfigPrefix + ".config"); + } + + // make sure we always have a fallback to write to + if (this.fallbackSink == null) { + this.fallbackSink = new DebugSink(FALLBACKSINK_NAME, settings, null); + } + + allSinks.put(FALLBACKSINK_NAME, this.fallbackSink); + + // create default sink + defaultSink = this.createSink( + DEFAULTSINK_NAME, + settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT), + settings, + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT + ); + if (defaultSink == null) { + log.error("Default endpoint could not be created, auditlog will not work properly."); + return; + } + + allSinks.put(DEFAULTSINK_NAME, defaultSink); + + // create all other sinks + Map sinkSettingsMap = Utils.convertJsonToxToStructuredMap( + settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS) + ); + + for (Entry sinkEntry : sinkSettingsMap.entrySet()) { + String sinkName = sinkEntry.getKey(); + // do not create fallback twice + if (sinkName.equalsIgnoreCase(FALLBACKSINK_NAME)) { + continue; + } + String type = settings.getAsSettings(ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName).get("type"); + if (type == null) { + log.error("No type defined for endpoint {}.", sinkName); + continue; + } + AuditLogSink sink = createSink( + sinkName, + type, + this.settings, + ConfigConstants.SECURITY_AUDIT_CONFIG_ENDPOINTS + "." + sinkName + ".config" + ); + if (sink == null) { + log.error("Endpoint '{}' could not be created, check log file for further information.", sinkName); + continue; + } + allSinks.put(sinkName.toLowerCase(), sink); + if (log.isDebugEnabled()) { + log.debug("sink '{}' created successfully.", sinkName); + } + } + } + + public AuditLogSink getSink(String sinkName) { + return allSinks.get(sinkName.toLowerCase()); + } + + public AuditLogSink getDefaultSink() { + return defaultSink; + } + + public void close() { + for (AuditLogSink sink : allSinks.values()) { + close(sink); + } + } + + protected void close(AuditLogSink sink) { + try { + log.info("Closing {}", sink.getClass().getSimpleName()); + sink.close(); + } catch (Exception ex) { + log.info("Could not close sink '{}' due to '{}'", sink.getClass().getSimpleName(), ex.getMessage()); + } + } + + private final AuditLogSink createSink(final String name, final String type, final Settings settings, final String settingsPrefix) { + AuditLogSink sink = null; + if (type != null) { + switch (type.toLowerCase()) { + case "internal_opensearch": + sink = new InternalOpenSearchSink(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); + break; + case "external_opensearch": + try { + sink = new ExternalOpenSearchSink(name, settings, settingsPrefix, configPath, fallbackSink); + } catch (Exception e) { + log.error("Audit logging unavailable: Unable to setup HttpOpenSearchAuditLog due to", e); + } + break; + case "webhook": + try { + sink = new WebhookSink(name, settings, settingsPrefix, configPath, fallbackSink); + } catch (Exception e1) { + log.error("Audit logging unavailable: Unable to setup WebhookAuditLog due to", e1); + } + break; + case "debug": + sink = new DebugSink(name, settings, fallbackSink); + break; + case "noop": + sink = new NoopSink(name, settings, fallbackSink); + break; + case "log4j": + sink = new Log4JSink(name, settings, settingsPrefix, fallbackSink); + break; + case "kafka": + sink = new KafkaSink(name, settings, settingsPrefix, fallbackSink); + break; + default: + try { + Class delegateClass = Class.forName(type); + if (AuditLogSink.class.isAssignableFrom(delegateClass)) { + try { + sink = (AuditLogSink) delegateClass.getConstructor( + String.class, + Settings.class, + String.class, + Path.class, + Client.class, + ThreadPool.class, + AuditLogSink.class + ).newInstance(name, settings, settingsPrefix, configPath, clientProvider, threadPool, fallbackSink); + } catch (Throwable e) { + sink = (AuditLogSink) delegateClass.getConstructor( + String.class, + Settings.class, + String.class, + AuditLogSink.class + ).newInstance(name, settings, settingsPrefix, fallbackSink); + } + } else { + log.error("Audit logging unavailable: '{}' is not a subclass of {}", type, AuditLogSink.class.getSimpleName()); + } + } catch (Throwable e) { // we need really catch a Throwable here! + log.error("Audit logging unavailable: Cannot instantiate object of class {} due to ", type, e); + } + } + } + return sink; + } } diff --git a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java index 083df01bbd..78780fb8e1 100644 --- a/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java +++ b/src/main/java/org/opensearch/security/auditlog/sink/WebhookSink.java @@ -54,301 +54,320 @@ public class WebhookSink extends AuditLogSink { - /* HttpClient is thread safe */ - private final CloseableHttpClient httpClient; - - String webhookUrl = null; - WebhookFormat webhookFormat = null; - final boolean verifySSL; - final KeyStore effectiveTruststore; - - public WebhookSink(final String name, final Settings settings, final String settingsPrefix, final Path configPath, AuditLogSink fallbackSink) throws Exception { - super(name, settings, settingsPrefix, fallbackSink); - - Settings sinkSettings = settings.getAsSettings(settingsPrefix); - - this.effectiveTruststore = getEffectiveKeyStore(configPath); - - final String webhookUrl = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL); - final String format = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT); - - verifySSL = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true); - httpClient = getHttpClient(); - - if(httpClient == null) { - log.error("Could not create HttpClient, audit log not available."); - return; - } - - if (Strings.isEmpty(webhookUrl)) { - log.error("plugins.security.audit.config.webhook.url not provided, webhook audit log will not work"); - return; - } else { - try { - // Sanity - check URL validity - new URL(webhookUrl); - this.webhookUrl = webhookUrl; - } catch (MalformedURLException ex) { - log.error("URL {} is invalid, webhook audit log will not work.", webhookUrl, ex); - } - } - - if (Strings.isEmpty(format)) { - log.warn("plugins.security.audit.config.webhook.format not provided, falling back to 'text'"); - webhookFormat = WebhookFormat.TEXT; - } else { - try { - webhookFormat = WebhookFormat.valueOf(format.toUpperCase()); - } catch (Exception ex) { - log.error("Could not find WebhookFormat for type {}, falling back to 'text'", format, ex); - webhookFormat = WebhookFormat.TEXT; - } - } - } - - @Override + /* HttpClient is thread safe */ + private final CloseableHttpClient httpClient; + + String webhookUrl = null; + WebhookFormat webhookFormat = null; + final boolean verifySSL; + final KeyStore effectiveTruststore; + + public WebhookSink( + final String name, + final Settings settings, + final String settingsPrefix, + final Path configPath, + AuditLogSink fallbackSink + ) throws Exception { + super(name, settings, settingsPrefix, fallbackSink); + + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + + this.effectiveTruststore = getEffectiveKeyStore(configPath); + + final String webhookUrl = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_URL); + final String format = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_FORMAT); + + verifySSL = sinkSettings.getAsBoolean(ConfigConstants.SECURITY_AUDIT_WEBHOOK_SSL_VERIFY, true); + httpClient = getHttpClient(); + + if (httpClient == null) { + log.error("Could not create HttpClient, audit log not available."); + return; + } + + if (Strings.isEmpty(webhookUrl)) { + log.error("plugins.security.audit.config.webhook.url not provided, webhook audit log will not work"); + return; + } else { + try { + // Sanity - check URL validity + new URL(webhookUrl); + this.webhookUrl = webhookUrl; + } catch (MalformedURLException ex) { + log.error("URL {} is invalid, webhook audit log will not work.", webhookUrl, ex); + } + } + + if (Strings.isEmpty(format)) { + log.warn("plugins.security.audit.config.webhook.format not provided, falling back to 'text'"); + webhookFormat = WebhookFormat.TEXT; + } else { + try { + webhookFormat = WebhookFormat.valueOf(format.toUpperCase()); + } catch (Exception ex) { + log.error("Could not find WebhookFormat for type {}, falling back to 'text'", format, ex); + webhookFormat = WebhookFormat.TEXT; + } + } + } + + @Override @SuppressWarnings("removal") - public boolean doStore(AuditMessage msg) { - if (Strings.isEmpty(webhookUrl)) { - log.debug("Webhook URL is null"); - return false; - } - if (msg == null) { - log.debug("Message is null"); - return true; - } - - return AccessController.doPrivileged(new PrivilegedAction() { - - @Override - public Boolean run() { - boolean success = false; - try { - switch (webhookFormat.method) { - case POST: - success = post(msg); - break; - case GET: - success =get(msg); - break; - default: - log.error("Http Method '{}' defined in WebhookFormat '{}' not implemented yet", webhookFormat.method.name(), - webhookFormat.name()); - } - // log something in case endpoint is not reachable or did not return 200 - if (!success) { - log.error(msg.toString()); - } - return success; - } catch(Throwable t) { - log.error("Uncaught exception while trying to log message.", t); - log.error(msg.toString()); - return false; - } - } - }); - } + public boolean doStore(AuditMessage msg) { + if (Strings.isEmpty(webhookUrl)) { + log.debug("Webhook URL is null"); + return false; + } + if (msg == null) { + log.debug("Message is null"); + return true; + } + + return AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public Boolean run() { + boolean success = false; + try { + switch (webhookFormat.method) { + case POST: + success = post(msg); + break; + case GET: + success = get(msg); + break; + default: + log.error( + "Http Method '{}' defined in WebhookFormat '{}' not implemented yet", + webhookFormat.method.name(), + webhookFormat.name() + ); + } + // log something in case endpoint is not reachable or did not return 200 + if (!success) { + log.error(msg.toString()); + } + return success; + } catch (Throwable t) { + log.error("Uncaught exception while trying to log message.", t); + log.error(msg.toString()); + return false; + } + } + }); + } @Override public void close() throws IOException { - if(httpClient != null) { - httpClient.close(); + if (httpClient != null) { + httpClient.close(); } } + /** + * Transforms an {@link AuditMessage} to JSON. By default, all fields are + * included in the JSON string. This method can be overridden by subclasses + * if a specific JSON format is needed. + * + * @param msg the AuditMessage to transform + * @return the JSON string + */ + protected String formatJson(final AuditMessage msg) { + return msg.toJson(); + } - /** - * Transforms an {@link AuditMessage} to JSON. By default, all fields are - * included in the JSON string. This method can be overridden by subclasses - * if a specific JSON format is needed. - * - * @param msg the AuditMessage to transform - * @return the JSON string - */ - protected String formatJson(final AuditMessage msg) { - return msg.toJson(); - } - - /** - * Transforms an {@link AuditMessage} to plain text. This method can be overridden - * by subclasses if a specific text format is needed. - * - * @param msg the AuditMessage to transform - * @return the text string - */ - protected String formatText(AuditMessage msg) { - return msg.toText(); - } - - /** - * Transforms an {@link AuditMessage} to Slack format. - * The default implementation returns - *

-	 * {
-	 *   "text": ""
-	 * }
-	 * 
- *

- * Can be overridden by subclasses if a more specific format is needed. - * - * @param msg the AuditMessage to transform - * @return the Slack formatted JSON string - */ - protected String formatSlack(AuditMessage msg) { - return "{\"text\": \"" + msg.toText() + "\"}"; - } - - /** - * Transforms an {@link AuditMessage} to a query parameter String. - * Used by {@link WebhookFormat#URL_PARAMETER_GET} and - * Used by {@link WebhookFormat#URL_PARAMETER_POST}. Can be overridden by - * subclasses if a specific format is needed. - * - * @param msg the AuditMessage to transform - * @return the query parameter string - */ - protected String formatUrlParameters(AuditMessage msg) { - return msg.toUrlParameters(); - } - - boolean get(AuditMessage msg) { - switch (webhookFormat) { - case URL_PARAMETER_GET: - return doGet(webhookUrl + formatUrlParameters(msg)); - default: - log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); - return false; - } - } - - protected boolean doGet(String url) { - HttpGet httpGet = new HttpGet(url); - CloseableHttpResponse serverResponse = null; - try { - serverResponse = httpClient.execute(httpGet); - int responseCode = serverResponse.getCode(); - if (responseCode != HttpStatus.SC_OK) { - log.error("Cannot GET to webhook URL '{}', server returned status {}", webhookUrl, responseCode); - return false; - } - return true; - } catch (Throwable e) { - log.error("Cannot GET to webhook URL '{}'", webhookUrl, e); - return false; - } finally { - try { - if (serverResponse != null) { - serverResponse.close(); - } - } catch (IOException e) { - log.error("Cannot close server response", e); - } - } - } - - boolean post(AuditMessage msg) { - - String payload; - String url = webhookUrl; - - switch (webhookFormat) { - case JSON: - payload = formatJson(msg); - break; - case TEXT: - payload = formatText(msg); - break; - case SLACK: - payload = "{\"text\": \"" + msg.toText() + "\"}"; - break; - case URL_PARAMETER_POST: - payload = ""; - url = webhookUrl + formatUrlParameters(msg); - break; - default: - log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); - return false; - } - - return doPost(url, payload); - - } - - protected boolean doPost(String url, String payload) { - - HttpPost postRequest = new HttpPost(url); - - StringEntity input = new StringEntity(payload, webhookFormat.contentType.withCharset(StandardCharsets.UTF_8)); - postRequest.setEntity(input); - - CloseableHttpResponse serverResponse = null; - try { - serverResponse = httpClient.execute(postRequest); - int responseCode = serverResponse.getCode(); - if (responseCode != HttpStatus.SC_OK) { - log.error("Cannot POST to webhook URL '{}', server returned status {}", webhookUrl, responseCode); - return false; - } - return true; - } catch (Throwable e) { - log.error("Cannot POST to webhook URL '{}' due to '{}'", webhookUrl, e.getMessage(), e); - return false; - } finally { - try { - if (serverResponse != null) { - serverResponse.close(); - } - } catch (IOException e) { - log.error("Cannot close server response", e); - } - } - } + /** + * Transforms an {@link AuditMessage} to plain text. This method can be overridden + * by subclasses if a specific text format is needed. + * + * @param msg the AuditMessage to transform + * @return the text string + */ + protected String formatText(AuditMessage msg) { + return msg.toText(); + } - @SuppressWarnings("removal") - private KeyStore getEffectiveKeyStore(final Path configPath) { + /** + * Transforms an {@link AuditMessage} to Slack format. + * The default implementation returns + *

+     * {
+     *   "text": ""
+     * }
+     * 
+ *

+ * Can be overridden by subclasses if a more specific format is needed. + * + * @param msg the AuditMessage to transform + * @return the Slack formatted JSON string + */ + protected String formatSlack(AuditMessage msg) { + return "{\"text\": \"" + msg.toText() + "\"}"; + } - return AccessController.doPrivileged(new PrivilegedAction() { + /** + * Transforms an {@link AuditMessage} to a query parameter String. + * Used by {@link WebhookFormat#URL_PARAMETER_GET} and + * Used by {@link WebhookFormat#URL_PARAMETER_POST}. Can be overridden by + * subclasses if a specific format is needed. + * + * @param msg the AuditMessage to transform + * @return the query parameter string + */ + protected String formatUrlParameters(AuditMessage msg) { + return msg.toUrlParameters(); + } - @Override - public KeyStore run() { - try { - Settings sinkSettings = settings.getAsSettings(settingsPrefix); + boolean get(AuditMessage msg) { + switch (webhookFormat) { + case URL_PARAMETER_GET: + return doGet(webhookUrl + formatUrlParameters(msg)); + default: + log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); + return false; + } + } - final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, null) != null - || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, null) != null; + protected boolean doGet(String url) { + HttpGet httpGet = new HttpGet(url); + CloseableHttpResponse serverResponse = null; + try { + serverResponse = httpClient.execute(httpGet); + int responseCode = serverResponse.getCode(); + if (responseCode != HttpStatus.SC_OK) { + log.error("Cannot GET to webhook URL '{}', server returned status {}", webhookUrl, responseCode); + return false; + } + return true; + } catch (Throwable e) { + log.error("Cannot GET to webhook URL '{}'", webhookUrl, e); + return false; + } finally { + try { + if (serverResponse != null) { + serverResponse.close(); + } + } catch (IOException e) { + log.error("Cannot close server response", e); + } + } + } - if(pem) { - X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream(PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, sinkSettings)); + boolean post(AuditMessage msg) { + + String payload; + String url = webhookUrl; + + switch (webhookFormat) { + case JSON: + payload = formatJson(msg); + break; + case TEXT: + payload = formatText(msg); + break; + case SLACK: + payload = "{\"text\": \"" + msg.toText() + "\"}"; + break; + case URL_PARAMETER_POST: + payload = ""; + url = webhookUrl + formatUrlParameters(msg); + break; + default: + log.error("WebhookFormat '{}' not implemented yet", webhookFormat.name()); + return false; + } + + return doPost(url, payload); - if(trustCertificates == null) { - String fullPath = settingsPrefix + "." + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH; - trustCertificates = PemKeyReader.loadCertificatesFromFile(PemKeyReader.resolve(fullPath, settings, configPath, false)); - } + } - return PemKeyReader.toTruststore("alw", trustCertificates); + protected boolean doPost(String url, String payload) { + HttpPost postRequest = new HttpPost(url); - } else { - return PemKeyReader.loadKeyStore(PemKeyReader.resolve(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, settings, configPath, false) - , SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings) - , settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE)); - } - } catch(Exception ex) { - log.error("Could not load key material. Make sure your certificates are located relative to the config directory", ex); - return null; - } - } - }); - } + StringEntity input = new StringEntity(payload, webhookFormat.contentType.withCharset(StandardCharsets.UTF_8)); + postRequest.setEntity(input); - CloseableHttpClient getHttpClient() { + CloseableHttpResponse serverResponse = null; + try { + serverResponse = httpClient.execute(postRequest); + int responseCode = serverResponse.getCode(); + if (responseCode != HttpStatus.SC_OK) { + log.error("Cannot POST to webhook URL '{}', server returned status {}", webhookUrl, responseCode); + return false; + } + return true; + } catch (Throwable e) { + log.error("Cannot POST to webhook URL '{}' due to '{}'", webhookUrl, e.getMessage(), e); + return false; + } finally { + try { + if (serverResponse != null) { + serverResponse.close(); + } + } catch (IOException e) { + log.error("Cannot close server response", e); + } + } + } + + @SuppressWarnings("removal") + private KeyStore getEffectiveKeyStore(final Path configPath) { + + return AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public KeyStore run() { + try { + Settings sinkSettings = settings.getAsSettings(settingsPrefix); + + final boolean pem = sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH, null) != null + || sinkSettings.get(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, null) != null; + + if (pem) { + X509Certificate[] trustCertificates = PemKeyReader.loadCertificatesFromStream( + PemKeyReader.resolveStream(ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_CONTENT, sinkSettings) + ); + + if (trustCertificates == null) { + String fullPath = settingsPrefix + "." + ConfigConstants.SECURITY_AUDIT_WEBHOOK_PEMTRUSTEDCAS_FILEPATH; + trustCertificates = PemKeyReader.loadCertificatesFromFile( + PemKeyReader.resolve(fullPath, settings, configPath, false) + ); + } + + return PemKeyReader.toTruststore("alw", trustCertificates); + + } else { + return PemKeyReader.loadKeyStore( + PemKeyReader.resolve( + SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, + settings, + configPath, + false + ), + SECURITY_SSL_TRANSPORT_TRUSTSTORE_PASSWORD.getSetting(settings), + settings.get(SSLConfigConstants.SECURITY_SSL_TRANSPORT_TRUSTSTORE_TYPE) + ); + } + } catch (Exception ex) { + log.error("Could not load key material. Make sure your certificates are located relative to the config directory", ex); + return null; + } + } + }); + } + + CloseableHttpClient getHttpClient() { // TODO: set a timeout until we have a proper way to deal with back pressure int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout, TimeUnit.SECONDS) - .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) + .build(); final TrustStrategy trustAllStrategy = new TrustStrategy() { @Override @@ -357,76 +376,74 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { } }; - try { - - HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); - if(!verifySSL) { - SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, - NoopHostnameVerifier.INSTANCE); - - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - return hcb.build(); - } - - if(effectiveTruststore == null) { - return HttpClients.custom() - .setDefaultRequestConfig(config) - .build(); - } - SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(effectiveTruststore, null).build(); - final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, - new DefaultHostnameVerifier()); - - final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() - .setSSLSocketFactory(sslsf) - .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) - .build(); - hcb.setConnectionManager(cm); - - return hcb.build(); - - - } catch(Exception ex) { - log.error("Could not create HTTPClient due to {}, audit log not available.", ex.getMessage(), ex); - return null; - } - } - - public static enum WebhookFormat { - URL_PARAMETER_GET(HttpMethod.GET, ContentType.TEXT_PLAIN), - URL_PARAMETER_POST(HttpMethod.POST, ContentType.TEXT_PLAIN), - TEXT(HttpMethod.POST, ContentType.TEXT_PLAIN), - JSON(HttpMethod.POST, ContentType.APPLICATION_JSON), - SLACK(HttpMethod.POST, ContentType.APPLICATION_JSON); - - private HttpMethod method; - private ContentType contentType; - - private WebhookFormat(HttpMethod method, ContentType contentType) { - this.method = method; - this.contentType = contentType; - } - - HttpMethod getMethod() { - return method; - } - - ContentType getContentType() { - return contentType; - } - - - } - - private static enum HttpMethod { - GET, - POST; - } + try { + + HttpClientBuilder hcb = HttpClients.custom().setDefaultRequestConfig(config); + if (!verifySSL) { + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(trustAllStrategy).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( + sslContext, + null, + null, + NoopHostnameVerifier.INSTANCE + ); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + return hcb.build(); + } + + if (effectiveTruststore == null) { + return HttpClients.custom().setDefaultRequestConfig(config).build(); + } + SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(effectiveTruststore, null).build(); + final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, null, null, new DefaultHostnameVerifier()); + + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslsf) + .setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeout, TimeUnit.SECONDS).build()) + .build(); + hcb.setConnectionManager(cm); + + return hcb.build(); + } catch (Exception ex) { + log.error("Could not create HTTPClient due to {}, audit log not available.", ex.getMessage(), ex); + return null; + } + } + + public static enum WebhookFormat { + URL_PARAMETER_GET(HttpMethod.GET, ContentType.TEXT_PLAIN), + URL_PARAMETER_POST(HttpMethod.POST, ContentType.TEXT_PLAIN), + TEXT(HttpMethod.POST, ContentType.TEXT_PLAIN), + JSON(HttpMethod.POST, ContentType.APPLICATION_JSON), + SLACK(HttpMethod.POST, ContentType.APPLICATION_JSON); + + private HttpMethod method; + private ContentType contentType; + + private WebhookFormat(HttpMethod method, ContentType contentType) { + this.method = method; + this.contentType = contentType; + } + + HttpMethod getMethod() { + return method; + } + + ContentType getContentType() { + return contentType; + } + + } + + private static enum HttpMethod { + GET, + POST; + } } diff --git a/src/main/java/org/opensearch/security/auth/AuthDomain.java b/src/main/java/org/opensearch/security/auth/AuthDomain.java index effa29adf3..decad5f068 100644 --- a/src/main/java/org/opensearch/security/auth/AuthDomain.java +++ b/src/main/java/org/opensearch/security/auth/AuthDomain.java @@ -61,8 +61,15 @@ public int getOrder() { @Override public String toString() { - return "AuthDomain [backend=" + backend + ", httpAuthenticator=" + httpAuthenticator + ", order=" + order + ", challenge=" - + challenge + "]"; + return "AuthDomain [backend=" + + backend + + ", httpAuthenticator=" + + httpAuthenticator + + ", order=" + + order + + ", challenge=" + + challenge + + "]"; } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 51e93978bd..0a287d19f5 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -26,7 +26,6 @@ package org.opensearch.security.auth; - import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Collection; @@ -85,44 +84,55 @@ public class BackendRegistry { private final XFFResolver xffResolver; private volatile boolean anonymousAuthEnabled = false; private final Settings opensearchSettings; - //private final InternalAuthenticationBackend iab; + // private final InternalAuthenticationBackend iab; private final AuditLog auditLog; private final ThreadPool threadPool; private final UserInjector userInjector; private final int ttlInMin; - private Cache userCache; //rest standard - private Cache restImpersonationCache; //used for rest impersonation + private Cache userCache; // rest standard + private Cache restImpersonationCache; // used for rest impersonation private Cache> restRoleCache; // private void createCaches() { - userCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey().getUsername(), notification.getCause()); - } - }).build(); + userCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey().getUsername(), notification.getCause()); + } + }) + .build(); - restImpersonationCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); - } - }).build(); + restImpersonationCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); + } + }) + .build(); - restRoleCache = CacheBuilder.newBuilder().expireAfterWrite(ttlInMin, TimeUnit.MINUTES) - .removalListener(new RemovalListener>() { - @Override - public void onRemoval(RemovalNotification> notification) { - log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); - } - }).build(); + restRoleCache = CacheBuilder.newBuilder() + .expireAfterWrite(ttlInMin, TimeUnit.MINUTES) + .removalListener(new RemovalListener>() { + @Override + public void onRemoval(RemovalNotification> notification) { + log.debug("Clear user cache for {} due to {}", notification.getKey(), notification.getCause()); + } + }) + .build(); } - public BackendRegistry(final Settings settings, final AdminDNs adminDns, - final XFFResolver xffResolver, final AuditLog auditLog, final ThreadPool threadPool) { + public BackendRegistry( + final Settings settings, + final AdminDNs adminDns, + final XFFResolver xffResolver, + final AuditLog auditLog, + final ThreadPool threadPool + ) { this.adminDns = adminDns; this.opensearchSettings = settings; this.xffResolver = xffResolver; @@ -130,11 +140,10 @@ public BackendRegistry(final Settings settings, final AdminDNs adminDns, this.threadPool = threadPool; this.userInjector = new UserInjector(settings, threadPool, auditLog, xffResolver); - this.ttlInMin = settings.getAsInt(ConfigConstants.SECURITY_CACHE_TTL_MINUTES, 60); // This is going to be defined in the opensearch.yml, so it's best suited to be initialized once. - this.injectedUserEnabled = opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED,false); + this.injectedUserEnabled = opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false); createCaches(); } @@ -153,8 +162,8 @@ public void invalidateCache() { public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { invalidateCache(); - anonymousAuthEnabled = dcm.isAnonymousAuthenticationEnabled()//config.dynamic.http.anonymous_auth_enabled - && !opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false); + anonymousAuthEnabled = dcm.isAnonymousAuthenticationEnabled()// config.dynamic.http.anonymous_auth_enabled + && !opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_DISABLE_ANONYMOUS_AUTHENTICATION, false); restAuthDomains = Collections.unmodifiableSortedSet(dcm.getRestAuthDomains()); restAuthorizers = Collections.unmodifiableSet(dcm.getRestAuthorizers()); @@ -164,8 +173,8 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { ipClientBlockRegistries = dcm.getIpClientBlockRegistries(); authBackendClientBlockRegistries = dcm.getAuthBackendClientBlockRegistries(); - //OpenSearch Security no default authc - initialized = !restAuthDomains.isEmpty() || anonymousAuthEnabled || injectedUserEnabled; + // OpenSearch Security no default authc + initialized = !restAuthDomains.isEmpty() || anonymousAuthEnabled || injectedUserEnabled; } /** @@ -177,7 +186,8 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { */ public boolean authenticate(final RestRequest request, final RestChannel channel, final ThreadContext threadContext) { final boolean isDebugEnabled = log.isDebugEnabled(); - if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress())) { + if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress + && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress())) { if (isDebugEnabled) { log.debug("Rejecting REST request because of blocked address: {}", request.getHttpChannel().getRemoteAddress()); } @@ -189,8 +199,8 @@ public boolean authenticate(final RestRequest request, final RestChannel channel final String sslPrincipal = (String) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL); - if(adminDns.isAdminDN(sslPrincipal)) { - //PKI authenticated REST call + if (adminDns.isAdminDN(sslPrincipal)) { + // PKI authenticated REST call threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; @@ -203,8 +213,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel if (!isInitialized()) { log.error("Not yet initialized (you may need to run securityadmin)"); - channel.sendResponse(new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, - "OpenSearch Security not initialized.")); + channel.sendResponse(new BytesRestResponse(RestStatus.SERVICE_UNAVAILABLE, "OpenSearch Security not initialized.")); return false; } @@ -212,7 +221,7 @@ public boolean authenticate(final RestRequest request, final RestChannel channel final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("Rest authentication request from {} [original: {}]", remoteAddress, request.getHttpChannel().getRemoteAddress()); - } + } threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, remoteAddress); @@ -224,15 +233,20 @@ public boolean authenticate(final RestRequest request, final RestChannel channel HTTPAuthenticator firstChallengingHttpAuthenticator = null; - //loop over all http/rest auth domains - for (final AuthDomain authDomain: restAuthDomains) { + // loop over all http/rest auth domains + for (final AuthDomain authDomain : restAuthDomains) { if (isDebugEnabled) { - log.debug("Check authdomain for rest {}/{} or {} in total", authDomain.getBackend().getType(), authDomain.getOrder(), restAuthDomains.size()); + log.debug( + "Check authdomain for rest {}/{} or {} in total", + authDomain.getBackend().getType(), + authDomain.getOrder(), + restAuthDomains.size() + ); } final HTTPAuthenticator httpAuthenticator = authDomain.getHttpAuthenticator(); - if(authDomain.isChallenge() && firstChallengingHttpAuthenticator == null) { + if (authDomain.isChallenge() && firstChallengingHttpAuthenticator == null) { firstChallengingHttpAuthenticator = httpAuthenticator; } @@ -260,17 +274,17 @@ public boolean authenticate(final RestRequest request, final RestChannel channel authCredenetials = ac; if (ac == null) { - //no credentials found in request - if(anonymousAuthEnabled) { + // no credentials found in request + if (anonymousAuthEnabled) { continue; } - if(authDomain.isChallenge() && httpAuthenticator.reRequestAuthentication(channel, null)) { + if (authDomain.isChallenge() && httpAuthenticator.reRequestAuthentication(channel, null)) { auditLog.logFailedLogin("", false, null, request); log.warn("No 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); return false; } else { - //no reRequest possible + // no reRequest possible if (isTraceEnabled) { log.trace("No 'Authorization' header, send 403"); } @@ -279,39 +293,54 @@ public boolean authenticate(final RestRequest request, final RestChannel channel } else { org.apache.logging.log4j.ThreadContext.put("user", ac.getUsername()); if (!ac.isComplete()) { - //credentials found in request but we need another client challenge - if(httpAuthenticator.reRequestAuthentication(channel, ac)) { - //auditLog.logFailedLogin(ac.getUsername()+" ", request); --noauditlog + // credentials found in request but we need another client challenge + if (httpAuthenticator.reRequestAuthentication(channel, ac)) { + // auditLog.logFailedLogin(ac.getUsername()+" ", request); --noauditlog return false; } else { - //no reRequest possible + // no reRequest possible continue; } } } - //http completed + // http completed authenticatedUser = authcz(userCache, restRoleCache, ac, authDomain.getBackend(), restAuthorizers); - if(authenticatedUser == null) { + if (authenticatedUser == null) { if (isDebugEnabled) { - log.debug("Cannot authenticate rest user {} (or add roles) with authdomain {}/{} of {}, try next", ac.getUsername(), authDomain.getBackend().getType(), authDomain.getOrder(), restAuthDomains); + log.debug( + "Cannot authenticate rest user {} (or add roles) with authdomain {}/{} of {}, try next", + ac.getUsername(), + authDomain.getBackend().getType(), + authDomain.getOrder(), + restAuthDomains + ); } - for (AuthFailureListener authFailureListener : this.authBackendFailureListeners.get(authDomain.getBackend().getClass().getName())) { + for (AuthFailureListener authFailureListener : this.authBackendFailureListeners.get( + authDomain.getBackend().getClass().getName() + )) { authFailureListener.onAuthFailure( - (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() - : null, - ac, request); + (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) + ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + : null, + ac, + request + ); } continue; } - if(adminDns.isAdmin(authenticatedUser)) { + if (adminDns.isAdmin(authenticatedUser)) { log.error("Cannot authenticate rest user because admin user is not permitted to login via HTTP"); auditLog.logFailedLogin(authenticatedUser.getName(), true, null, request); - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, - "Cannot authenticate user because admin user is not permitted to login via HTTP")); + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + "Cannot authenticate user because admin user is not permitted to login via HTTP" + ) + ); return false; } @@ -325,19 +354,26 @@ public boolean authenticate(final RestRequest request, final RestChannel channel authenticatedUser.setRequestedTenant(tenant); authenticated = true; break; - }//end looping auth domains + }// end looping auth domains - if(authenticated) { + if (authenticated) { final User impersonatedUser = impersonate(request, authenticatedUser); - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser==null?authenticatedUser:impersonatedUser); - auditLog.logSucceededLogin((impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, - authenticatedUser.getName(), request); + threadContext.putTransient( + ConfigConstants.OPENDISTRO_SECURITY_USER, + impersonatedUser == null ? authenticatedUser : impersonatedUser + ); + auditLog.logSucceededLogin( + (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), + false, + authenticatedUser.getName(), + request + ); } else { if (isDebugEnabled) { log.debug("User still not authenticated after checking {} auth domains", restAuthDomains.size()); } - if(authCredenetials == null && anonymousAuthEnabled) { + if (authCredenetials == null && anonymousAuthEnabled) { final String tenant = Utils.coalesce(request.header("securitytenant"), request.header("security_tenant")); User anonymousUser = new User(User.ANONYMOUS.getName(), new HashSet(User.ANONYMOUS.getRoles()), null); anonymousUser.setRequestedTenant(tenant); @@ -350,26 +386,33 @@ public boolean authenticate(final RestRequest request, final RestChannel channel return true; } - if(firstChallengingHttpAuthenticator != null) { + if (firstChallengingHttpAuthenticator != null) { if (isDebugEnabled) { log.debug("Rerequest with {}", firstChallengingHttpAuthenticator.getClass()); } - if(firstChallengingHttpAuthenticator.reRequestAuthentication(channel, null)) { + if (firstChallengingHttpAuthenticator.reRequestAuthentication(channel, null)) { if (isDebugEnabled) { log.debug("Rerequest {} failed", firstChallengingHttpAuthenticator.getClass()); } - log.warn("Authentication finally failed for {} from {}", authCredenetials == null ? null:authCredenetials.getUsername(), remoteAddress); - auditLog.logFailedLogin(authCredenetials == null ? null:authCredenetials.getUsername(), false, null, request); + log.warn( + "Authentication finally failed for {} from {}", + authCredenetials == null ? null : authCredenetials.getUsername(), + remoteAddress + ); + auditLog.logFailedLogin(authCredenetials == null ? null : authCredenetials.getUsername(), false, null, request); return false; } } - log.warn("Authentication finally failed for {} from {}", authCredenetials == null ? null : authCredenetials.getUsername(), - remoteAddress); - auditLog.logFailedLogin(authCredenetials == null ? null:authCredenetials.getUsername(), false, null, request); + log.warn( + "Authentication finally failed for {} from {}", + authCredenetials == null ? null : authCredenetials.getUsername(), + remoteAddress + ); + auditLog.logFailedLogin(authCredenetials == null ? null : authCredenetials.getUsername(), false, null, request); notifyIpAuthFailureListeners(request, authCredenetials); @@ -382,8 +425,12 @@ public boolean authenticate(final RestRequest request, final RestChannel channel private void notifyIpAuthFailureListeners(RestRequest request, AuthCredentials authCredentials) { notifyIpAuthFailureListeners( - (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() : null, - authCredentials, request); + (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) + ? ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + : null, + authCredentials, + request + ); } private void notifyIpAuthFailureListeners(InetAddress remoteAddress, AuthCredentials authCredentials, Object request) { @@ -397,9 +444,13 @@ private void notifyIpAuthFailureListeners(InetAddress remoteAddress, AuthCredent * * @return null if user cannot b authenticated */ - private User checkExistsAndAuthz(final Cache cache, final User user, final AuthenticationBackend authenticationBackend, - final Set authorizers) { - if(user == null) { + private User checkExistsAndAuthz( + final Cache cache, + final User user, + final AuthenticationBackend authenticationBackend, + final Set authorizers + ) { + if (user == null) { return null; } @@ -407,14 +458,18 @@ private User checkExistsAndAuthz(final Cache cache, final User use final boolean isTraceEnabled = log.isTraceEnabled(); try { - return cache.get(user.getName(), new Callable() { //no cache miss in case of noop + return cache.get(user.getName(), new Callable() { // no cache miss in case of noop @Override public User call() throws Exception { if (isTraceEnabled) { - log.trace("Credentials for user {} not cached, return from {} backend directly", user.getName(), authenticationBackend.getType()); + log.trace( + "Credentials for user {} not cached, return from {} backend directly", + user.getName(), + authenticationBackend.getType() + ); } - if(authenticationBackend.exists(user)) { - authz(user, null, authorizers); //no role cache because no miss here in case of noop + if (authenticationBackend.exists(user)) { + authz(user, null, authorizers); // no role cache because no miss here in case of noop return user; } @@ -431,23 +486,24 @@ public User call() throws Exception { return null; } } + private void authz(User authenticatedUser, Cache> roleCache, final Set authorizers) { - if(authenticatedUser == null) { + if (authenticatedUser == null) { return; } - if(roleCache != null) { + if (roleCache != null) { final Set cachedBackendRoles = roleCache.getIfPresent(authenticatedUser); - if(cachedBackendRoles != null) { + if (cachedBackendRoles != null) { authenticatedUser.addRoles(new HashSet(cachedBackendRoles)); return; } } - if(authorizers == null || authorizers.isEmpty()) { + if (authorizers == null || authorizers.isEmpty()) { return; } @@ -455,7 +511,11 @@ private void authz(User authenticatedUser, Cache> roleCache, f for (final AuthorizationBackend ab : authorizers) { try { if (isTraceEnabled) { - log.trace("Backend roles for {} not cached, return from {} backend directly", authenticatedUser.getName(), ab.getType()); + log.trace( + "Backend roles for {} not cached, return from {} backend directly", + authenticatedUser.getName(), + ab.getType() + ); } ab.fillRoles(authenticatedUser, new AuthCredentials(authenticatedUser.getName())); } catch (Exception e) { @@ -463,7 +523,7 @@ private void authz(User authenticatedUser, Cache> roleCache, f } } - if(roleCache != null) { + if (roleCache != null) { roleCache.put(authenticatedUser, new HashSet(authenticatedUser.getRoles())); } } @@ -473,17 +533,22 @@ private void authz(User authenticatedUser, Cache> roleCache, f * * @return null if user cannot b authenticated */ - private User authcz(final Cache cache, Cache> roleCache, final AuthCredentials ac, - final AuthenticationBackend authBackend, final Set authorizers) { - if(ac == null) { + private User authcz( + final Cache cache, + Cache> roleCache, + final AuthCredentials ac, + final AuthenticationBackend authBackend, + final Set authorizers + ) { + if (ac == null) { return null; } try { - //noop backend configured and no authorizers - //that mean authc and authz was completely done via HTTP (like JWT or PKI) - if(authBackend.getClass() == NoOpAuthenticationBackend.class && authorizers.isEmpty()) { - //no cache + // noop backend configured and no authorizers + // that mean authc and authz was completely done via HTTP (like JWT or PKI) + if (authBackend.getClass() == NoOpAuthenticationBackend.class && authorizers.isEmpty()) { + // no cache return authBackend.authenticate(ac); } @@ -491,7 +556,11 @@ private User authcz(final Cache cache, Cache injectUserAndRoles(TransportRequest transportRequest, String String[] strs = injectedUserAndRoles.split("\\|"); if (strs.length == 0) { - log.error("Roles injected string malformed, could not extract parts. User string was '{}.'" + - " Roles injection failed.", injectedUserAndRoles); + log.error( + "Roles injected string malformed, could not extract parts. User string was '{}.'" + " Roles injection failed.", + injectedUserAndRoles + ); return null; } @@ -71,16 +73,20 @@ public Set injectUserAndRoles(TransportRequest transportRequest, String } Set roles = ImmutableSet.copyOf(strs[1].split(",")); - if(user != null && roles != null) { + if (user != null && roles != null) { addUser(user, transportRequest, action, task, ctx); } return roles; } - private void addUser(final User user, final TransportRequest transportRequest, - final String action, final Task task, final ThreadContext threadContext) { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null) - return; + private void addUser( + final User user, + final TransportRequest transportRequest, + final String action, + final Task task, + final ThreadContext threadContext + ) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null) return; threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); } diff --git a/src/main/java/org/opensearch/security/auth/UserInjector.java b/src/main/java/org/opensearch/security/auth/UserInjector.java index 9253023eb3..9ce040c485 100644 --- a/src/main/java/org/opensearch/security/auth/UserInjector.java +++ b/src/main/java/org/opensearch/security/auth/UserInjector.java @@ -172,16 +172,16 @@ public InjectedUser getInjectedUser() { return injectedUser; } - boolean injectUser(RestRequest request) { InjectedUser injectedUser = getInjectedUser(); - if(injectedUser == null) { + if (injectedUser == null) { return false; } // Set remote address into the thread context if (injectedUser.getTransportAddress() != null) { - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, injectedUser.getTransportAddress()); + threadPool.getThreadContext() + .putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, injectedUser.getTransportAddress()); } else { threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, xffResolver.resolve(request)); } diff --git a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java index e74c3ad70a..801dafc75d 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/ClientBlockRegistry.java @@ -20,6 +20,8 @@ public interface ClientBlockRegistry { boolean isBlocked(ClientIdType clientId); + void block(ClientIdType clientId); + Class getClientIdType(); } diff --git a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java index 450dda54db..334073118a 100644 --- a/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java +++ b/src/main/java/org/opensearch/security/auth/blocking/HeapBasedClientBlockRegistry.java @@ -35,15 +35,19 @@ public class HeapBasedClientBlockRegistry implements ClientBlockRe public HeapBasedClientBlockRegistry(long expiryMs, int maxEntries, Class clientIdType) { this.clientIdType = clientIdType; - this.cache = CacheBuilder.newBuilder().expireAfterWrite(expiryMs, TimeUnit.MILLISECONDS).maximumSize(maxEntries).concurrencyLevel(4) - .removalListener(new RemovalListener() { - @Override - public void onRemoval(RemovalNotification notification) { - if (log.isInfoEnabled()) { - log.info("Unblocking " + notification.getKey()); - } + this.cache = CacheBuilder.newBuilder() + .expireAfterWrite(expiryMs, TimeUnit.MILLISECONDS) + .maximumSize(maxEntries) + .concurrencyLevel(4) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + if (log.isInfoEnabled()) { + log.info("Unblocking " + notification.getKey()); } - }).build(); + } + }) + .build(); } @Override diff --git a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java index adc49c8735..98443a2902 100644 --- a/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java +++ b/src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java @@ -52,27 +52,27 @@ public class InternalAuthenticationBackend implements AuthenticationBackend, Aut @Override public boolean exists(User user) { - if(user == null || internalUsersModel == null) { + if (user == null || internalUsersModel == null) { return false; } final boolean exists = internalUsersModel.exists(user.getName()); - if(exists) { + if (exists) { user.addRoles(internalUsersModel.getBackenRoles(user.getName())); - //FIX https://github.com/opendistro-for-elasticsearch/security/pull/23 - //Credits to @turettn + // FIX https://github.com/opendistro-for-elasticsearch/security/pull/23 + // Credits to @turettn final Map customAttributes = internalUsersModel.getAttributes(user.getName()); Map attributeMap = new HashMap<>(); - if(customAttributes != null) { - for(Entry attributeEntry: customAttributes.entrySet()) { - attributeMap.put("attr.internal."+attributeEntry.getKey(), attributeEntry.getValue()); + if (customAttributes != null) { + for (Entry attributeEntry : customAttributes.entrySet()) { + attributeMap.put("attr.internal." + attributeEntry.getKey(), attributeEntry.getValue()); } } final List securityRoles = internalUsersModel.getSecurityRoles(user.getName()); - if(securityRoles != null) { + if (securityRoles != null) { user.addSecurityRoles(securityRoles); } @@ -104,10 +104,11 @@ public User authenticate(final AuthCredentials credentials) { final byte[] password; String hash; - if(!internalUsersModel.exists(credentials.getUsername())) { + if (!internalUsersModel.exists(credentials.getUsername())) { userExists = false; password = credentials.getPassword(); - hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not found and invalid password + hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not + // found and invalid password } else { userExists = true; password = credentials.getPassword(); @@ -123,22 +124,22 @@ public User authenticate(final AuthCredentials credentials) { char[] array = new char[buf.limit()]; buf.get(array); - Arrays.fill(password, (byte)0); + Arrays.fill(password, (byte) 0); try { if (passwordMatchesHash(hash, array) && userExists) { final List roles = internalUsersModel.getBackenRoles(credentials.getUsername()); final Map customAttributes = internalUsersModel.getAttributes(credentials.getUsername()); - if(customAttributes != null) { - for(Entry attributeName: customAttributes.entrySet()) { - credentials.addAttribute("attr.internal."+attributeName.getKey(), attributeName.getValue()); + if (customAttributes != null) { + for (Entry attributeName : customAttributes.entrySet()) { + credentials.addAttribute("attr.internal." + attributeName.getKey(), attributeName.getValue()); } } final User user = new User(credentials.getUsername(), roles, credentials); final List securityRoles = internalUsersModel.getSecurityRoles(credentials.getUsername()); - if(securityRoles != null) { + if (securityRoles != null) { user.addSecurityRoles(securityRoles); } return user; @@ -149,7 +150,7 @@ public User authenticate(final AuthCredentials credentials) { throw new OpenSearchSecurityException("password does not match"); } } finally { - Arrays.fill(wrap.array(), (byte)0); + Arrays.fill(wrap.array(), (byte) 0); Arrays.fill(buf.array(), '\0'); Arrays.fill(array, '\0'); } @@ -164,18 +165,19 @@ public String getType() { public void fillRoles(User user, AuthCredentials credentials) throws OpenSearchSecurityException { if (internalUsersModel == null) { - throw new OpenSearchSecurityException("Internal authentication backend not configured. May be OpenSearch Security is not initialized."); + throw new OpenSearchSecurityException( + "Internal authentication backend not configured. May be OpenSearch Security is not initialized." + ); } - if(exists(user)) { + if (exists(user)) { final List roles = internalUsersModel.getBackenRoles(user.getName()); - if(roles != null && !roles.isEmpty() && user != null) { + if (roles != null && !roles.isEmpty() && user != null) { user.addRoles(roles); } } - } @Subscribe @@ -183,5 +185,4 @@ public void onInternalUsersModelChanged(InternalUsersModel ium) { this.internalUsersModel = ium; } - } diff --git a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java index a4d596b61d..0fc796d94f 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AbstractRateLimiter.java @@ -32,10 +32,16 @@ public abstract class AbstractRateLimiter implements AuthFailureLi protected final RateTracker rateTracker; public AbstractRateLimiter(Settings settings, Path configPath, Class clientIdType) { - this.clientBlockRegistry = new HeapBasedClientBlockRegistry<>(settings.getAsInt("block_expiry_seconds", 60 * 10) * 1000, - settings.getAsInt("max_blocked_clients", 100_000), clientIdType); - this.rateTracker = RateTracker.create(settings.getAsInt("time_window_seconds", 60 * 60) * 1000, settings.getAsInt("allowed_tries", 10), - settings.getAsInt("max_tracked_clients", 100_000)); + this.clientBlockRegistry = new HeapBasedClientBlockRegistry<>( + settings.getAsInt("block_expiry_seconds", 60 * 10) * 1000, + settings.getAsInt("max_blocked_clients", 100_000), + clientIdType + ); + this.rateTracker = RateTracker.create( + settings.getAsInt("time_window_seconds", 60 * 60) * 1000, + settings.getAsInt("allowed_tries", 10), + settings.getAsInt("max_tracked_clients", 100_000) + ); } @Override diff --git a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java index 35a6571f8f..876615870a 100644 --- a/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java +++ b/src/main/java/org/opensearch/security/auth/limiting/AddressBasedRateLimiter.java @@ -25,7 +25,10 @@ import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.user.AuthCredentials; -public class AddressBasedRateLimiter extends AbstractRateLimiter implements AuthFailureListener, ClientBlockRegistry { +public class AddressBasedRateLimiter extends AbstractRateLimiter + implements + AuthFailureListener, + ClientBlockRegistry { public AddressBasedRateLimiter(Settings settings, Path configPath) { super(settings, configPath, InetAddress.class); diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java index 4b0f3079f2..1d81479f37 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceConfig.java @@ -105,19 +105,20 @@ public class ComplianceConfig { private final boolean enabled; private ComplianceConfig( - final boolean enabled, - final boolean logExternalConfig, - final boolean logInternalConfig, - final boolean logReadMetadataOnly, - final Map> watchedReadFields, - final Set ignoredComplianceUsersForRead, - final boolean logWriteMetadataOnly, - final boolean logDiffsForWrite, - final List watchedWriteIndicesPatterns, - final Set ignoredComplianceUsersForWrite, - final String securityIndex, - final String destinationType, - final String destinationIndex) { + final boolean enabled, + final boolean logExternalConfig, + final boolean logInternalConfig, + final boolean logReadMetadataOnly, + final Map> watchedReadFields, + final Set ignoredComplianceUsersForRead, + final boolean logWriteMetadataOnly, + final boolean logDiffsForWrite, + final List watchedWriteIndicesPatterns, + final Set ignoredComplianceUsersForWrite, + final String securityIndex, + final String destinationType, + final String destinationIndex + ) { this.enabled = enabled; this.logExternalConfig = logExternalConfig; this.logInternalConfig = logInternalConfig; @@ -133,22 +134,20 @@ private ComplianceConfig( this.watchedWriteIndicesPatterns = watchedWriteIndicesPatterns; this.ignoredComplianceUsersForWrite = ignoredComplianceUsersForWrite; - this.readEnabledFields = watchedReadFields.entrySet().stream() - .filter(entry -> !Strings.isNullOrEmpty(entry.getKey())) - .collect( - ImmutableMap.toImmutableMap( - entry -> WildcardMatcher.from(entry.getKey()), - entry -> ImmutableSet.copyOf(entry.getValue()) - ) - ); + this.readEnabledFields = watchedReadFields.entrySet() + .stream() + .filter(entry -> !Strings.isNullOrEmpty(entry.getKey())) + .collect( + ImmutableMap.toImmutableMap(entry -> WildcardMatcher.from(entry.getKey()), entry -> ImmutableSet.copyOf(entry.getValue())) + ); DateTimeFormatter auditLogPattern = null; String auditLogIndex = null; if (INTERNAL_OPENSEARCH.equalsIgnoreCase(destinationType)) { try { - auditLogPattern = DateTimeFormat.forPattern(destinationIndex); //throws IllegalArgumentException if no pattern + auditLogPattern = DateTimeFormat.forPattern(destinationIndex); // throws IllegalArgumentException if no pattern } catch (IllegalArgumentException e) { - //no pattern + // no pattern auditLogIndex = destinationIndex; } catch (Exception e) { log.error("Unable to check if auditlog index {} is part of compliance setup", destinationIndex, e); @@ -157,43 +156,45 @@ private ComplianceConfig( this.auditLogPattern = auditLogPattern; this.auditLogIndex = auditLogIndex; - this.readEnabledFieldsCache = CacheBuilder.newBuilder() - .maximumSize(CACHE_SIZE) - .build(new CacheLoader() { - @Override - public WildcardMatcher load(String index) throws Exception { - return WildcardMatcher.from(getFieldsForIndex(index)); - } - }); + this.readEnabledFieldsCache = CacheBuilder.newBuilder().maximumSize(CACHE_SIZE).build(new CacheLoader() { + @Override + public WildcardMatcher load(String index) throws Exception { + return WildcardMatcher.from(getFieldsForIndex(index)); + } + }); } @VisibleForTesting public ComplianceConfig( - final boolean enabled, - final boolean logExternalConfig, - final boolean logInternalConfig, - final boolean logReadMetadataOnly, - final Map> watchedReadFields, - final Set ignoredComplianceUsersForRead, - final boolean logWriteMetadataOnly, - final boolean logDiffsForWrite, - final List watchedWriteIndicesPatterns, - final Set ignoredComplianceUsersForWrite, - Settings settings) { + final boolean enabled, + final boolean logExternalConfig, + final boolean logInternalConfig, + final boolean logReadMetadataOnly, + final Map> watchedReadFields, + final Set ignoredComplianceUsersForRead, + final boolean logWriteMetadataOnly, + final boolean logDiffsForWrite, + final List watchedWriteIndicesPatterns, + final Set ignoredComplianceUsersForWrite, + Settings settings + ) { this( - enabled, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - watchedReadFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndicesPatterns, - ignoredComplianceUsersForWrite, - settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX), - settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, null), - settings.get(ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, "'security-auditlog-'YYYY.MM.dd") + enabled, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + watchedReadFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndicesPatterns, + ignoredComplianceUsersForWrite, + settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX), + settings.get(ConfigConstants.SECURITY_AUDIT_TYPE_DEFAULT, null), + settings.get( + ConfigConstants.SECURITY_AUDIT_CONFIG_DEFAULT_PREFIX + ConfigConstants.SECURITY_AUDIT_OPENSEARCH_INDEX, + "'security-auditlog-'YYYY.MM.dd" + ) ); } @@ -215,7 +216,14 @@ public void log(Logger logger) { @JsonCreator public static ComplianceConfig from(Map properties, @JacksonInject Settings settings) throws JsonProcessingException { if (!FIELDS.containsAll(properties.keySet())) { - throw new UnrecognizedPropertyException(null, "Invalid property present in the input data for compliance config", null, ComplianceConfig.class, null, null); + throw new UnrecognizedPropertyException( + null, + "Invalid property present in the input data for compliance config", + null, + ComplianceConfig.class, + null, + null + ); } final boolean enabled = getOrDefault(properties, "enabled", true); @@ -223,24 +231,28 @@ public static ComplianceConfig from(Map properties, @JacksonInje final boolean logInternalConfig = getOrDefault(properties, "internal_config", false); final boolean logReadMetadataOnly = getOrDefault(properties, "read_metadata_only", false); final Map> watchedReadFields = getOrDefault(properties, "read_watched_fields", Collections.emptyMap()); - final Set ignoredComplianceUsersForRead = ImmutableSet.copyOf(getOrDefault(properties, "read_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)); + final Set ignoredComplianceUsersForRead = ImmutableSet.copyOf( + getOrDefault(properties, "read_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS) + ); final boolean logWriteMetadataOnly = getOrDefault(properties, "write_metadata_only", false); final boolean logDiffsForWrite = getOrDefault(properties, "write_log_diffs", false); final List watchedWriteIndicesPatterns = getOrDefault(properties, "write_watched_indices", Collections.emptyList()); - final Set ignoredComplianceUsersForWrite = ImmutableSet.copyOf(getOrDefault(properties, "write_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS)); + final Set ignoredComplianceUsersForWrite = ImmutableSet.copyOf( + getOrDefault(properties, "write_ignore_users", AuditConfig.DEFAULT_IGNORED_USERS) + ); return new ComplianceConfig( - enabled, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - watchedReadFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndicesPatterns, - ignoredComplianceUsersForWrite, - settings + enabled, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + watchedReadFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndicesPatterns, + ignoredComplianceUsersForWrite, + settings ); } @@ -250,47 +262,71 @@ public static ComplianceConfig from(Map properties, @JacksonInje * @return compliance configuration */ public static ComplianceConfig from(Settings settings) { - final boolean logExternalConfig = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, false); + final boolean logExternalConfig = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_EXTERNAL_CONFIG_ENABLED, + false + ); final boolean logInternalConfig = settings.getAsBoolean(ConfigConstants.SECURITY_COMPLIANCE_HISTORY_INTERNAL_CONFIG_ENABLED, false); - final boolean logReadMetadataOnly = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, false); - final boolean logWriteMetadataOnly = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, false); - final boolean logDiffsForWrite = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, false); - final List watchedReadFields = settings.getAsList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, - Collections.emptyList(), false); - //plugins.security.compliance.pii_fields: - // - indexpattern,fieldpattern,fieldpattern,.... + final boolean logReadMetadataOnly = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_METADATA_ONLY, + false + ); + final boolean logWriteMetadataOnly = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_METADATA_ONLY, + false + ); + final boolean logDiffsForWrite = settings.getAsBoolean( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_LOG_DIFFS, + false + ); + final List watchedReadFields = settings.getAsList( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_WATCHED_FIELDS, + Collections.emptyList(), + false + ); + // plugins.security.compliance.pii_fields: + // - indexpattern,fieldpattern,fieldpattern,.... final Map> readEnabledFields = watchedReadFields.stream() - .map(watchedReadField -> watchedReadField.split(",")) - .filter(split -> split.length != 0 && !Strings.isNullOrEmpty(split[0])) - .collect(ImmutableMap.toImmutableMap( - split -> split[0], - split -> split.length == 1 ? - ImmutableList.of("*") : Arrays.stream(split).skip(1).collect(ImmutableList.toImmutableList()) - )); - final List watchedWriteIndices = settings.getAsList(ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, Collections.emptyList()); + .map(watchedReadField -> watchedReadField.split(",")) + .filter(split -> split.length != 0 && !Strings.isNullOrEmpty(split[0])) + .collect( + ImmutableMap.toImmutableMap( + split -> split[0], + split -> split.length == 1 + ? ImmutableList.of("*") + : Arrays.stream(split).skip(1).collect(ImmutableList.toImmutableList()) + ) + ); + final List watchedWriteIndices = settings.getAsList( + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_WATCHED_INDICES, + Collections.emptyList() + ); final Set ignoredComplianceUsersForRead = ConfigConstants.getSettingAsSet( - settings, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, - AuditConfig.DEFAULT_IGNORED_USERS, - false); + settings, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_READ_IGNORE_USERS, + AuditConfig.DEFAULT_IGNORED_USERS, + false + ); final Set ignoredComplianceUsersForWrite = ConfigConstants.getSettingAsSet( - settings, - ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, - AuditConfig.DEFAULT_IGNORED_USERS, - false); + settings, + ConfigConstants.OPENDISTRO_SECURITY_COMPLIANCE_HISTORY_WRITE_IGNORE_USERS, + AuditConfig.DEFAULT_IGNORED_USERS, + false + ); return new ComplianceConfig( - true, - logExternalConfig, - logInternalConfig, - logReadMetadataOnly, - readEnabledFields, - ignoredComplianceUsersForRead, - logWriteMetadataOnly, - logDiffsForWrite, - watchedWriteIndices, - ignoredComplianceUsersForWrite, - settings); + true, + logExternalConfig, + logInternalConfig, + logReadMetadataOnly, + readEnabledFields, + ignoredComplianceUsersForRead, + logWriteMetadataOnly, + logDiffsForWrite, + watchedWriteIndices, + ignoredComplianceUsersForWrite, + settings + ); } /** @@ -415,10 +451,11 @@ private Set getFieldsForIndex(String index) { } } - return readEnabledFields.entrySet().stream() - .filter(entry -> entry.getKey().test(index)) - .flatMap(entry -> entry.getValue().stream()) - .collect(ImmutableSet.toImmutableSet()); + return readEnabledFields.entrySet() + .stream() + .filter(entry -> entry.getKey().test(index)) + .flatMap(entry -> entry.getValue().stream()) + .collect(ImmutableSet.toImmutableSet()); } /** diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java index b7803a4836..3d31111407 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListener.java @@ -37,6 +37,6 @@ public class ComplianceIndexingOperationListener implements IndexingOperationListener { public void setIs(IndexService is) { - //noop + // noop } } diff --git a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java index c8834c9e31..cf369bb0cb 100644 --- a/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java +++ b/src/main/java/org/opensearch/security/compliance/ComplianceIndexingOperationListenerImpl.java @@ -40,7 +40,7 @@ public ComplianceIndexingOperationListenerImpl(final AuditLog auditlog) { @Override public void setIs(final IndexService is) { - if(this.is != null) { + if (this.is != null) { throw new OpenSearchException("Index service already set"); } this.is = is; @@ -66,7 +66,9 @@ public void postDelete(final ShardId shardId, final Delete delete, final DeleteR final ComplianceConfig complianceConfig = auditlog.getComplianceConfig(); if (isLoggingWriteEnabled(complianceConfig, shardId.getIndexName())) { Objects.requireNonNull(is); - if(result.getFailure() == null && result.isFound() && delete.origin() == org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { + if (result.getFailure() == null + && result.isFound() + && delete.origin() == org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { auditlog.logDocumentDeleted(shardId, delete, result); } } @@ -83,15 +85,14 @@ public Index preIndex(final ShardId shardId, final Index index) { return index; } - if((shard = is.getShardOrNull(shardId.getId())) == null) { + if ((shard = is.getShardOrNull(shardId.getId())) == null) { return index; } if (shard.isReadAllowed()) { try { - final GetResult getResult = shard.getService().getForUpdate(index.id(), - index.getIfSeqNo(), index.getIfPrimaryTerm()); + final GetResult getResult = shard.getService().getForUpdate(index.id(), index.getIfSeqNo(), index.getIfPrimaryTerm()); if (getResult.isExists()) { threadContext.set(new Context(getResult)); @@ -113,7 +114,6 @@ public Index preIndex(final ShardId shardId, final Index index) { return index; } - @Override public void postIndex(final ShardId shardId, final Index index, final Exception ex) { if (isLoggingWriteDiffEnabled(auditlog.getComplianceConfig(), shardId.getIndexName())) { @@ -130,7 +130,7 @@ public void postIndex(ShardId shardId, Index index, IndexResult result) { if (complianceConfig.shouldLogDiffsForWrite()) { final Context context = threadContext.get(); - final GetResult previousContent = context==null?null:context.getGetResult(); + final GetResult previousContent = context == null ? null : context.getGetResult(); threadContext.remove(); Objects.requireNonNull(is); @@ -138,26 +138,31 @@ public void postIndex(ShardId shardId, Index index, IndexResult result) { return; } - if(is.getShardOrNull(shardId.getId()) == null) { + if (is.getShardOrNull(shardId.getId()) == null) { return; } - if(previousContent == null) { - //no previous content - if(!result.isCreated()) { - log.warn("No previous content and not created (its an update but do not find orig source) for {}/{}/{}", index.startTime(), shardId, index.id()); + if (previousContent == null) { + // no previous content + if (!result.isCreated()) { + log.warn( + "No previous content and not created (its an update but do not find orig source) for {}/{}/{}", + index.startTime(), + shardId, + index.id() + ); } - assert result.isCreated():"No previous content and not created"; + assert result.isCreated() : "No previous content and not created"; } else { - if(result.isCreated()) { + if (result.isCreated()) { log.warn("Previous content and created for {}/{}/{}", index.startTime(), shardId, index.id()); } - assert !result.isCreated():"Previous content and created"; + assert !result.isCreated() : "Previous content and created"; } auditlog.logDocumentWritten(shardId, previousContent, index, result); } else { - //no diffs + // no diffs if (result.getFailure() != null || index.origin() != org.opensearch.index.engine.Engine.Operation.Origin.PRIMARY) { return; } diff --git a/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java b/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java index ff507c2a73..73f536c2f8 100644 --- a/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java +++ b/src/main/java/org/opensearch/security/compliance/FieldReadCallback.java @@ -46,8 +46,8 @@ public final class FieldReadCallback { private static final Logger log = LogManager.getLogger(FieldReadCallback.class); - //private final ThreadContext threadContext; - //private final ClusterService clusterService; + // private final ThreadContext threadContext; + // private final ClusterService clusterService; private final Index index; private final WildcardMatcher maskedFieldsMatcher; private final AuditLog auditLog; @@ -56,19 +56,24 @@ public final class FieldReadCallback { private Doc doc; private final ShardId shardId; - public FieldReadCallback(final ThreadContext threadContext, final IndexService indexService, - final ClusterService clusterService, final AuditLog auditLog, - final WildcardMatcher maskedFieldsMatcher, ShardId shardId) { + public FieldReadCallback( + final ThreadContext threadContext, + final IndexService indexService, + final ClusterService clusterService, + final AuditLog auditLog, + final WildcardMatcher maskedFieldsMatcher, + ShardId shardId + ) { super(); - //this.threadContext = Objects.requireNonNull(threadContext); - //this.clusterService = Objects.requireNonNull(clusterService); + // this.threadContext = Objects.requireNonNull(threadContext); + // this.clusterService = Objects.requireNonNull(clusterService); this.index = Objects.requireNonNull(indexService).index(); this.auditLog = auditLog; this.maskedFieldsMatcher = maskedFieldsMatcher; this.shardId = shardId; try { sfc = (SourceFieldsContext) HeaderHelper.deserializeSafeFromHeader(threadContext, "_opendistro_security_source_field_context"); - if(sfc != null && sfc.hasIncludesOrExcludes()) { + if (sfc != null && sfc.hasIncludesOrExcludes()) { if (log.isTraceEnabled()) { log.trace("_opendistro_security_source_field_context: {}", sfc); } @@ -76,39 +81,40 @@ public FieldReadCallback(final ThreadContext threadContext, final IndexService i filterFunction = XContentMapValues.filter(sfc.getIncludes(), sfc.getExcludes()); } } catch (Exception e) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("Cannot deserialize _opendistro_security_source_field_context because of {}", e.toString()); } } } private boolean recordField(final String fieldName, boolean isStringField) { - return !(isStringField && maskedFieldsMatcher.test(fieldName)) && auditLog.getComplianceConfig().readHistoryEnabledForField(index.getName(), fieldName); + return !(isStringField && maskedFieldsMatcher.test(fieldName)) + && auditLog.getComplianceConfig().readHistoryEnabledForField(index.getName(), fieldName); } public void binaryFieldRead(final FieldInfo fieldInfo, byte[] fieldValue) { try { - if(!recordField(fieldInfo.name, false) && !fieldInfo.name.equals("_source") && !fieldInfo.name.equals("_id")) { + if (!recordField(fieldInfo.name, false) && !fieldInfo.name.equals("_source") && !fieldInfo.name.equals("_id")) { return; } - if(fieldInfo.name.equals("_source")) { + if (fieldInfo.name.equals("_source")) { - if(filterFunction != null) { + if (filterFunction != null) { final Map filteredSource = filterFunction.apply(Utils.byteArrayToMutableJsonMap(fieldValue)); fieldValue = Utils.jsonMapToByteArray(filteredSource); } Map filteredSource = new JsonFlattener(new String(fieldValue, StandardCharsets.UTF_8)).flattenAsMap(); - for(String k: filteredSource.keySet()) { - if(!recordField(k, filteredSource.get(k) instanceof String)) { + for (String k : filteredSource.keySet()) { + if (!recordField(k, filteredSource.get(k) instanceof String)) { continue; } fieldRead0(k, filteredSource.get(k)); } } else if (fieldInfo.name.equals("_id")) { fieldRead0(fieldInfo.name, Uid.decodeId(fieldValue)); - } else { + } else { fieldRead0(fieldInfo.name, new String(fieldValue, StandardCharsets.UTF_8)); } } catch (Exception e) { @@ -118,7 +124,7 @@ public void binaryFieldRead(final FieldInfo fieldInfo, byte[] fieldValue) { public void stringFieldRead(final FieldInfo fieldInfo, final String fieldValue) { try { - if(!recordField(fieldInfo.name, true)) { + if (!recordField(fieldInfo.name, true)) { return; } fieldRead0(fieldInfo.name, fieldValue); @@ -129,7 +135,7 @@ public void stringFieldRead(final FieldInfo fieldInfo, final String fieldValue) public void numericFieldRead(final FieldInfo fieldInfo, final Number fieldValue) { try { - if(!recordField(fieldInfo.name, false)) { + if (!recordField(fieldInfo.name, false)) { return; } fieldRead0(fieldInfo.name, fieldValue); @@ -139,15 +145,15 @@ public void numericFieldRead(final FieldInfo fieldInfo, final Number fieldValue) } private void fieldRead0(final String fieldName, final Object fieldValue) { - if(doc != null) { - if(fieldName.equals("_id")) { + if (doc != null) { + if (fieldName.equals("_id")) { doc.setId(fieldValue.toString()); } else { doc.addField(new Field(fieldName, fieldValue)); } } else { final String indexName = index.getName(); - if(fieldName.equals("_id")) { + if (fieldName.equals("_id")) { doc = new Doc(indexName, fieldValue.toString()); } else { doc = new Doc(indexName, null); @@ -157,12 +163,12 @@ private void fieldRead0(final String fieldName, final Object fieldValue) { } public void finished() { - if(doc == null) { + if (doc == null) { return; } try { Map f = new HashMap(); - for(Field fi: doc.fields) { + for (Field fi : doc.fields) { f.put(fi.fieldName, String.valueOf(fi.fieldValue)); } auditLog.logDocumentRead(doc.indexName, doc.id, shardId, f); @@ -202,11 +208,13 @@ public String toString() { private class Field { final String fieldName; final Object fieldValue; + public Field(String fieldName, Object fieldValue) { super(); this.fieldName = fieldName; this.fieldValue = fieldValue; } + @Override public String toString() { return "Field [fieldName=" + fieldName + ", fieldValue=" + fieldValue + "]"; diff --git a/src/main/java/org/opensearch/security/configuration/AdminDNs.java b/src/main/java/org/opensearch/security/configuration/AdminDNs.java index 72a4485e9f..204f277808 100644 --- a/src/main/java/org/opensearch/security/configuration/AdminDNs.java +++ b/src/main/java/org/opensearch/security/configuration/AdminDNs.java @@ -63,7 +63,7 @@ public AdminDNs(final Settings settings) { final List adminDnsA = settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_ADMIN_DN, Collections.emptyList()); - for (String dn:adminDnsA) { + for (String dn : adminDnsA) { try { log.debug("{} is registered as an admin dn", dn); adminDn.add(new LdapName(dn)); @@ -75,16 +75,17 @@ public AdminDNs(final Settings settings) { } adminUsernames.add(dn); } else { - log.error("Unable to parse admin dn {}",dn, e); + log.error("Unable to parse admin dn {}", dn, e); } } } - log.debug("Loaded {} admin DN's {}",adminDn.size(), adminDn); + log.debug("Loaded {} admin DN's {}", adminDn.size(), adminDn); - final Settings impersonationDns = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN+"."); + final Settings impersonationDns = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_IMPERSONATION_DN + "."); - allowedDnsImpersonations = impersonationDns.keySet().stream() + allowedDnsImpersonations = impersonationDns.keySet() + .stream() .map(this::toLdapName) .filter(Objects::nonNull) .collect( @@ -96,17 +97,18 @@ public AdminDNs(final Settings settings) { log.debug("Loaded {} impersonation DN's {}", allowedDnsImpersonations.size(), allowedDnsImpersonations); - final Settings impersonationUsersRest = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."); + final Settings impersonationUsersRest = settings.getByPrefix(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + "."); - allowedRestImpersonations = impersonationUsersRest.keySet().stream() + allowedRestImpersonations = impersonationUsersRest.keySet() + .stream() .collect( ImmutableMap.toImmutableMap( Function.identity(), - user -> WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS+"."+user)) + user -> WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_AUTHCZ_REST_IMPERSONATION_USERS + "." + user)) ) ); - log.debug("Loaded {} impersonation users for REST {}",allowedRestImpersonations.size(), allowedRestImpersonations); + log.debug("Loaded {} impersonation users for REST {}", allowedRestImpersonations.size(), allowedRestImpersonations); } private LdapName toLdapName(String dn) { @@ -132,17 +134,17 @@ public boolean isAdmin(User user) { public boolean isAdminDN(String dn) { - if(dn == null) return false; + if (dn == null) return false; try { return isAdminDN(new LdapName(dn)); } catch (InvalidNameException e) { - return false; + return false; } } private boolean isAdminDN(LdapName dn) { - if(dn == null) return false; + if (dn == null) return false; boolean isAdmin = adminDn.contains(dn); @@ -154,6 +156,8 @@ private boolean isAdminDN(LdapName dn) { } public boolean isRestImpersonationAllowed(final String originalUser, final String impersonated) { - return (originalUser != null) ? allowedRestImpersonations.getOrDefault(originalUser, WildcardMatcher.NONE).test(impersonated) : false; + return (originalUser != null) + ? allowedRestImpersonations.getOrDefault(originalUser, WildcardMatcher.NONE).test(impersonated) + : false; } } diff --git a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java index 1c42321986..00101d9a73 100644 --- a/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java +++ b/src/main/java/org/opensearch/security/configuration/ClusterInfoHolder.java @@ -43,7 +43,7 @@ public class ClusterInfoHolder implements ClusterStateListener { @Override public void clusterChanged(ClusterChangedEvent event) { - if(nodes == null || event.nodesChanged()) { + if (nodes == null || event.nodesChanged()) { nodes = event.state().nodes(); if (log.isDebugEnabled()) { log.debug("Cluster Info Holder now initialized for 'nodes'"); @@ -51,7 +51,7 @@ public void clusterChanged(ClusterChangedEvent event) { initialized = true; } - isLocalNodeElectedClusterManager = event.localNodeClusterManager()?Boolean.TRUE:Boolean.FALSE; + isLocalNodeElectedClusterManager = event.localNodeClusterManager() ? Boolean.TRUE : Boolean.FALSE; } public Boolean isLocalNodeElectedClusterManager() { @@ -63,13 +63,13 @@ public boolean isInitialized() { } public Boolean hasNode(DiscoveryNode node) { - if(nodes == null) { - if(log.isDebugEnabled()) { + if (nodes == null) { + if (log.isDebugEnabled()) { log.debug("Cluster Info Holder not initialized yet for 'nodes'"); } return null; } - return nodes.nodeExists(node)?Boolean.TRUE:Boolean.FALSE; + return nodes.nodeExists(node) ? Boolean.TRUE : Boolean.FALSE; } } diff --git a/src/main/java/org/opensearch/security/configuration/CompatConfig.java b/src/main/java/org/opensearch/security/configuration/CompatConfig.java index 48f91b10be..ec2a521afe 100644 --- a/src/main/java/org/opensearch/security/configuration/CompatConfig.java +++ b/src/main/java/org/opensearch/security/configuration/CompatConfig.java @@ -57,12 +57,15 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { log.debug("dynamicSecurityConfig updated?: {}", (dcm != null)); } - //true is default + // true is default public boolean restAuthEnabled() { - final boolean restInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, false); + final boolean restInitiallyDisabled = staticSettings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_REST_AUTH_INITIALLY, + false + ); final boolean isTraceEnabled = log.isTraceEnabled(); - if(restInitiallyDisabled) { - if(dcm == null) { + if (restInitiallyDisabled) { + if (dcm == null) { if (isTraceEnabled) { log.trace("dynamicSecurityConfig is null, initially static restDisabled"); } @@ -80,12 +83,15 @@ public boolean restAuthEnabled() { } - //true is default + // true is default public boolean transportInterClusterAuthEnabled() { - final boolean interClusterAuthInitiallyDisabled = staticSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, false); + final boolean interClusterAuthInitiallyDisabled = staticSettings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_DISABLE_INTERTRANSPORT_AUTH_INITIALLY, + false + ); final boolean isTraceEnabled = log.isTraceEnabled(); - if(interClusterAuthInitiallyDisabled) { - if(dcm == null) { + if (interClusterAuthInitiallyDisabled) { + if (dcm == null) { if (isTraceEnabled) { log.trace("dynamicSecurityConfig is null, initially static interClusterAuthDisabled"); } @@ -107,7 +113,7 @@ public boolean transportInterClusterAuthEnabled() { */ public boolean transportInterClusterPassiveAuthEnabled() { final boolean interClusterAuthInitiallyPassive = transportPassiveAuthSetting.getDynamicSettingValue(); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("{} {}", SECURITY_UNSUPPORTED_PASSIVE_INTERTRANSPORT_AUTH_INITIALLY, interClusterAuthInitiallyPassive); } return interClusterAuthInitiallyPassive; diff --git a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java index cb8fc1eedf..a3b4cff90d 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigCallback.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigCallback.java @@ -32,8 +32,11 @@ public interface ConfigCallback { void success(SecurityDynamicConfiguration dConf); + void noData(String id); + void singleFailure(Failure failure); + void failure(Throwable t); } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java index 3019c76462..f7802fd10e 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationLoaderSecurity7.java @@ -78,7 +78,10 @@ public class ConfigurationLoaderSecurity7 { super(); this.client = client; this.settings = settings; - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.cs = cs; log.debug("Index is: {}", securityIndex); } @@ -91,7 +94,8 @@ boolean isAuditConfigDocPresentInIndex() { return isAuditConfigDocPresentInIndex.get(); } - Map> load(final CType[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid) throws InterruptedException, TimeoutException { + Map> load(final CType[] events, long timeout, TimeUnit timeUnit, boolean acceptInvalid) + throws InterruptedException, TimeoutException { final CountDownLatch latch = new CountDownLatch(events.length); final Map> rs = new HashMap<>(events.length); final boolean isDebugEnabled = log.isDebugEnabled(); @@ -99,8 +103,13 @@ Map> load(final CType[] events, long time @Override public void success(SecurityDynamicConfiguration dConf) { - if(latch.getCount() <= 0) { - log.error("Latch already counted down (for {} of {}) (index={})", dConf.getCType().toLCString(), Arrays.toString(events), securityIndex); + if (latch.getCount() <= 0) { + log.error( + "Latch already counted down (for {} of {}) (index={})", + dConf.getCType().toLCString(), + Arrays.toString(events), + securityIndex + ); } // Audit configuration doc is available in the index. @@ -112,25 +121,39 @@ public void success(SecurityDynamicConfiguration dConf) { rs.put(dConf.getCType(), dConf); latch.countDown(); if (isDebugEnabled) { - log.debug("Received config for {} (of {}) with current latch value={}", dConf.getCType().toLCString(), Arrays.toString(events), latch.getCount()); + log.debug( + "Received config for {} (of {}) with current latch value={}", + dConf.getCType().toLCString(), + Arrays.toString(events), + latch.getCount() + ); } } @Override public void singleFailure(Failure failure) { - log.error("Failure {} retrieving configuration for {} (index={})", failure==null?null:failure.getMessage(), Arrays.toString(events), securityIndex); + log.error( + "Failure {} retrieving configuration for {} (index={})", + failure == null ? null : failure.getMessage(), + Arrays.toString(events), + securityIndex + ); } @Override public void noData(String id) { CType cType = CType.fromString(id); - // Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by returning valid empty + // Since NODESDN is newly introduced data-type applying for existing clusters as well, we make it backward compatible by + // returning valid empty // SecurityDynamicConfiguration. // Same idea for new setting WHITELIST/ALLOWLIST if (cType == CType.NODESDN || cType == CType.WHITELIST || cType == CType.ALLOWLIST) { try { - SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc(cType, ConfigurationRepository.getDefaultConfigVersion()); + SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc( + cType, + ConfigurationRepository.getDefaultConfigVersion() + ); rs.put(cType, empty); latch.countDown(); return; @@ -139,12 +162,15 @@ public void noData(String id) { } } - if(cType == CType.AUDIT) { + if (cType == CType.AUDIT) { // Audit configuration doc is not available in the index. // Configuration cannot be hot-reloaded. isAuditConfigDocPresentInIndex.set(false); try { - SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc(cType, ConfigurationRepository.getDefaultConfigVersion()); + SecurityDynamicConfiguration empty = ConfigHelper.createEmptySdc( + cType, + ConfigurationRepository.getDefaultConfigVersion() + ); empty.putCObject("config", AuditConfig.from(settings)); rs.put(cType, empty); latch.countDown(); @@ -163,16 +189,26 @@ public void failure(Throwable t) { } }, acceptInvalid); - if(!latch.await(timeout, timeUnit)) { - //timeout - throw new TimeoutException("Timeout after "+timeout+""+timeUnit+" while retrieving configuration for "+Arrays.toString(events)+ "(index="+securityIndex+")"); + if (!latch.await(timeout, timeUnit)) { + // timeout + throw new TimeoutException( + "Timeout after " + + timeout + + "" + + timeUnit + + " while retrieving configuration for " + + Arrays.toString(events) + + "(index=" + + securityIndex + + ")" + ); } return rs; } void loadAsync(final CType[] events, final ConfigCallback callback, boolean acceptInvalid) { - if(events == null || events.length == 0) { + if (events == null || events.length == 0) { log.warn("No config events requested to load"); return; } @@ -193,28 +229,28 @@ public void onResponse(MultiGetResponse response) { MultiGetItemResponse[] responses = response.getResponses(); for (int i = 0; i < responses.length; i++) { MultiGetItemResponse singleResponse = responses[i]; - if(singleResponse != null && !singleResponse.isFailed()) { + if (singleResponse != null && !singleResponse.isFailed()) { GetResponse singleGetResponse = singleResponse.getResponse(); - if(singleGetResponse.isExists() && !singleGetResponse.isSourceEmpty()) { - //success + if (singleGetResponse.isExists() && !singleGetResponse.isSourceEmpty()) { + // success try { final SecurityDynamicConfiguration dConf = toConfig(singleGetResponse, acceptInvalid); - if(dConf != null) { + if (dConf != null) { callback.success(dConf.deepClone()); } else { - callback.failure(new Exception("Cannot parse settings for "+singleGetResponse.getId())); + callback.failure(new Exception("Cannot parse settings for " + singleGetResponse.getId())); } } catch (Exception e) { log.error(e.toString()); callback.failure(e); } } else { - //does not exist or empty source + // does not exist or empty source callback.noData(singleGetResponse.getId()); } } else { - //failure - callback.singleFailure(singleResponse==null?null:singleResponse.getFailure()); + // failure + callback.singleFailure(singleResponse == null ? null : singleResponse.getFailure()); } } } @@ -233,8 +269,6 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, final long seqNo = singleGetResponse.getSeqNo(); final long primaryTerm = singleGetResponse.getPrimaryTerm(); - - if (ref == null || ref.length() == 0) { log.error("Empty or null byte reference for {}", id); return null; @@ -247,7 +281,7 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, parser.nextToken(); parser.nextToken(); - if(!id.equals((parser.currentName()))) { + if (!id.equals((parser.currentName()))) { log.error("Cannot parse config for type {} because {}!={}", id, id, parser.currentName()); return null; } @@ -258,35 +292,47 @@ private SecurityDynamicConfiguration toConfig(GetResponse singleGetResponse, final JsonNode jsonNode = DefaultObjectMapper.readTree(jsonAsString); int configVersion = 1; - - - if(jsonNode.get("_meta") != null) { + if (jsonNode.get("_meta") != null) { assert jsonNode.get("_meta").get("type").asText().equals(id); configVersion = jsonNode.get("_meta").get("config_version").asInt(); } - if(log.isDebugEnabled()) { - log.debug("Load "+id+" with version "+configVersion); + if (log.isDebugEnabled()) { + log.debug("Load " + id + " with version " + configVersion); } if (CType.ACTIONGROUPS.toLCString().equals(id)) { try { - return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), configVersion, seqNo, primaryTerm, acceptInvalid); + return SecurityDynamicConfiguration.fromJson( + jsonAsString, + CType.fromString(id), + configVersion, + seqNo, + primaryTerm, + acceptInvalid + ); } catch (Exception e) { - if(log.isDebugEnabled()) { - log.debug("Unable to load "+id+" with version "+configVersion+" - Try loading legacy format ..."); + if (log.isDebugEnabled()) { + log.debug("Unable to load " + id + " with version " + configVersion + " - Try loading legacy format ..."); } return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), 0, seqNo, primaryTerm, acceptInvalid); } } - return SecurityDynamicConfiguration.fromJson(jsonAsString, CType.fromString(id), configVersion, seqNo, primaryTerm, acceptInvalid); + return SecurityDynamicConfiguration.fromJson( + jsonAsString, + CType.fromString(id), + configVersion, + seqNo, + primaryTerm, + acceptInvalid + ); } finally { - if(parser != null) { + if (parser != null) { try { parser.close(); } catch (IOException e) { - //ignore + // ignore } } } diff --git a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java index 31e2a7d16f..92ae22af01 100644 --- a/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java +++ b/src/main/java/org/opensearch/security/configuration/ConfigurationRepository.java @@ -95,9 +95,18 @@ public class ConfigurationRepository { private final AtomicBoolean installDefaultConfig = new AtomicBoolean(); private final boolean acceptInvalid; - private ConfigurationRepository(Settings settings, final Path configPath, ThreadPool threadPool, - Client client, ClusterService clusterService, AuditLog auditLog) { - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + private ConfigurationRepository( + Settings settings, + final Path configPath, + ThreadPool threadPool, + Client client, + ClusterService clusterService, + AuditLog auditLog + ) { + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.settings = settings; this.client = client; this.threadPool = threadPool; @@ -107,45 +116,90 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread this.acceptInvalid = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false); cl = new ConfigurationLoaderSecurity7(client, threadPool, settings, clusterService); - configCache = CacheBuilder - .newBuilder() - .build(); + configCache = CacheBuilder.newBuilder().build(); bgThread = new Thread(() -> { try { - LOGGER.info("Background init thread started. Install default config?: "+installDefaultConfig.get()); + LOGGER.info("Background init thread started. Install default config?: " + installDefaultConfig.get()); // wait for the cluster here until it will finish managed node election while (clusterService.state().blocks().hasGlobalBlockWithStatus(RestStatus.SERVICE_UNAVAILABLE)) { LOGGER.info("Wait for cluster to be available ..."); TimeUnit.SECONDS.sleep(1); } - if(installDefaultConfig.get()) { + if (installDefaultConfig.get()) { try { String lookupDir = System.getProperty("security.default_init.dir"); - final String cd = lookupDir != null? (lookupDir+"/") : new Environment(settings, configPath).configDir().toAbsolutePath().toString()+"/opensearch-security/"; - File confFile = new File(cd+"config.yml"); - if(confFile.exists()) { + final String cd = lookupDir != null + ? (lookupDir + "/") + : new Environment(settings, configPath).configDir().toAbsolutePath().toString() + "/opensearch-security/"; + File confFile = new File(cd + "config.yml"); + if (confFile.exists()) { final ThreadContext threadContext = threadPool.getThreadContext(); - try(StoredContext ctx = threadContext.stashContext()) { + try (StoredContext ctx = threadContext.stashContext()) { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); createSecurityIndexIfAbsent(); waitForSecurityIndexToBeAtLeastYellow(); - ConfigHelper.uploadFile(client, cd+"config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"roles_mapping.yml", securityIndex, CType.ROLESMAPPING, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"internal_users.yml", securityIndex, CType.INTERNALUSERS, DEFAULT_CONFIG_VERSION); - ConfigHelper.uploadFile(client, cd+"action_groups.yml", securityIndex, CType.ACTIONGROUPS, DEFAULT_CONFIG_VERSION); - if(DEFAULT_CONFIG_VERSION == 2) { - ConfigHelper.uploadFile(client, cd+"tenants.yml", securityIndex, CType.TENANTS, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd + "config.yml", securityIndex, CType.CONFIG, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile(client, cd + "roles.yml", securityIndex, CType.ROLES, DEFAULT_CONFIG_VERSION); + ConfigHelper.uploadFile( + client, + cd + "roles_mapping.yml", + securityIndex, + CType.ROLESMAPPING, + DEFAULT_CONFIG_VERSION + ); + ConfigHelper.uploadFile( + client, + cd + "internal_users.yml", + securityIndex, + CType.INTERNALUSERS, + DEFAULT_CONFIG_VERSION + ); + ConfigHelper.uploadFile( + client, + cd + "action_groups.yml", + securityIndex, + CType.ACTIONGROUPS, + DEFAULT_CONFIG_VERSION + ); + if (DEFAULT_CONFIG_VERSION == 2) { + ConfigHelper.uploadFile( + client, + cd + "tenants.yml", + securityIndex, + CType.TENANTS, + DEFAULT_CONFIG_VERSION + ); } final boolean populateEmptyIfFileMissing = true; - ConfigHelper.uploadFile(client, cd+"nodes_dn.yml", securityIndex, CType.NODESDN, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "whitelist.yml", securityIndex, CType.WHITELIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); - ConfigHelper.uploadFile(client, cd + "allowlist.yml", securityIndex, CType.ALLOWLIST, DEFAULT_CONFIG_VERSION, populateEmptyIfFileMissing); + ConfigHelper.uploadFile( + client, + cd + "nodes_dn.yml", + securityIndex, + CType.NODESDN, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); + ConfigHelper.uploadFile( + client, + cd + "whitelist.yml", + securityIndex, + CType.WHITELIST, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); + ConfigHelper.uploadFile( + client, + cd + "allowlist.yml", + securityIndex, + CType.ALLOWLIST, + DEFAULT_CONFIG_VERSION, + populateEmptyIfFileMissing + ); // audit.yml is not packaged by default final String auditConfigPath = cd + "audit.yml"; @@ -161,7 +215,7 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread } } - while(!dynamicConfigFactory.isInitialized()) { + while (!dynamicConfigFactory.isInitialized()) { try { LOGGER.debug("Try to load config ..."); reloadConfiguration(Arrays.asList(CType.values())); @@ -180,7 +234,10 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread final Set deprecatedAuditKeysInSettings = AuditConfig.getDeprecatedKeys(settings); if (!deprecatedAuditKeysInSettings.isEmpty()) { - LOGGER.warn("Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", deprecatedAuditKeysInSettings); + LOGGER.warn( + "Following keys {} are deprecated in opensearch settings. They will be removed in plugin v2.0.0.0", + deprecatedAuditKeysInSettings + ); } final boolean isAuditConfigDocPresentInIndex = cl.isAuditConfigDocPresentInIndex(); if (isAuditConfigDocPresentInIndex) { @@ -189,14 +246,16 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread } LOGGER.info("Hot-reloading of audit configuration is enabled"); } else { - LOGGER.info("Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it."); + LOGGER.info( + "Hot-reloading of audit configuration is disabled. Using configuration with defaults from opensearch settings. Populate the configuration in index using audit.yml or securityadmin to enable it." + ); auditLog.setConfig(AuditConfig.from(settings)); } LOGGER.info("Node '{}' initialized", clusterService.localNode().getName()); } catch (Exception e) { - LOGGER.error("Unexpected exception while initializing node "+e, e); + LOGGER.error("Unexpected exception while initializing node " + e, e); } }); @@ -204,17 +263,9 @@ private ConfigurationRepository(Settings settings, final Path configPath, Thread private boolean createSecurityIndexIfAbsent() { try { - final Map indexSettings = ImmutableMap.of( - "index.number_of_shards", 1, - "index.auto_expand_replicas", "0-all" - ); - final CreateIndexRequest createIndexRequest = new CreateIndexRequest(securityIndex) - .settings(indexSettings); - final boolean ok = client.admin() - .indices() - .create(createIndexRequest) - .actionGet() - .isAcknowledged(); + final Map indexSettings = ImmutableMap.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all"); + final CreateIndexRequest createIndexRequest = new CreateIndexRequest(securityIndex).settings(indexSettings); + final boolean ok = client.admin().indices().create(createIndexRequest).actionGet().isAcknowledged(); LOGGER.info("Index {} created?: {}", securityIndex, ok); return ok; } catch (ResourceAlreadyExistsException resourceAlreadyExistsException) { @@ -227,19 +278,24 @@ private void waitForSecurityIndexToBeAtLeastYellow() { LOGGER.info("Node started, try to initialize it. Wait for at least yellow cluster state...."); ClusterHealthResponse response = null; try { - response = client.admin().cluster().health(new ClusterHealthRequest(securityIndex) - .waitForActiveShards(1) - .waitForYellowStatus()).actionGet(); + response = client.admin() + .cluster() + .health(new ClusterHealthRequest(securityIndex).waitForActiveShards(1).waitForYellowStatus()) + .actionGet(); } catch (Exception e) { LOGGER.debug("Caught a {} but we just try again ...", e.toString()); } - while(response == null || response.isTimedOut() || response.getStatus() == ClusterHealthStatus.RED) { - LOGGER.debug("index '{}' not healthy yet, we try again ... (Reason: {})", securityIndex, response==null?"no response":(response.isTimedOut()?"timeout":"other, maybe red cluster")); + while (response == null || response.isTimedOut() || response.getStatus() == ClusterHealthStatus.RED) { + LOGGER.debug( + "index '{}' not healthy yet, we try again ... (Reason: {})", + securityIndex, + response == null ? "no response" : (response.isTimedOut() ? "timeout" : "other, maybe red cluster") + ); try { Thread.sleep(500); } catch (InterruptedException e) { - //ignore + // ignore Thread.currentThread().interrupt(); } try { @@ -256,13 +312,17 @@ public void initOnNodeStart() { LOGGER.info("Will attempt to create index {} and default configs if they are absent", securityIndex); installDefaultConfig.set(true); bgThread.start(); - } else if (settings.getAsBoolean(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)){ - LOGGER.info("Will not attempt to create index {} and default configs if they are absent. Use securityadmin to initialize cluster", - securityIndex); + } else if (settings.getAsBoolean(ConfigConstants.SECURITY_BACKGROUND_INIT_IF_SECURITYINDEX_NOT_EXIST, true)) { + LOGGER.info( + "Will not attempt to create index {} and default configs if they are absent. Use securityadmin to initialize cluster", + securityIndex + ); bgThread.start(); } else { - LOGGER.info("Will not attempt to create index {} and default configs if they are absent. Will not perform background initialization", - securityIndex); + LOGGER.info( + "Will not attempt to create index {} and default configs if they are absent. Will not perform background initialization", + securityIndex + ); } } catch (Throwable e2) { LOGGER.error("Error during node initialization: {}", e2, e2); @@ -274,9 +334,22 @@ public boolean isAuditHotReloadingEnabled() { return cl.isAuditConfigDocPresentInIndex(); } - public static ConfigurationRepository create(Settings settings, final Path configPath, final ThreadPool threadPool, - Client client, ClusterService clusterService, AuditLog auditLog) { - final ConfigurationRepository repository = new ConfigurationRepository(settings, configPath, threadPool, client, clusterService, auditLog); + public static ConfigurationRepository create( + Settings settings, + final Path configPath, + final ThreadPool threadPool, + Client client, + ClusterService clusterService, + AuditLog auditLog + ) { + final ConfigurationRepository repository = new ConfigurationRepository( + settings, + configPath, + threadPool, + client, + clusterService, + auditLog + ); return repository; } @@ -290,8 +363,8 @@ public void setDynamicConfigFactory(DynamicConfigFactory dynamicConfigFactory) { * @return can also return empty in case it was never loaded */ public SecurityDynamicConfiguration getConfiguration(CType configurationType) { - SecurityDynamicConfiguration conf= configCache.getIfPresent(configurationType); - if(conf != null) { + SecurityDynamicConfiguration conf = configCache.getIfPresent(configurationType); + if (conf != null) { return conf.deepClone(); } return SecurityDynamicConfiguration.empty(); @@ -316,7 +389,6 @@ public void reloadConfiguration(Collection configTypes) throws ConfigUpda } } - private void reloadConfiguration0(Collection configTypes, boolean acceptInvalid) { final Map> loaded = getConfigurationsFromIndex(configTypes, false, acceptInvalid); configCache.putAll(loaded); @@ -333,7 +405,7 @@ private synchronized void notifyAboutChanges(Map> getConfigurationsFromIndex(Collection configTypes, boolean logComplianceEvent) { + public Map> getConfigurationsFromIndex( + Collection configTypes, + boolean logComplianceEvent + ) { return getConfigurationsFromIndex(configTypes, logComplianceEvent, this.acceptInvalid); } - public Map> getConfigurationsFromIndex(Collection configTypes, boolean logComplianceEvent, boolean acceptInvalid) { + public Map> getConfigurationsFromIndex( + Collection configTypes, + boolean logComplianceEvent, + boolean acceptInvalid + ) { final ThreadContext threadContext = threadPool.getThreadContext(); final Map> retVal = new HashMap<>(); - try(StoredContext ctx = threadContext.stashContext()) { + try (StoredContext ctx = threadContext.stashContext()) { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); IndexMetadata securityMetadata = clusterService.state().metadata().index(this.securityIndex); - MappingMetadata mappingMetadata = securityMetadata==null?null:securityMetadata.mapping(); + MappingMetadata mappingMetadata = securityMetadata == null ? null : securityMetadata.mapping(); if (securityMetadata != null && mappingMetadata != null) { - if("security".equals(mappingMetadata.type())) { + if ("security".equals(mappingMetadata.type())) { LOGGER.debug("security index exists and was created before ES 7 (legacy layout)"); } else { LOGGER.debug("security index exists and was created with ES 7 (new layout)"); } - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); - + retVal.putAll( + validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size()) + ); } else { - //wait (and use new layout) + // wait (and use new layout) LOGGER.debug("security index not exists (yet)"); - retVal.putAll(validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size())); + retVal.putAll( + validate(cl.load(configTypes.toArray(new CType[0]), 10, TimeUnit.SECONDS, acceptInvalid), configTypes.size()) + ); } } catch (Exception e) { @@ -389,9 +471,10 @@ public Map> getConfigurationsFromIndex(Co return retVal; } - private Map> validate(Map> conf, int expectedSize) throws InvalidConfigException { + private Map> validate(Map> conf, int expectedSize) + throws InvalidConfigException { - if(conf == null || conf.size() != expectedSize) { + if (conf == null || conf.size() != expectedSize) { throw new InvalidConfigException("Retrieved only partial configuration"); } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java index 4bb27a6cbb..fa1c4989e0 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java @@ -68,19 +68,32 @@ public class DlsFilterLevelActionHandler { private static final Logger log = LogManager.getLogger(DlsFilterLevelActionHandler.class); - private static final Function LOCAL_CLUSTER_ALIAS_GETTER = ReflectiveAttributeAccessors - .protectedObjectAttr("localClusterAlias", String.class); - - public static boolean handle(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved, Client nodeClient, ClusterService clusterService, IndicesService indicesService, - IndexNameExpressionResolver resolver, DlsQueryParser dlsQueryParser, ThreadContext threadContext) { + private static final Function LOCAL_CLUSTER_ALIAS_GETTER = ReflectiveAttributeAccessors.protectedObjectAttr( + "localClusterAlias", + String.class + ); + + public static boolean handle( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved, + Client nodeClient, + ClusterService clusterService, + IndicesService indicesService, + IndexNameExpressionResolver resolver, + DlsQueryParser dlsQueryParser, + ThreadContext threadContext + ) { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE) != null) { return true; } - if (action.startsWith("cluster:") || action.startsWith("indices:admin/template/") - || action.startsWith("indices:admin/index_template/")) { + if (action.startsWith("cluster:") + || action.startsWith("indices:admin/template/") + || action.startsWith("indices:admin/index_template/")) { return true; } @@ -98,8 +111,19 @@ public static boolean handle(String action, ActionRequest request, ActionListene return true; } - return new DlsFilterLevelActionHandler(action, request, listener, evaluatedDlsFlsConfig, resolved, nodeClient, clusterService, indicesService, - resolver, dlsQueryParser, threadContext).handle(); + return new DlsFilterLevelActionHandler( + action, + request, + listener, + evaluatedDlsFlsConfig, + resolved, + nodeClient, + clusterService, + indicesService, + resolver, + dlsQueryParser, + threadContext + ).handle(); } private final String action; @@ -117,9 +141,19 @@ public static boolean handle(String action, ActionRequest request, ActionListene private BoolQueryBuilder filterLevelQueryBuilder; private DocumentAllowList documentAllowlist; - DlsFilterLevelActionHandler(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved, Client nodeClient, ClusterService clusterService, IndicesService indicesService, - IndexNameExpressionResolver resolver, DlsQueryParser dlsQueryParser, ThreadContext threadContext) { + DlsFilterLevelActionHandler( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved, + Client nodeClient, + ClusterService clusterService, + IndicesService indicesService, + IndexNameExpressionResolver resolver, + DlsQueryParser dlsQueryParser, + ThreadContext threadContext + ) { this.action = action; this.request = request; this.listener = listener; @@ -170,8 +204,11 @@ private boolean handle() { return handle((ClusterSearchShardsRequest) request, ctx); } else { log.error("Unsupported request type for filter level DLS: " + request); - listener.onFailure(new OpenSearchSecurityException( - "Unsupported request type for filter level DLS: " + action + "; " + request.getClass().getName())); + listener.onFailure( + new OpenSearchSecurityException( + "Unsupported request type for filter level DLS: " + action + "; " + request.getClass().getName() + ) + ); return false; } } @@ -230,7 +267,9 @@ private boolean handle(GetRequest getRequest, StoredContext ctx) { } SearchRequest searchRequest = new SearchRequest(getRequest.indices()); - BoolQueryBuilder query = QueryBuilders.boolQuery().must(QueryBuilders.idsQuery().addIds(getRequest.id())).must(filterLevelQueryBuilder); + BoolQueryBuilder query = QueryBuilders.boolQuery() + .must(QueryBuilders.idsQuery().addIds(getRequest.id())) + .must(filterLevelQueryBuilder); searchRequest.source(SearchSourceBuilder.searchSource().query(query)); nodeClient.search(searchRequest, new ActionListener() { @@ -247,8 +286,21 @@ public void onResponse(SearchResponse response) { if (hits == 1) { getListener.onResponse(new GetResponse(searchHitToGetResult(response.getHits().getAt(0)))); } else if (hits == 0) { - getListener.onResponse(new GetResponse(new GetResult(searchRequest.indices()[0], getRequest.id(), - SequenceNumbers.UNASSIGNED_SEQ_NO, SequenceNumbers.UNASSIGNED_PRIMARY_TERM, -1, false, null, null, null))); + getListener.onResponse( + new GetResponse( + new GetResult( + searchRequest.indices()[0], + getRequest.id(), + SequenceNumbers.UNASSIGNED_SEQ_NO, + SequenceNumbers.UNASSIGNED_PRIMARY_TERM, + -1, + false, + null, + null, + null + ) + ) + ); } else { log.error("Unexpected hit count " + hits + " in " + response); listener.onFailure(new OpenSearchSecurityException("Internal error when performing DLS")); @@ -274,8 +326,9 @@ private boolean handle(MultiGetRequest multiGetRequest, StoredContext ctx) { documentAllowlist.applyTo(threadContext); } - Map> idsGroupedByIndex = multiGetRequest.getItems().stream() - .collect(Collectors.groupingBy((item) -> item.index(), Collectors.mapping((item) -> item.id(), Collectors.toSet()))); + Map> idsGroupedByIndex = multiGetRequest.getItems() + .stream() + .collect(Collectors.groupingBy((item) -> item.index(), Collectors.mapping((item) -> item.id(), Collectors.toSet()))); Set indices = idsGroupedByIndex.keySet(); SearchRequest searchRequest = new SearchRequest(indices.toArray(new String[indices.size()])); @@ -283,14 +336,16 @@ private boolean handle(MultiGetRequest multiGetRequest, StoredContext ctx) { if (indices.size() == 1) { Set ids = idsGroupedByIndex.get(indices.iterator().next()); - query = QueryBuilders.boolQuery().must(QueryBuilders.idsQuery().addIds(ids.toArray(new String[ids.size()]))) - .must(filterLevelQueryBuilder); + query = QueryBuilders.boolQuery() + .must(QueryBuilders.idsQuery().addIds(ids.toArray(new String[ids.size()]))) + .must(filterLevelQueryBuilder); } else { BoolQueryBuilder mgetQuery = QueryBuilders.boolQuery().minimumShouldMatch(1); for (Map.Entry> entry : idsGroupedByIndex.entrySet()) { - BoolQueryBuilder indexQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", entry.getKey())) - .must(QueryBuilders.idsQuery().addIds(entry.getValue().toArray(new String[entry.getValue().size()]))); + BoolQueryBuilder indexQuery = QueryBuilders.boolQuery() + .must(QueryBuilders.termQuery("_index", entry.getKey())) + .must(QueryBuilders.idsQuery().addIds(entry.getValue().toArray(new String[entry.getValue().size()]))); mgetQuery.should(indexQuery); } @@ -315,7 +370,9 @@ public void onResponse(SearchResponse response) { @SuppressWarnings("unchecked") ActionListener multiGetListener = (ActionListener) listener; - multiGetListener.onResponse(new MultiGetResponse(itemResponses.toArray(new MultiGetItemResponse[itemResponses.size()]))); + multiGetListener.onResponse( + new MultiGetResponse(itemResponses.toArray(new MultiGetItemResponse[itemResponses.size()])) + ); } catch (Exception e) { listener.onFailure(e); } @@ -332,8 +389,11 @@ public void onFailure(Exception e) { } private boolean handle(ClusterSearchShardsRequest request, StoredContext ctx) { - listener.onFailure(new OpenSearchSecurityException( - "Filter-level DLS via cross cluster search is not available for scrolling and minimize_roundtrips=true")); + listener.onFailure( + new OpenSearchSecurityException( + "Filter-level DLS via cross cluster search is not available for scrolling and minimize_roundtrips=true" + ) + ); return false; } @@ -373,9 +433,14 @@ private GetResult searchHitToGetResult(SearchHit hit) { } else { if (log.isWarnEnabled()) { - log.warn("Could not find IndexService for " + hit.getIndex() + "; assuming all fields as document fields." + log.warn( + "Could not find IndexService for " + + hit.getIndex() + + "; assuming all fields as document fields." + "This should not happen, however this should also not pose a big problem as ES mixes the fields again anyway.\n" - + "IndexMetadata: " + indexMetadata); + + "IndexMetadata: " + + indexMetadata + ); } documentFields = fields; @@ -383,8 +448,17 @@ private GetResult searchHitToGetResult(SearchHit hit) { } } - return new GetResult(hit.getIndex(), hit.getId(), hit.getSeqNo(), hit.getPrimaryTerm(), hit.getVersion(), true, hit.getSourceRef(), - documentFields, metadataFields); + return new GetResult( + hit.getIndex(), + hit.getId(), + hit.getSeqNo(), + hit.getPrimaryTerm(), + hit.getVersion(), + true, + hit.getSourceRef(), + documentFields, + metadataFields + ); } private boolean modifyQuery() throws IOException { @@ -441,11 +515,15 @@ private boolean modifyQuery(String localClusterAlias) throws IOException { dlsQueryBuilder.should(parsedDlsQuery); } else { // The original request referred to several indices. That's why we have to scope each query to the index it is meant for - dlsQueryBuilder.should(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", prefixedIndex)).must(parsedDlsQuery)); + dlsQueryBuilder.should( + QueryBuilders.boolQuery().must(QueryBuilders.termQuery("_index", prefixedIndex)).must(parsedDlsQuery) + ); } - Set queryBuilders = QueryBuilderTraverser.findAll(parsedDlsQuery, - (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null); + Set queryBuilders = QueryBuilderTraverser.findAll( + parsedDlsQuery, + (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null + ); for (QueryBuilder queryBuilder : queryBuilders) { TermsQueryBuilder termsQueryBuilder = (TermsQueryBuilder) queryBuilder; diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java index d849e6d999..7100be35e5 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsFilterLeafReader.java @@ -85,7 +85,7 @@ import org.opensearch.security.support.SecurityUtils; import org.opensearch.security.support.WildcardMatcher; -class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { +class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { private static final String KEYWORD = ".keyword"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; @@ -108,11 +108,18 @@ class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { private DlsGetEvaluator dge = null; - - DlsFlsFilterLeafReader(final LeafReader delegate, final Set includesExcludes, - final Query dlsQuery, final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, final ShardId shardId, final Salt salt) { + DlsFlsFilterLeafReader( + final LeafReader delegate, + final Set includesExcludes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + final ShardId shardId, + final Salt salt + ) { super(delegate); maskFields = (maskedFields != null && maskedFields.size() > 0); @@ -199,8 +206,6 @@ class DlsFlsFilterLeafReader extends SequentialStoredFieldsLeafReader { System.arraycopy(fa, 0, tmp, 0, i); this.flsFieldInfos = new FieldInfos(tmp); - - } else { this.includesSet = null; this.excludesSet = null; @@ -221,9 +226,9 @@ private class DlsGetEvaluator { private final boolean hasDeletions; public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyDlsHere) throws IOException { - if(dlsQuery != null && applyDlsHere) { - //borrowed from Apache Lucene (Copyright Apache Software Foundation (ASF)) - //https://github.com/apache/lucene-solr/blob/branch_6_3/lucene/misc/src/java/org/apache/lucene/index/PKIndexSplitter.java + if (dlsQuery != null && applyDlsHere) { + // borrowed from Apache Lucene (Copyright Apache Software Foundation (ASF)) + // https://github.com/apache/lucene-solr/blob/branch_6_3/lucene/misc/src/java/org/apache/lucene/index/PKIndexSplitter.java final IndexSearcher searcher = new IndexSearcher(DlsFlsFilterLeafReader.this); searcher.setQueryCache(null); final Weight preserveWeight = searcher.createWeight(dlsQuery, ScoreMode.COMPLETE_NO_SCORES, 1f); @@ -253,7 +258,7 @@ public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyD hasDeletions = true; } else { - //no dls or handled in a different place + // no dls or handled in a different place liveBits = in.getLiveDocs(); numDocs = in.numDocs(); readerCacheHelper = in.getReaderCacheHelper(); @@ -261,7 +266,7 @@ public DlsGetEvaluator(final Query dlsQuery, final LeafReader in, boolean applyD } } - //return null means no hidden docs + // return null means no hidden docs public Bits getLiveDocs() { return liveBits; } @@ -288,19 +293,18 @@ private MaskedFieldsMap(Map maskedFieldsMap) { public static MaskedFieldsMap extractMaskedFields(boolean maskFields, Set maskedFields, final Salt salt) { if (maskFields) { - return new MaskedFieldsMap(maskedFields.stream() - .map(mf -> new MaskedField(mf, salt)) - .collect(ImmutableMap.toImmutableMap(mf -> WildcardMatcher.from(mf.getName()), Function.identity()))); + return new MaskedFieldsMap( + maskedFields.stream() + .map(mf -> new MaskedField(mf, salt)) + .collect(ImmutableMap.toImmutableMap(mf -> WildcardMatcher.from(mf.getName()), Function.identity())) + ); } else { return new MaskedFieldsMap(Collections.emptyMap()); } } public Optional getMaskedField(String fieldName) { - return maskedFieldsMap.entrySet().stream() - .filter(entry -> entry.getKey().test(fieldName)) - .map(Map.Entry::getValue) - .findFirst(); + return maskedFieldsMap.entrySet().stream().filter(entry -> entry.getKey().test(fieldName)).map(Map.Entry::getValue).findFirst(); } public boolean anyMatch(String fieldName) { @@ -311,7 +315,6 @@ public WildcardMatcher getMatcher() { return WildcardMatcher.from(maskedFieldsMap.keySet()); } - } private static class DlsFlsSubReaderWrapper extends FilterDirectoryReader.SubReaderWrapper { @@ -326,10 +329,17 @@ private static class DlsFlsSubReaderWrapper extends FilterDirectoryReader.SubRea private final ShardId shardId; private final Salt salt; - public DlsFlsSubReaderWrapper(final Set includes, final Query dlsQuery, - final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, ShardId shardId, final Salt salt) { + public DlsFlsSubReaderWrapper( + final Set includes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + ShardId shardId, + final Salt salt + ) { this.includes = includes; this.dlsQuery = dlsQuery; this.indexService = indexService; @@ -343,7 +353,18 @@ public DlsFlsSubReaderWrapper(final Set includes, final Query dlsQuery, @Override public LeafReader wrap(final LeafReader reader) { - return new DlsFlsFilterLeafReader(reader, includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsFilterLeafReader( + reader, + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } } @@ -360,11 +381,32 @@ static class DlsFlsDirectoryReader extends FilterDirectoryReader { private final ShardId shardId; private final Salt salt; - public DlsFlsDirectoryReader(final DirectoryReader in, final Set includes, final Query dlsQuery, - final IndexService indexService, final ThreadContext threadContext, - final ClusterService clusterService, - final AuditLog auditlog, final Set maskedFields, ShardId shardId, final Salt salt) throws IOException { - super(in, new DlsFlsSubReaderWrapper(includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt)); + public DlsFlsDirectoryReader( + final DirectoryReader in, + final Set includes, + final Query dlsQuery, + final IndexService indexService, + final ThreadContext threadContext, + final ClusterService clusterService, + final AuditLog auditlog, + final Set maskedFields, + ShardId shardId, + final Salt salt + ) throws IOException { + super( + in, + new DlsFlsSubReaderWrapper( + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ) + ); this.includes = includes; this.dlsQuery = dlsQuery; this.indexService = indexService; @@ -378,7 +420,18 @@ public DlsFlsDirectoryReader(final DirectoryReader in, final Set include @Override protected DirectoryReader doWrapDirectoryReader(final DirectoryReader in) throws IOException { - return new DlsFlsDirectoryReader(in, includes, dlsQuery, indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsDirectoryReader( + in, + includes, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } @Override @@ -467,7 +520,7 @@ private boolean isFls(final BytesRef termAsFiledName) { private boolean isFls(final String name) { - if(!flsEnabled) { + if (!flsEnabled) { return true; } @@ -477,7 +530,7 @@ private boolean isFls(final String name) { @Override public FieldInfos getFieldInfos() { - if(!flsEnabled) { + if (!flsEnabled) { return in.getFieldInfos(); } @@ -487,8 +540,14 @@ public FieldInfos getFieldInfos() { private class ComplianceAwareStoredFieldVisitor extends StoredFieldVisitor { private final StoredFieldVisitor delegate; - private FieldReadCallback fieldReadCallback = - new FieldReadCallback(threadContext, indexService, clusterService, auditlog, maskedFieldsMap.getMatcher(), shardId); + private FieldReadCallback fieldReadCallback = new FieldReadCallback( + threadContext, + indexService, + clusterService, + auditlog, + maskedFieldsMap.getMatcher(), + shardId + ); public ComplianceAwareStoredFieldVisitor(final StoredFieldVisitor delegate) { super(); @@ -501,7 +560,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO delegate.binaryField(fieldInfo, value); } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return delegate.needsField(fieldInfo); @@ -584,7 +642,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO } } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return isFls(fieldInfo.name) ? delegate.needsField(fieldInfo) : Status.NO; @@ -640,7 +697,11 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO if (fieldInfo.name.equals("_source")) { final BytesReference bytesRef = new BytesArray(value); - final Tuple> bytesRefTuple = XContentHelper.convertToMap(bytesRef, false, XContentType.JSON); + final Tuple> bytesRefTuple = XContentHelper.convertToMap( + bytesRef, + false, + XContentType.JSON + ); Map filteredSource = bytesRefTuple.v2(); MapUtils.deepTraverseMap(filteredSource, HASH_CB); final XContentBuilder xBuilder = XContentBuilder.builder(bytesRefTuple.v1().xContent()).map(filteredSource); @@ -650,7 +711,6 @@ public void binaryField(final FieldInfo fieldInfo, final byte[] value) throws IO } } - @Override public Status needsField(final FieldInfo fieldInfo) throws IOException { return delegate.needsField(fieldInfo); @@ -745,7 +805,7 @@ public Fields getTermVectors(final int docID) throws IOException { @Override public Iterator iterator() { - return Iterators. filter(fields.iterator(), input -> isFls(input)); + return Iterators.filter(fields.iterator(), input -> isFls(input)); } @Override @@ -781,7 +841,7 @@ private BinaryDocValues wrapBinaryDocValues(final String field, final BinaryDocV final MaskedFieldsMap maskedFieldsMap; - if (binaryDocValues != null && ((maskedFieldsMap=getRuntimeMaskedFieldInfo()) != null)) { + if (binaryDocValues != null && ((maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null)) { final MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); if (mf != null) { @@ -822,7 +882,6 @@ public BytesRef binaryValue() throws IOException { return binaryDocValues; } - @Override public SortedDocValues getSortedDocValues(final String field) throws IOException { return isFls(field) ? wrapSortedDocValues(field, in.getSortedDocValues(field)) : null; @@ -832,7 +891,7 @@ private SortedDocValues wrapSortedDocValues(final String field, final SortedDocV final MaskedFieldsMap maskedFieldsMap; - if (sortedDocValues != null && (maskedFieldsMap=getRuntimeMaskedFieldInfo())!=null) { + if (sortedDocValues != null && (maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null) { final MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); if (mf != null) { @@ -843,7 +902,6 @@ public int lookupTerm(BytesRef key) throws IOException { return sortedDocValues.lookupTerm(key); } - @Override public TermsEnum termsEnum() throws IOException { return new MaskedTermsEnum(sortedDocValues.termsEnum(), mf); @@ -913,7 +971,6 @@ private SortedSetDocValues wrapSortedSetDocValues(final String field, final Sort final MaskedFieldsMap maskedFieldsMap; - if (sortedSetDocValues != null && ((maskedFieldsMap = getRuntimeMaskedFieldInfo()) != null)) { MaskedField mf = maskedFieldsMap.getMaskedField(handleKeyword(field)).orElse(null); @@ -1002,16 +1059,16 @@ public Terms terms(String field) throws IOException { private Terms wrapTerms(final String field, Terms terms) throws IOException { - if(terms == null) { + if (terms == null) { return null; } MaskedFieldsMap maskedFieldInfo = getRuntimeMaskedFieldInfo(); - if(maskedFieldInfo != null && maskedFieldInfo.anyMatch(handleKeyword(field))) { + if (maskedFieldInfo != null && maskedFieldInfo.anyMatch(handleKeyword(field))) { return null; } - if("_field_names".equals(field)) { + if ("_field_names".equals(field)) { return new FilteredTerms(terms); } return terms; @@ -1024,8 +1081,8 @@ public FilteredTermsEnum(TermsEnum delegate) { @Override public BytesRef next() throws IOException { - //wind forward in the sequence of terms until we reached the end or we find a allowed term(=field name) - //so that calling this method never return a term which is not allowed by fls rules + // wind forward in the sequence of terms until we reached the end or we find a allowed term(=field name) + // so that calling this method never return a term which is not allowed by fls rules for (BytesRef nextBytesRef = in.next(); nextBytesRef != null; nextBytesRef = in.next()) { if (!isFls((nextBytesRef))) { continue; @@ -1038,20 +1095,20 @@ public BytesRef next() throws IOException { @Override public SeekStatus seekCeil(BytesRef text) throws IOException { - //Get the current seek status for a given term in the original sequence of terms + // Get the current seek status for a given term in the original sequence of terms final SeekStatus delegateStatus = in.seekCeil(text); - //So delegateStatus here is either FOUND or NOT_FOUND - //check if the current term (=field name) is allowed - //If so just return current seek status + // So delegateStatus here is either FOUND or NOT_FOUND + // check if the current term (=field name) is allowed + // If so just return current seek status if (delegateStatus != SeekStatus.END && isFls((in.term()))) { return delegateStatus; } else if (delegateStatus == SeekStatus.END) { - //If we hit the end just return END + // If we hit the end just return END return SeekStatus.END; } else { - //If we are not at the end and the current term (=field name) is not allowed just check if - //we are at the end of the (filtered) iterator + // If we are not at the end and the current term (=field name) is not allowed just check if + // we are at the end of the (filtered) iterator if (this.next() != null) { return SeekStatus.NOT_FOUND; } else { @@ -1060,7 +1117,6 @@ public SeekStatus seekCeil(BytesRef text) throws IOException { } } - @Override public boolean seekExact(BytesRef term) throws IOException { return isFls(term) && in.seekExact(term); @@ -1079,12 +1135,15 @@ public long ord() throws IOException { private final class FilteredTerms extends FilterTerms { - //According to - //https://www.elastic.co/guide/en/elasticsearch/reference/6.8/mapping-field-names-field.html - //"The _field_names field used to index the names of every field in a document that contains any value other than null" - //"For fields which have either doc_values or norm enabled the exists query will still be available but will not use the _field_names field." - //That means if a field has no doc values (which is always the case for an analyzed string) and no norms we need to strip the non allowed fls fields - //from the _field_names field. They are stored as terms, so we need to create a FilterTerms implementation which skips the terms (=field names)not allowed by fls + // According to + // https://www.elastic.co/guide/en/elasticsearch/reference/6.8/mapping-field-names-field.html + // "The _field_names field used to index the names of every field in a document that contains any value other than null" + // "For fields which have either doc_values or norm enabled the exists query will still be available but will not use the + // _field_names field." + // That means if a field has no doc values (which is always the case for an analyzed string) and no norms we need to strip the non + // allowed fls fields + // from the _field_names field. They are stored as terms, so we need to create a FilterTerms implementation which skips the terms + // (=field names)not allowed by fls public FilteredTerms(Terms delegate) throws IOException { super(delegate); @@ -1123,13 +1182,15 @@ public boolean hasDeletions() { @SuppressWarnings("unchecked") private MaskedFieldsMap getRuntimeMaskedFieldInfo() { - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String maskedEval = SecurityUtils.evalMap(maskedFieldsMap, indexService.index().getName()); - if(maskedEval != null) { + if (maskedEval != null) { final Set mf = maskedFieldsMap.get(maskedEval); - if(mf != null && !mf.isEmpty()) { + if (mf != null && !mf.isEmpty()) { return MaskedFieldsMap.extractMaskedFields(true, mf, salt); } @@ -1139,8 +1200,8 @@ private MaskedFieldsMap getRuntimeMaskedFieldInfo() { } private String handleKeyword(final String field) { - if(field != null && field.endsWith(KEYWORD)) { - return field.substring(0, field.length()-KEYWORD.length()); + if (field != null && field.endsWith(KEYWORD)) { + return field.substring(0, field.length() - KEYWORD.length()); } return field; } @@ -1158,7 +1219,7 @@ public MaskedTermsEnum(TermsEnum delegate, MaskedField mf) { @Override public BytesRef next() throws IOException { - return delegate.next(); //no masking here + return delegate.next(); // no masking here } @Override @@ -1223,7 +1284,6 @@ public TermState termState() throws IOException { } - private String getRuntimeActionName() { return (String) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ACTION_NAME); } @@ -1233,16 +1293,15 @@ private boolean isSuggest() { } private boolean applyDlsHere() { - if(isSuggest()) { - //we need to apply it here + if (isSuggest()) { + // we need to apply it here return true; } - final String action = getRuntimeActionName(); assert action != null; - //we need to apply here if it is not a search request - //(a get for example) + // we need to apply here if it is not a search request + // (a get for example) return !action.startsWith("indices:data/read/search"); } } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java index f5751efcae..9bce6564dc 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsRequestValve.java @@ -37,7 +37,13 @@ public interface DlsFlsRequestValve { - boolean invoke(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, Resolved resolved); + boolean invoke( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved + ); void handleSearchContext(SearchContext context, ThreadPool threadPool, NamedXContentRegistry namedXContentRegistry); @@ -45,9 +51,14 @@ public interface DlsFlsRequestValve { public static class NoopDlsFlsRequestValve implements DlsFlsRequestValve { - @Override - public boolean invoke(String action, ActionRequest request, ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - Resolved resolved) { + @Override + public boolean invoke( + String action, + ActionRequest request, + ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + Resolved resolved + ) { return true; } diff --git a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java index 947557d342..81fe9e255f 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFlsValveImpl.java @@ -82,8 +82,8 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve { - private static final String MAP_EXECUTION_HINT = "map"; - private static final Logger log = LogManager.getLogger(DlsFlsValveImpl.class); + private static final String MAP_EXECUTION_HINT = "map"; + private static final Logger log = LogManager.getLogger(DlsFlsValveImpl.class); private final Client nodeClient; private final ClusterService clusterService; @@ -92,8 +92,14 @@ public class DlsFlsValveImpl implements DlsFlsRequestValve { private final DlsQueryParser dlsQueryParser; private final IndexNameExpressionResolver resolver; - public DlsFlsValveImpl(Settings settings, Client nodeClient, ClusterService clusterService, IndexNameExpressionResolver resolver, - NamedXContentRegistry namedXContentRegistry, ThreadContext threadContext) { + public DlsFlsValveImpl( + Settings settings, + Client nodeClient, + ClusterService clusterService, + IndexNameExpressionResolver resolver, + NamedXContentRegistry namedXContentRegistry, + ThreadContext threadContext + ) { super(); this.nodeClient = nodeClient; this.clusterService = clusterService; @@ -109,12 +115,25 @@ public DlsFlsValveImpl(Settings settings, Client nodeClient, ClusterService clus * @param listener * @return false on error */ - public boolean invoke(String action, ActionRequest request, final ActionListener listener, EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, - final Resolved resolved) { + public boolean invoke( + String action, + ActionRequest request, + final ActionListener listener, + EvaluatedDlsFlsConfig evaluatedDlsFlsConfig, + final Resolved resolved + ) { if (log.isDebugEnabled()) { - log.debug("DlsFlsValveImpl.invoke()\nrequest: " + request + "\nevaluatedDlsFlsConfig: " + evaluatedDlsFlsConfig + "\nresolved: " - + resolved + "\nmode: " + mode); + log.debug( + "DlsFlsValveImpl.invoke()\nrequest: " + + request + + "\nevaluatedDlsFlsConfig: " + + evaluatedDlsFlsConfig + + "\nresolved: " + + resolved + + "\nmode: " + + mode + ); } if (evaluatedDlsFlsConfig == null || evaluatedDlsFlsConfig.isEmpty()) { @@ -173,10 +192,10 @@ public boolean invoke(String action, ActionRequest request, final ActionListener SearchRequest searchRequest = ((SearchRequest) request); - //When we encounter a terms or sampler aggregation with masked fields activated we forcibly - //need to switch off global ordinals because field masking can break ordering + // When we encounter a terms or sampler aggregation with masked fields activated we forcibly + // need to switch off global ordinals because field masking can break ordering // CS-SUPPRESS-SINGLE: RegexpSingleline Ignore term inside of url - //https://www.elastic.co/guide/en/elasticsearch/reference/master/eager-global-ordinals.html#_avoiding_global_ordinal_loading + // https://www.elastic.co/guide/en/elasticsearch/reference/master/eager-global-ordinals.html#_avoiding_global_ordinal_loading // CS-ENFORCE-SINGLE if (evaluatedDlsFlsConfig.hasFieldMasking()) { @@ -197,8 +216,7 @@ public boolean invoke(String action, ActionRequest request, final ActionListener } } - if (!evaluatedDlsFlsConfig.hasFls() && !evaluatedDlsFlsConfig.hasDls() - && searchRequest.source().aggregations() != null) { + if (!evaluatedDlsFlsConfig.hasFls() && !evaluatedDlsFlsConfig.hasDls() && searchRequest.source().aggregations() != null) { boolean cacheable = true; @@ -224,8 +242,11 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (!cacheable) { searchRequest.requestCache(Boolean.FALSE); } else { - LogManager.getLogger("debuglogger").error("Shard requestcache enabled for " - + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source()))); + LogManager.getLogger("debuglogger") + .error( + "Shard requestcache enabled for " + + (searchRequest.source() == null ? "" : Strings.toString(XContentType.JSON, searchRequest.source())) + ); } } else { @@ -241,7 +262,9 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (request instanceof BulkRequest) { for (DocWriteRequest inner : ((BulkRequest) request).requests()) { if (inner instanceof UpdateRequest) { - listener.onFailure(new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated")); + listener.onFailure( + new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated") + ); return false; } } @@ -250,7 +273,9 @@ public boolean invoke(String action, ActionRequest request, final ActionListener if (request instanceof BulkShardRequest) { for (BulkItemRequest inner : ((BulkShardRequest) request).items()) { if (inner.request() instanceof UpdateRequest) { - listener.onFailure(new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated")); + listener.onFailure( + new OpenSearchSecurityException("Update is not supported when FLS or DLS or Fieldmasking is activated") + ); return false; } } @@ -261,9 +286,13 @@ public boolean invoke(String action, ActionRequest request, final ActionListener return false; } - if(action.contains("plugins/replication")) { - listener.onFailure(new OpenSearchSecurityException("Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated", - RestStatus.FORBIDDEN)); + if (action.contains("plugins/replication")) { + listener.onFailure( + new OpenSearchSecurityException( + "Cross Cluster Replication is not supported when FLS or DLS or Fieldmasking is activated", + RestStatus.FORBIDDEN + ) + ); return false; } @@ -292,8 +321,19 @@ public boolean invoke(String action, ActionRequest request, final ActionListener } if (doFilterLevelDls && filteredDlsFlsConfig.hasDls()) { - return DlsFilterLevelActionHandler.handle(action, request, listener, evaluatedDlsFlsConfig, resolved, nodeClient, clusterService, - OpenSearchSecurityPlugin.GuiceHolder.getIndicesService(), resolver, dlsQueryParser, threadContext); + return DlsFilterLevelActionHandler.handle( + action, + request, + listener, + evaluatedDlsFlsConfig, + resolved, + nodeClient, + clusterService, + OpenSearchSecurityPlugin.GuiceHolder.getIndicesService(), + resolver, + dlsQueryParser, + threadContext + ); } else { return true; } @@ -303,8 +343,10 @@ public boolean invoke(String action, ActionRequest request, final ActionListener public void handleSearchContext(SearchContext context, ThreadPool threadPool, NamedXContentRegistry namedXContentRegistry) { try { @SuppressWarnings("unchecked") - final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader(threadPool.getThreadContext(), - ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER); + final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadPool.getThreadContext(), + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + ); final String dlsEval = SecurityUtils.evalMap(queries, context.indexShard().indexSettings().getIndex().getName()); @@ -319,8 +361,11 @@ public void handleSearchContext(SearchContext context, ThreadPool threadPool, Na final Set unparsedDlsQueries = queries.get(dlsEval); if (unparsedDlsQueries != null && !unparsedDlsQueries.isEmpty()) { - BooleanQuery.Builder queryBuilder = dlsQueryParser.parse(unparsedDlsQueries, context.getQueryShardContext(), - (q) -> new ConstantScoreQuery(q)); + BooleanQuery.Builder queryBuilder = dlsQueryParser.parse( + unparsedDlsQueries, + context.getQueryShardContext(), + (q) -> new ConstantScoreQuery(q) + ); queryBuilder.add(context.parsedQuery().query(), Occur.MUST); @@ -343,11 +388,11 @@ public void onQueryPhase(QuerySearchResult queryResult) { assert aggregations != null; queryResult.aggregations( - InternalAggregations.from( - StreamSupport.stream(aggregations.spliterator(), false) - .map(aggregation -> aggregateBuckets((InternalAggregation)aggregation)) - .collect(ImmutableList.toImmutableList()) - ) + InternalAggregations.from( + StreamSupport.stream(aggregations.spliterator(), false) + .map(aggregation -> aggregateBuckets((InternalAggregation) aggregation)) + .collect(ImmutableList.toImmutableList()) + ) ); } @@ -363,7 +408,10 @@ private static InternalAggregation aggregateBuckets(InternalAggregation aggregat return aggregation; } - private static List mergeBuckets(List buckets, Comparator comparator) { + private static List mergeBuckets( + List buckets, + Comparator comparator + ) { if (log.isDebugEnabled()) { log.debug("Merging buckets: {}", buckets.stream().map(b -> b.getKeyAsString()).collect(ImmutableList.toImmutableList())); } @@ -383,18 +431,28 @@ private void setDlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> dlsQueries = dlsFls.getDlsQueriesByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, Base64Helper.serializeObject((Serializable) dlsQueries)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, + Base64Helper.serializeObject((Serializable) dlsQueries) + ); if (log.isDebugEnabled()) { log.debug("added response header for DLS info: {}", dlsQueries); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER) != null) { - Object deserializedDlsQueries = Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER)); + Object deserializedDlsQueries = Base64Helper.deserializeObject( + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER) + ); if (!dlsQueries.equals(deserializedDlsQueries)) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + " does not match (SG 900D)"); + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + " does not match (SG 900D)" + ); } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, Base64Helper.serializeObject((Serializable) dlsQueries)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER, + Base64Helper.serializeObject((Serializable) dlsQueries) + ); if (log.isDebugEnabled()) { log.debug("attach DLS info: {}", dlsQueries); } @@ -408,7 +466,12 @@ private void setDlsModeHeader(Mode mode) { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER) != null) { if (!modeString.equals(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER))) { - log.warn("Cannot update DLS mode to " + mode + "; current: " + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER)); + log.warn( + "Cannot update DLS mode to " + + mode + + "; current: " + + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER) + ); } } else { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER, modeString); @@ -430,22 +493,32 @@ private void setFlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> maskedFieldsMap = dlsFls.getFieldMaskingByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, Base64Helper.serializeObject((Serializable) maskedFieldsMap)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, + Base64Helper.serializeObject((Serializable) maskedFieldsMap) + ); if (log.isDebugEnabled()) { log.debug("added response header for masked fields info: {}", maskedFieldsMap); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER) != null) { - if (!maskedFieldsMap.equals(Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER)))) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " does not match (SG 901D)"); + if (!maskedFieldsMap.equals( + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER)) + )) { + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " does not match (SG 901D)" + ); } else { if (log.isDebugEnabled()) { log.debug(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + " already set"); } } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, Base64Helper.serializeObject((Serializable) maskedFieldsMap)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER, + Base64Helper.serializeObject((Serializable) maskedFieldsMap) + ); if (log.isDebugEnabled()) { log.debug("attach masked fields info: {}", maskedFieldsMap); } @@ -457,22 +530,37 @@ private void setFlsHeaders(EvaluatedDlsFlsConfig dlsFls, ActionRequest request) Map> flsFields = dlsFls.getFlsByIndex(); if (request instanceof ClusterSearchShardsRequest && HeaderHelper.isTrustedClusterRequest(threadContext)) { - threadContext.addResponseHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, Base64Helper.serializeObject((Serializable) flsFields)); + threadContext.addResponseHeader( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, + Base64Helper.serializeObject((Serializable) flsFields) + ); if (log.isDebugEnabled()) { log.debug("added response header for FLS info: {}", flsFields); } } else { if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER) != null) { - if (!flsFields.equals(Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER)))) { - throw new OpenSearchSecurityException(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + " does not match (SG 901D) " + flsFields - + "---" + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER))); + if (!flsFields.equals( + Base64Helper.deserializeObject(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER)) + )) { + throw new OpenSearchSecurityException( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + + " does not match (SG 901D) " + + flsFields + + "---" + + Base64Helper.deserializeObject( + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER) + ) + ); } else { if (log.isDebugEnabled()) { log.debug(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + " already set"); } } } else { - threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, Base64Helper.serializeObject((Serializable) flsFields)); + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER, + Base64Helper.serializeObject((Serializable) flsFields) + ); if (log.isDebugEnabled()) { log.debug("attach FLS info: {}", flsFields); } @@ -500,9 +588,16 @@ private void finalizeBucket() { if (mergeCount == 1) { builder.add(this.bucket); } else { - builder.add(new StringTerms.Bucket(StringTermsGetter.getTerm(bucket), mergedDocCount, - (InternalAggregations) bucket.getAggregations(), showDocCountError, mergedDocCountError, - StringTermsGetter.getDocValueFormat(bucket))); + builder.add( + new StringTerms.Bucket( + StringTermsGetter.getTerm(bucket), + mergedDocCount, + (InternalAggregations) bucket.getAggregations(), + showDocCountError, + mergedDocCountError, + StringTermsGetter.getDocValueFormat(bucket) + ) + ); } } @@ -543,8 +638,7 @@ private static class StringTermsGetter { private static final Field TERM_BYTES = getField(StringTerms.Bucket.class, "termBytes"); private static final Field FORMAT = getField(InternalTerms.Bucket.class, "format"); - private StringTermsGetter() { - } + private StringTermsGetter() {} private static Field getFieldPrivileged(Class cls, String name) { try { @@ -569,7 +663,7 @@ private static Field getField(Class cls, String name) { private static T getFieldValue(Field field, C c) { try { - return (T)field.get(c); + return (T) field.get(c); } catch (IllegalArgumentException | IllegalAccessException e) { log.error("Exception while getting value {} of class {}", field.getName(), c.getClass().getSimpleName(), e); if (e instanceof RuntimeException) { @@ -594,7 +688,9 @@ public static DocValueFormat getDocValueFormat(StringTerms.Bucket bucket) { } public static enum Mode { - ADAPTIVE, LUCENE_LEVEL, FILTER_LEVEL; + ADAPTIVE, + LUCENE_LEVEL, + FILTER_LEVEL; static Mode get(Settings settings) { String modeString = settings.get(ConfigConstants.SECURITY_DLS_MODE); diff --git a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java index a5f07541c8..9640abcd8e 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java +++ b/src/main/java/org/opensearch/security/configuration/DlsQueryParser.java @@ -41,24 +41,28 @@ import org.opensearch.index.query.TermsQueryBuilder; import org.opensearch.security.queries.QueryBuilderTraverser; - public final class DlsQueryParser { private static final Logger log = LogManager.getLogger(DlsQueryParser.class); private static final Query NON_NESTED_QUERY; static { - //Match all documents but not the nested ones - //Nested document types start with __ - //https://discuss.elastic.co/t/whats-nested-documents-layout-inside-the-lucene/59944/9 + // Match all documents but not the nested ones + // Nested document types start with __ + // https://discuss.elastic.co/t/whats-nested-documents-layout-inside-the-lucene/59944/9 NON_NESTED_QUERY = new BooleanQuery.Builder().add(new MatchAllDocsQuery(), Occur.FILTER) - .add(new PrefixQuery(new Term("_type", "__")), Occur.MUST_NOT).build(); + .add(new PrefixQuery(new Term("_type", "__")), Occur.MUST_NOT) + .build(); } - private static Cache parsedQueryCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(4, TimeUnit.HOURS) - .build(); - private static Cache queryContainsTlqCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(4, TimeUnit.HOURS) - .build(); + private static Cache parsedQueryCache = CacheBuilder.newBuilder() + .maximumSize(10000) + .expireAfterWrite(4, TimeUnit.HOURS) + .build(); + private static Cache queryContainsTlqCache = CacheBuilder.newBuilder() + .maximumSize(10000) + .expireAfterWrite(4, TimeUnit.HOURS) + .build(); private final NamedXContentRegistry namedXContentRegistry; @@ -70,8 +74,11 @@ public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardCont return parse(unparsedDlsQueries, queryShardContext, null); } - public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardContext queryShardContext, - Function queryMapFunction) { + public BooleanQuery.Builder parse( + Set unparsedDlsQueries, + QueryShardContext queryShardContext, + Function queryMapFunction + ) { if (unparsedDlsQueries == null || unparsedDlsQueries.isEmpty()) { return null; @@ -100,8 +107,11 @@ public BooleanQuery.Builder parse(Set unparsedDlsQueries, QueryShardCont return dlsQueryBuilder; } - private static void handleNested(final QueryShardContext queryShardContext, final BooleanQuery.Builder dlsQueryBuilder, - final Query parentQuery) { + private static void handleNested( + final QueryShardContext queryShardContext, + final BooleanQuery.Builder dlsQueryBuilder, + final Query parentQuery + ) { final BitSetProducer parentDocumentsFilter = queryShardContext.bitsetFilter(NON_NESTED_QUERY); dlsQueryBuilder.add(new ToChildBlockJoinQuery(parentQuery, parentDocumentsFilter), Occur.SHOULD); } @@ -112,8 +122,11 @@ public QueryBuilder parse(String unparsedDlsQuery) { @Override public QueryBuilder call() throws Exception { - final XContentParser parser = JsonXContent.jsonXContent.createParser(namedXContentRegistry, - DeprecationHandler.THROW_UNSUPPORTED_OPERATION, unparsedDlsQuery); + final XContentParser parser = JsonXContent.jsonXContent.createParser( + namedXContentRegistry, + DeprecationHandler.THROW_UNSUPPORTED_OPERATION, + unparsedDlsQuery + ); return AbstractQueryBuilder.parseInnerQueryBuilder(parser); } @@ -143,18 +156,19 @@ boolean containsTermLookupQuery(Set unparsedQueries) { return false; } - boolean containsTermLookupQuery(String query) { + boolean containsTermLookupQuery(String query) { try { return queryContainsTlqCache.get(query, () -> { QueryBuilder queryBuilder = parse(query); - return QueryBuilderTraverser.exists(queryBuilder, - (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null); + return QueryBuilderTraverser.exists( + queryBuilder, + (q) -> (q instanceof TermsQueryBuilder) && ((TermsQueryBuilder) q).termsLookup() != null + ); }); } catch (ExecutionException e) { throw new RuntimeException("Error handling parsing " + query, e.getCause()); } } - } diff --git a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java index 79069ef53e..1a0460b91c 100644 --- a/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java +++ b/src/main/java/org/opensearch/security/configuration/EmptyFilterLeafReader.java @@ -83,6 +83,7 @@ public CacheHelper getCoreCacheHelper() { public CacheHelper getReaderCacheHelper() { return null; } + private static class EmptySubReaderWrapper extends FilterDirectoryReader.SubReaderWrapper { @Override diff --git a/src/main/java/org/opensearch/security/configuration/MaskedField.java b/src/main/java/org/opensearch/security/configuration/MaskedField.java index c04567eeef..8cb20ccdfe 100644 --- a/src/main/java/org/opensearch/security/configuration/MaskedField.java +++ b/src/main/java/org/opensearch/security/configuration/MaskedField.java @@ -40,11 +40,11 @@ public MaskedField(final String value, final Salt salt) { } else if (tokenCount == 2) { name = tokens.get(0); algo = tokens.get(1); - } else if (tokenCount >= 3 && tokenCount%2==1) { + } else if (tokenCount >= 3 && tokenCount % 2 == 1) { name = tokens.get(0); - regexReplacements = new ArrayList<>((tokenCount-1)/2); - for(int i=1; i((tokenCount - 1) / 2); + for (int i = 1; i < tokenCount - 1; i = i + 2) { + regexReplacements.add(new RegexReplacement(tokens.get(i), tokens.get(i + 1))); } } else { throw new IllegalArgumentException("Expected 1 or 2 or >=3 (but then odd count) tokens, got " + tokenCount); @@ -52,7 +52,7 @@ public MaskedField(final String value, final Salt salt) { } public final void isValid() throws Exception { - mask(new byte[] {1,2,3,4,5}); + mask(new byte[] { 1, 2, 3, 4, 5 }); } public byte[] mask(byte[] value) { @@ -72,7 +72,7 @@ public String mask(String value) { } public BytesRef mask(BytesRef value) { - if(value == null) { + if (value == null) { return null; } @@ -87,8 +87,6 @@ public String getName() { return name; } - - @Override public int hashCode() { final int prime = 31; @@ -101,37 +99,35 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; MaskedField other = (MaskedField) obj; if (algo == null) { - if (other.algo != null) - return false; - } else if (!algo.equals(other.algo)) - return false; + if (other.algo != null) return false; + } else if (!algo.equals(other.algo)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; if (regexReplacements == null) { - if (other.regexReplacements != null) - return false; - } else if (!regexReplacements.equals(other.regexReplacements)) - return false; + if (other.regexReplacements != null) return false; + } else if (!regexReplacements.equals(other.regexReplacements)) return false; return true; } - - @Override public String toString() { - return "MaskedField [name=" + name + ", algo=" + algo + ", regexReplacements=" + regexReplacements - + ", defaultSalt=" + Arrays.toString(defaultSalt) + ", isDefault()=" + isDefault() + "]"; + return "MaskedField [name=" + + name + + ", algo=" + + algo + + ", regexReplacements=" + + regexReplacements + + ", defaultSalt=" + + Arrays.toString(defaultSalt) + + ", isDefault()=" + + isDefault() + + "]"; } private boolean isDefault() { @@ -148,7 +144,7 @@ private byte[] customHash(byte[] in) { } } else if (regexReplacements != null) { String cur = new String(in, StandardCharsets.UTF_8); - for(RegexReplacement rr: regexReplacements) { + for (RegexReplacement rr : regexReplacements) { cur = cur.replaceAll(rr.getRegex(), rr.getReplacement()); } return cur.getBytes(StandardCharsets.UTF_8); @@ -190,7 +186,7 @@ private static class RegexReplacement { public RegexReplacement(String regex, String replacement) { super(); - this.regex = regex.substring(1).substring(0, regex.length()-2); + this.regex = regex.substring(1).substring(0, regex.length() - 2); this.replacement = replacement; } @@ -213,23 +209,16 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; RegexReplacement other = (RegexReplacement) obj; if (regex == null) { - if (other.regex != null) - return false; - } else if (!regex.equals(other.regex)) - return false; + if (other.regex != null) return false; + } else if (!regex.equals(other.regex)) return false; if (replacement == null) { - if (other.replacement != null) - return false; - } else if (!replacement.equals(other.replacement)) - return false; + if (other.replacement != null) return false; + } else if (!replacement.equals(other.replacement)) return false; return true; } diff --git a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java index e2f10dfcae..3f7b4737bf 100644 --- a/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java +++ b/src/main/java/org/opensearch/security/configuration/PrivilegesInterceptorImpl.java @@ -56,18 +56,30 @@ public class PrivilegesInterceptorImpl extends PrivilegesInterceptor { private static final String USER_TENANT = "__user__"; private static final String EMPTY_STRING = ""; private static final Map KIBANA_INDEX_SETTINGS = ImmutableMap.of( - IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1, - IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1" + IndexMetadata.SETTING_NUMBER_OF_SHARDS, + 1, + IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, + "0-1" ); protected final Logger log = LogManager.getLogger(this.getClass()); - public PrivilegesInterceptorImpl(IndexNameExpressionResolver resolver, ClusterService clusterService, Client client, ThreadPool threadPool) { + public PrivilegesInterceptorImpl( + IndexNameExpressionResolver resolver, + ClusterService clusterService, + Client client, + ThreadPool threadPool + ) { super(resolver, clusterService, client, threadPool); } - private boolean isTenantAllowed(final ActionRequest request, final String action, final User user, final Map tenants, - final String requestedTenant) { + private boolean isTenantAllowed( + final ActionRequest request, + final String action, + final User user, + final Map tenants, + final String requestedTenant + ) { if (!tenants.keySet().contains(requestedTenant)) { log.warn("Tenant {} is not allowed for user {}", requestedTenant, user.getName()); @@ -94,23 +106,29 @@ private boolean isTenantAllowed(final ActionRequest request, final String action * */ @Override - public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final String action, final User user, final DynamicConfigModel config, - final Resolved requestedResolved, final Map tenants) { + public ReplaceResult replaceDashboardsIndex( + final ActionRequest request, + final String action, + final User user, + final DynamicConfigModel config, + final Resolved requestedResolved, + final Map tenants + ) { - final boolean enabled = config.isDashboardsMultitenancyEnabled();//config.dynamic.kibana.multitenancy_enabled; + final boolean enabled = config.isDashboardsMultitenancyEnabled();// config.dynamic.kibana.multitenancy_enabled; if (!enabled) { return CONTINUE_EVALUATION_REPLACE_RESULT; } - //next two lines needs to be retrieved from configuration - final String dashboardsServerUsername = config.getDashboardsServerUsername();//config.dynamic.kibana.server_username; - final String dashboardsIndexName = config.getDashboardsIndexname();//config.dynamic.kibana.index; + // next two lines needs to be retrieved from configuration + final String dashboardsServerUsername = config.getDashboardsServerUsername();// config.dynamic.kibana.server_username; + final String dashboardsIndexName = config.getDashboardsIndexname();// config.dynamic.kibana.index; String requestedTenant = user.getRequestedTenant(); - if(USER_TENANT.equals(requestedTenant)) { + if (USER_TENANT.equals(requestedTenant)) { final boolean private_tenant_enabled = config.isDashboardsPrivateTenantEnabled(); - if(!private_tenant_enabled) { + if (!private_tenant_enabled) { return ACCESS_DENIED_REPLACE_RESULT; } } @@ -120,8 +138,10 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S log.debug("raw requestedTenant: '" + requestedTenant + "'"); } - //intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias involved - final boolean dashboardsIndexOnly = !user.getName().equals(dashboardsServerUsername) && resolveToDashboardsIndexOrAlias(requestedResolved, dashboardsIndexName); + // intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias + // involved + final boolean dashboardsIndexOnly = !user.getName().equals(dashboardsServerUsername) + && resolveToDashboardsIndexOrAlias(requestedResolved, dashboardsIndexName); final boolean isTraceEnabled = log.isTraceEnabled(); if (requestedTenant == null || requestedTenant.length() == 0) { if (isTraceEnabled) { @@ -140,21 +160,23 @@ public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final S } if (isDebugEnabled && !user.getName().equals(dashboardsServerUsername)) { - //log statements only here + // log statements only here log.debug("requestedResolved: " + requestedResolved); } - //request not made by the kibana server and user index is the only index/alias involved + // request not made by the kibana server and user index is the only index/alias involved if (!user.getName().equals(dashboardsServerUsername) && !requestedResolved.isLocalAll()) { final Set indices = requestedResolved.getAllIndices(); final String tenantIndexName = toUserIndexName(dashboardsIndexName, requestedTenant); - if (indices.size() == 1 && indices.iterator().next().startsWith(tenantIndexName) && - isTenantAllowed(request, action, user, tenants, requestedTenant)) { - return ACCESS_GRANTED_REPLACE_RESULT; + if (indices.size() == 1 + && indices.iterator().next().startsWith(tenantIndexName) + && isTenantAllowed(request, action, user, tenants, requestedTenant)) { + return ACCESS_GRANTED_REPLACE_RESULT; } } - //intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias involved + // intercept when requests are not made by the kibana server and if the kibana index/alias (.kibana) is the only index/alias + // involved if (dashboardsIndexOnly) { if (isDebugEnabled) { @@ -211,7 +233,8 @@ private CreateIndexRequestBuilder newCreateIndexRequestBuilderIfAbsent(final Str } String concreteName = getConcreteIndexName(name, indicesLookup); if (concreteName != null) { - return client.admin().indices() + return client.admin() + .indices() .prepareCreate(concreteName) .addAlias(new Alias(name)) .setSettings(KIBANA_INDEX_SETTINGS) @@ -220,7 +243,12 @@ private CreateIndexRequestBuilder newCreateIndexRequestBuilderIfAbsent(final Str return null; } - private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, final String oldIndexName, final String newIndexName, final String action) { + private CreateIndexRequestBuilder replaceIndex( + final ActionRequest request, + final String oldIndexName, + final String newIndexName, + final String action + ) { boolean kibOk = false; CreateIndexRequestBuilder createIndexRequestBuilder = null; @@ -228,10 +256,10 @@ private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, fina log.debug("{} index will be replaced with {} in this {} request", oldIndexName, newIndexName, request.getClass().getName()); } - //handle msearch and mget - //in case of GET change the .kibana index to the userskibanaindex - //in case of Search add the usersDashboardsindex - //if (request instanceof CompositeIndicesRequest) { + // handle msearch and mget + // in case of GET change the .kibana index to the userskibanaindex + // in case of Search add the usersDashboardsindex + // if (request instanceof CompositeIndicesRequest) { String[] newIndexNames = new String[] { newIndexName }; // CreateIndexRequest @@ -312,7 +340,7 @@ private CreateIndexRequestBuilder replaceIndex(final ActionRequest request, fina ((SingleShardRequest) request).index(newIndexName); kibOk = true; } else if (request instanceof RefreshRequest) { - ((RefreshRequest) request).indices(newIndexNames); //??? + ((RefreshRequest) request).indices(newIndexNames); // ??? kibOk = true; } else if (request instanceof ReplicationRequest) { ((ReplicationRequest) request).index(newIndexName); diff --git a/src/main/java/org/opensearch/security/configuration/Salt.java b/src/main/java/org/opensearch/security/configuration/Salt.java index cde4104fa2..3799fa846f 100644 --- a/src/main/java/org/opensearch/security/configuration/Salt.java +++ b/src/main/java/org/opensearch/security/configuration/Salt.java @@ -45,13 +45,19 @@ public Salt(final byte[] salt) { private Salt(final String saltAsString) { this.salt16 = new byte[SALT_SIZE]; if (saltAsString.equals(ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT)) { - log.warn("If you plan to use field masking pls configure compliance salt {} to be a random string of 16 chars length identical on all nodes", saltAsString); + log.warn( + "If you plan to use field masking pls configure compliance salt {} to be a random string of 16 chars length identical on all nodes", + saltAsString + ); } try { ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(saltAsString); byteBuffer.get(salt16); if (byteBuffer.remaining() > 0) { - log.warn("Provided compliance salt {} is greater than 16 bytes. Only the first 16 bytes are used for salting", saltAsString); + log.warn( + "Provided compliance salt {} is greater than 16 bytes. Only the first 16 bytes are used for salting", + saltAsString + ); } } catch (BufferUnderflowException e) { throw new OpenSearchException("Provided compliance salt " + saltAsString + " must at least contain 16 bytes", e); @@ -73,7 +79,10 @@ byte[] getSalt16() { * @return configuration */ public static Salt from(final Settings settings) { - final String saltAsString = settings.get(ConfigConstants.SECURITY_COMPLIANCE_SALT, ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT); + final String saltAsString = settings.get( + ConfigConstants.SECURITY_COMPLIANCE_SALT, + ConfigConstants.SECURITY_COMPLIANCE_SALT_DEFAULT + ); return new Salt(saltAsString); } } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java index cfb2a1de61..2e58424c63 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityFlsDlsIndexSearcherWrapper.java @@ -39,11 +39,24 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SecurityIndexSearcherWrapper { // TODO: the list is outdated. It is necessary to change how meta fields are handled in the near future. - // We may consider using MapperService.isMetadataField() instead of relying on the static set or - // (if it is too costly or does not meet requirements) use IndicesModule.getBuiltInMetadataFields() - // for OpenSearch version specific Set of meta fields - private static final Set metaFields = Sets.newHashSet("_source", "_version", "_field_names", - "_seq_no", "_primary_term", "_id", IgnoredFieldMapper.NAME, "_index", "_routing", "_size", "_timestamp", "_ttl", "_type"); + // We may consider using MapperService.isMetadataField() instead of relying on the static set or + // (if it is too costly or does not meet requirements) use IndicesModule.getBuiltInMetadataFields() + // for OpenSearch version specific Set of meta fields + private static final Set metaFields = Sets.newHashSet( + "_source", + "_version", + "_field_names", + "_seq_no", + "_primary_term", + "_id", + IgnoredFieldMapper.NAME, + "_index", + "_routing", + "_size", + "_timestamp", + "_ttl", + "_type" + ); private final ClusterService clusterService; private final IndexService indexService; private final AuditLog auditlog; @@ -51,9 +64,16 @@ public class SecurityFlsDlsIndexSearcherWrapper extends SecurityIndexSearcherWra private final DlsQueryParser dlsQueryParser; private final Salt salt; - public SecurityFlsDlsIndexSearcherWrapper(final IndexService indexService, final Settings settings, - final AdminDNs adminDNs, final ClusterService clusterService, final AuditLog auditlog, - final ComplianceIndexingOperationListener ciol, final PrivilegesEvaluator evaluator, final Salt salt) { + public SecurityFlsDlsIndexSearcherWrapper( + final IndexService indexService, + final Settings settings, + final AdminDNs adminDNs, + final ClusterService clusterService, + final AuditLog auditlog, + final ComplianceIndexingOperationListener ciol, + final PrivilegesEvaluator evaluator, + final Salt salt + ) { super(indexService, settings, adminDNs, evaluator); ciol.setIs(indexService); this.clusterService = clusterService; @@ -64,7 +84,7 @@ public SecurityFlsDlsIndexSearcherWrapper(final IndexService indexService, final if (allowNowinDlsQueries) { nowInMillis = () -> System.currentTimeMillis(); } else { - nowInMillis = () -> {throw new IllegalArgumentException("'now' is not allowed in DLS queries");}; + nowInMillis = () -> { throw new IllegalArgumentException("'now' is not allowed in DLS queries"); }; } log.debug("FLS/DLS {} enabled for index {}", this, indexService.index().getName()); this.salt = salt; @@ -80,14 +100,20 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm Set maskedFields = null; Query dlsQuery = null; - if(!isAdmin) { - - final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER); - final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER); - final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader(threadContext, - ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER); + if (!isAdmin) { + + final Map> allowedFlsFields = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_HEADER + ); + final Map> queries = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_HEADER + ); + final Map> maskedFieldsMap = (Map>) HeaderHelper.deserializeSafeFromHeader( + threadContext, + ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_HEADER + ); final String flsEval = SecurityUtils.evalMap(allowedFlsFields, index.getName()); final String dlsEval = SecurityUtils.evalMap(queries, index.getName()); @@ -114,7 +140,17 @@ protected DirectoryReader dlsFlsWrap(final DirectoryReader reader, boolean isAdm } } - return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader(reader, flsFields, dlsQuery, - indexService, threadContext, clusterService, auditlog, maskedFields, shardId, salt); + return new DlsFlsFilterLeafReader.DlsFlsDirectoryReader( + reader, + flsFields, + dlsQuery, + indexService, + threadContext, + clusterService, + auditlog, + maskedFields, + shardId, + salt + ); } } diff --git a/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java index a998b5f278..9c7d451fa3 100644 --- a/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SecurityIndexSearcherWrapper.java @@ -47,7 +47,7 @@ import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; -public class SecurityIndexSearcherWrapper implements CheckedFunction { +public class SecurityIndexSearcherWrapper implements CheckedFunction { protected final Logger log = LogManager.getLogger(this.getClass()); protected final ThreadContext threadContext; @@ -63,18 +63,32 @@ public class SecurityIndexSearcherWrapper implements CheckedFunction existingConfiguration = load(getConfigName(), false); - - if (!isWriteable(channel, existingConfiguration, name)) { - return; - } - - boolean existed = existingConfiguration.exists(name); - existingConfiguration.remove(name); - - if (existed) { - AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - successResponse(channel, "'" + name + "' deleted."); - } - }); - - } else { - notFound(channel, getResourceName() + " " + name + " not found."); - } - } - - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); - - if (existingConfiguration.getSeqNo() < 0) { - forbidden(channel, "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate."); - return; - } - - if (!isWriteable(channel, existingConfiguration, name)) { - return; - } - - if (isReadonlyFieldUpdated(existingConfiguration, content)) { - conflict(channel, "Attempted to update read-only property."); - return; - } - - if (log.isTraceEnabled() && content != null) { - log.trace(content.toString()); - } - - boolean existed = existingConfiguration.exists(name); - final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()); - if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) { - forbidden(channel, "No permissions"); - return; - } - existingConfiguration.putCObject(name, newContent); - - AbstractApiAction.saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (existed) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); - } - - } - }); - - } - - protected void handlePost(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - notImplemented(channel, Method.POST); - } - - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) throws IOException { - return false; - } + protected abstract String getResourceName(); + + protected abstract CType getConfigName(); + + protected void handleApiRequest(final RestChannel channel, final RestRequest request, final Client client) throws IOException { + + try { + // validate additional settings, if any + AbstractConfigurationValidator validator = getValidator(request, request.content()); + if (!validator.validate()) { + request.params().clear(); + badRequestResponse(channel, validator); + return; + } + switch (request.method()) { + case DELETE: + handleDelete(channel, request, client, validator.getContentAsNode()); + break; + case POST: + handlePost(channel, request, client, validator.getContentAsNode()); + break; + case PUT: + handlePut(channel, request, client, validator.getContentAsNode()); + break; + case GET: + handleGet(channel, request, client, validator.getContentAsNode()); + break; + default: + throw new IllegalArgumentException(request.method() + " not supported"); + } + } catch (JsonMappingException jme) { + throw jme; + // TODO strip source + // if(jme.getLocation() == null || jme.getLocation().getSourceRef() == null) { + // throw jme; + // } else throw new JsonMappingException(null, jme.getMessage()); + } + } + + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); + + if (!isWriteable(channel, existingConfiguration, name)) { + return; + } + + boolean existed = existingConfiguration.exists(name); + existingConfiguration.remove(name); + + if (existed) { + AbstractApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + successResponse(channel, "'" + name + "' deleted."); + } + } + ); + + } else { + notFound(channel, getResourceName() + " " + name + " not found."); + } + } + + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); + + if (existingConfiguration.getSeqNo() < 0) { + forbidden( + channel, + "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate." + ); + return; + } + + if (!isWriteable(channel, existingConfiguration, name)) { + return; + } + + if (isReadonlyFieldUpdated(existingConfiguration, content)) { + conflict(channel, "Attempted to update read-only property."); + return; + } + + if (log.isTraceEnabled() && content != null) { + log.trace(content.toString()); + } + + boolean existed = existingConfiguration.exists(name); + final Object newContent = DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass()); + if (!hasPermissionsToCreate(existingConfiguration, newContent, getResourceName())) { + forbidden(channel, "No permissions"); + return; + } + existingConfiguration.putCObject(name, newContent); + + AbstractApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (existed) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } + + } + } + ); + + } - protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) - throws IOException{ + protected void handlePost(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.POST); + } - final String resourcename = request.param("name"); + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) throws IOException { + return false; + } - final SecurityDynamicConfiguration configuration = load(getConfigName(), true); - filter(configuration); + protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { + final String resourcename = request.param("name"); - // no specific resource requested, return complete config - if (resourcename == null || resourcename.length() == 0) { + final SecurityDynamicConfiguration configuration = load(getConfigName(), true); + filter(configuration); - successResponse(channel, configuration); - return; - } + // no specific resource requested, return complete config + if (resourcename == null || resourcename.length() == 0) { - if (!configuration.exists(resourcename)) { - notFound(channel, "Resource '" + resourcename + "' not found."); - return; - } + successResponse(channel, configuration); + return; + } - configuration.removeOthers(resourcename); - successResponse(channel, configuration); + if (!configuration.exists(resourcename)) { + notFound(channel, "Resource '" + resourcename + "' not found."); + return; + } - return; - } + configuration.removeOthers(resourcename); + successResponse(channel, configuration); - protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { - SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); - return DynamicConfigFactory.addStatics(loaded); - } + return; + } - protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { - SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent, acceptInvalid).get(config).deepClone(); + protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { + SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent) + .get(config) + .deepClone(); return DynamicConfigFactory.addStatics(loaded); } - protected boolean ensureIndexExists() { - if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { - return false; - } - return true; - } - - protected void filter(SecurityDynamicConfiguration builder) { - if (!isSuperAdmin()){ - builder.removeHidden(); - } - builder.set_meta(null); - } - - protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { - // Default is false. Override function for additional logic - return false; - } - - protected boolean isReadonlyFieldUpdated(final SecurityDynamicConfiguration configuration, final JsonNode targetResource) { - // Default is false. Override function for additional logic - return false; - } - - abstract class OnSucessActionListener implements ActionListener { - - private final RestChannel channel; - - public OnSucessActionListener(RestChannel channel) { - super(); - this.channel = channel; - } - - @Override - public final void onFailure(Exception e) { - if (ExceptionsHelper.unwrapCause(e) instanceof VersionConflictEngineException) { - conflict(channel, e.getMessage()); - } else { - internalErrorResponse(channel, "Error "+e.getMessage()); - } - } - - } - - public static void saveAndUpdateConfigs(final String indexName, final Client client, final CType cType, final SecurityDynamicConfiguration configuration, final ActionListener actionListener) { - final IndexRequest ir = new IndexRequest(indexName); - final String id = cType.toLCString(); - - configuration.removeStatic(); - - try { - client.index(ir.id(id) - .setRefreshPolicy(RefreshPolicy.IMMEDIATE) - .setIfSeqNo(configuration.getSeqNo()) - .setIfPrimaryTerm(configuration.getPrimaryTerm()) - .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false)), - new ConfigUpdatingActionListener<>(new String[]{id}, client, actionListener)); - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected static class ConfigUpdatingActionListener implements ActionListener { - private final String[] cTypes; - private final Client client; - private final ActionListener delegate; - - public ConfigUpdatingActionListener(String[] cTypes, Client client, ActionListener delegate) { - this.cTypes = Objects.requireNonNull(cTypes, "cTypes must not be null"); - this.client = Objects.requireNonNull(client, "client must not be null"); - this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); - } - - @Override - public void onResponse(Response response) { - - final ConfigUpdateRequest cur = new ConfigUpdateRequest(cTypes); - - client.execute(ConfigUpdateAction.INSTANCE, cur, new ActionListener() { - @Override - public void onResponse(final ConfigUpdateResponse ur) { - if(ur.hasFailures()) { - delegate.onFailure(ur.failures().get(0)); - return; - } - delegate.onResponse(response); - } - - @Override - public void onFailure(final Exception e) { - delegate.onFailure(e); - } - }); - - } - - @Override - public void onFailure(Exception e) { - delegate.onFailure(e); - } - - } - - @Override - protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - - // consume all parameters first so we can return a correct HTTP status, - // not 400 - consumeParameters(request); - - // check if .opendistro_security index has been initialized - if (!ensureIndexExists()) { - return channel -> internalErrorResponse(channel, ErrorType.SECURITY_NOT_INITIALIZED.getMessage()); - } - - // check if request is authorized - String authError = restApiPrivilegesEvaluator.checkAccessPermissions(request, getEndpoint()); - - final User user = (User) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final String userName = user == null ? null : user.getName(); - if (authError != null) { - log.error("No permission to access REST API: " + authError); - auditLog.logMissingPrivileges(authError, userName, request); - // for rest request - request.params().clear(); - return channel -> forbidden(channel, "No permission to access REST API: " + authError); - } else { - auditLog.logGrantedPrivileges(userName, request); - } - - final Object originalUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final Object originalRemoteAddress = threadPool.getThreadContext() - .getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - final Object originalOrigin = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); - - return channel -> threadPool.generic().submit(() -> { - try (StoredContext ignore = threadPool.getThreadContext().stashContext()) { - threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUser); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, originalRemoteAddress); - threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originalOrigin); - - handleApiRequest(channel, request, client); - } catch (Exception e) { - log.error("Error processing request {}", request, e); - try { - channel.sendResponse(new BytesRestResponse(channel, e)); - } catch (IOException ioe) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - }); - } - - protected boolean checkConfigUpdateResponse(final ConfigUpdateResponse response) { - - final int nodeCount = cs.state().getNodes().getNodes().size(); - final int expectedConfigCount = 1; - - boolean success = response.getNodes().size() == nodeCount; - if (!success) { - log.error( - "Expected " + nodeCount + " nodes to return response, but got only " + response.getNodes().size()); - } - - for (final String nodeId : response.getNodesMap().keySet()) { - final ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); - final boolean successNode = node.getUpdatedConfigTypes() != null - && node.getUpdatedConfigTypes().length == expectedConfigCount; - - if (!successNode) { - log.error("Expected " + expectedConfigCount + " config types for node " + nodeId + " but got only " - + Arrays.toString(node.getUpdatedConfigTypes())); - } - - success = success && successNode; - } - - return success; - } - - protected static XContentBuilder convertToJson(RestChannel channel, ToXContent toxContent) { - try { - XContentBuilder builder = channel.newBuilder(); - toxContent.toXContent(builder, ToXContent.EMPTY_PARAMS); - return builder; - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected void response(RestChannel channel, RestStatus status, String message) { - try { - final XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - builder.field("status", status.name()); - builder.field("message", message); - builder.endObject(); - channel.sendResponse(new BytesRestResponse(status, builder)); - } catch (IOException e) { - throw ExceptionsHelper.convertToOpenSearchException(e); - } - } - - protected void successResponse(RestChannel channel, SecurityDynamicConfiguration response) { - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, convertToJson(channel, response))); - } - - protected void successResponse(RestChannel channel) { - try { - final XContentBuilder builder = channel.newBuilder(); - builder.startObject(); - builder.endObject(); - channel.sendResponse( - new BytesRestResponse(RestStatus.OK, builder)); - } catch (IOException e) { - internalErrorResponse(channel, "Unable to fetch license: " + e.getMessage()); - log.error("Cannot fetch convert license to XContent due to", e); - } - } - - protected void badRequestResponse(RestChannel channel, AbstractConfigurationValidator validator) { - channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, validator.errorsAsXContent(channel))); - } - - protected void successResponse(RestChannel channel, String message) { - response(channel, RestStatus.OK, message); - } - - protected void createdResponse(RestChannel channel, String message) { - response(channel, RestStatus.CREATED, message); - } - - protected void badRequestResponse(RestChannel channel, String message) { - response(channel, RestStatus.BAD_REQUEST, message); - } - - protected void notFound(RestChannel channel, String message) { - response(channel, RestStatus.NOT_FOUND, message); - } - - protected void forbidden(RestChannel channel, String message) { - response(channel, RestStatus.FORBIDDEN, message); - } - - protected void internalErrorResponse(RestChannel channel, String message) { - response(channel, RestStatus.INTERNAL_SERVER_ERROR, message); - } - - protected void unprocessable(RestChannel channel, String message) { - response(channel, RestStatus.UNPROCESSABLE_ENTITY, message); - } - - protected void conflict(RestChannel channel, String message) { - response(channel, RestStatus.CONFLICT, message); - } - - protected void notImplemented(RestChannel channel, Method method) { - response(channel, RestStatus.NOT_IMPLEMENTED, - "Method " + method.name() + " not supported for this action."); - } - - protected final boolean isReserved(SecurityDynamicConfiguration configuration, String resourceName) { - if(isStatic(configuration, resourceName)) { //static is also always reserved - return true; - } - - final Object o = configuration.getCEntry(resourceName); - return o != null && o instanceof Hideable && ((Hideable) o).isReserved(); - } - - protected final boolean isHidden(SecurityDynamicConfiguration configuration, String resourceName) { - return configuration.isHidden(resourceName) && !isSuperAdmin(); - } - - protected final boolean isStatic(SecurityDynamicConfiguration configuration, String resourceName) { - final Object o = configuration.getCEntry(resourceName); - return o != null && o instanceof StaticDefinable && ((StaticDefinable) o).isStatic(); - } - - /** - * Consume all defined parameters for the request. Before we handle the - * request in subclasses where we actually need the parameter, some global - * checks are performed, e.g. check whether the .security_index index exists. Thus, the - * parameter(s) have not been consumed, and OpenSearch will always return a 400 with - * an internal error message. - * - * @param request - */ - protected void consumeParameters(final RestRequest request) { - request.param("name"); - } - - @Override - public String getName() { - return getClass().getSimpleName(); - } - - protected abstract Endpoint getEndpoint(); - - protected boolean isSuperAdmin() { - return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint()); - } - - /** - * Resource is readonly if it is reserved and user is not super admin. - * @param existingConfiguration Configuration - * @param name - * @return True if resource readonly - */ - protected boolean isReadOnly(final SecurityDynamicConfiguration existingConfiguration, - String name) { - return isSuperAdmin() ? false: isReserved(existingConfiguration, name); - } - - /** - * Checks if it is valid to add role to opendistro_security_roles or rolesmapping. - * Role can be mapped to user if it exists. Only superadmin can add hidden or reserved roles. - * - * @param channel Rest Channel for response - * @param role Name of the role - * @return True if role can be mapped - */ - protected boolean isValidRolesMapping(final RestChannel channel, final String role) { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - final SecurityDynamicConfiguration rolesMappingConfiguration = load(CType.ROLESMAPPING, false); - - if (!rolesConfiguration.exists(role)) { - notFound(channel, "Role '"+role+"' is not available for role-mapping."); - return false; - } - - if (isHidden(rolesConfiguration, role)) { - notFound(channel, "Role '" + role + "' is not available for role-mapping."); - return false; - } - - return isWriteable(channel, rolesMappingConfiguration, role); - } - - boolean isWriteable(final RestChannel channel, final SecurityDynamicConfiguration configuration, final String resourceName) { - if (isHidden(configuration, resourceName)) { - notFound(channel, "Resource '" + resourceName + "' is not available."); - return false; - } - - if (isReadOnly(configuration, resourceName)) { - forbidden(channel, "Resource '" + resourceName + "' is read-only."); - return false; - } - return true; - } + protected final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent, boolean acceptInvalid) { + SecurityDynamicConfiguration loaded = cl.getConfigurationsFromIndex( + Collections.singleton(config), + logComplianceEvent, + acceptInvalid + ).get(config).deepClone(); + return DynamicConfigFactory.addStatics(loaded); + } + + protected boolean ensureIndexExists() { + if (!cs.state().metadata().hasConcreteIndex(this.securityIndexName)) { + return false; + } + return true; + } + + protected void filter(SecurityDynamicConfiguration builder) { + if (!isSuperAdmin()) { + builder.removeHidden(); + } + builder.set_meta(null); + } + + protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { + // Default is false. Override function for additional logic + return false; + } + + protected boolean isReadonlyFieldUpdated(final SecurityDynamicConfiguration configuration, final JsonNode targetResource) { + // Default is false. Override function for additional logic + return false; + } + + abstract class OnSucessActionListener implements ActionListener { + + private final RestChannel channel; + + public OnSucessActionListener(RestChannel channel) { + super(); + this.channel = channel; + } + + @Override + public final void onFailure(Exception e) { + if (ExceptionsHelper.unwrapCause(e) instanceof VersionConflictEngineException) { + conflict(channel, e.getMessage()); + } else { + internalErrorResponse(channel, "Error " + e.getMessage()); + } + } + + } + + public static void saveAndUpdateConfigs( + final String indexName, + final Client client, + final CType cType, + final SecurityDynamicConfiguration configuration, + final ActionListener actionListener + ) { + final IndexRequest ir = new IndexRequest(indexName); + final String id = cType.toLCString(); + + configuration.removeStatic(); + + try { + client.index( + ir.id(id) + .setRefreshPolicy(RefreshPolicy.IMMEDIATE) + .setIfSeqNo(configuration.getSeqNo()) + .setIfPrimaryTerm(configuration.getPrimaryTerm()) + .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false)), + new ConfigUpdatingActionListener<>(new String[] { id }, client, actionListener) + ); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected static class ConfigUpdatingActionListener implements ActionListener { + private final String[] cTypes; + private final Client client; + private final ActionListener delegate; + + public ConfigUpdatingActionListener(String[] cTypes, Client client, ActionListener delegate) { + this.cTypes = Objects.requireNonNull(cTypes, "cTypes must not be null"); + this.client = Objects.requireNonNull(client, "client must not be null"); + this.delegate = Objects.requireNonNull(delegate, "delegate must not be null"); + } + + @Override + public void onResponse(Response response) { + + final ConfigUpdateRequest cur = new ConfigUpdateRequest(cTypes); + + client.execute(ConfigUpdateAction.INSTANCE, cur, new ActionListener() { + @Override + public void onResponse(final ConfigUpdateResponse ur) { + if (ur.hasFailures()) { + delegate.onFailure(ur.failures().get(0)); + return; + } + delegate.onResponse(response); + } + + @Override + public void onFailure(final Exception e) { + delegate.onFailure(e); + } + }); + + } + + @Override + public void onFailure(Exception e) { + delegate.onFailure(e); + } + + } + + @Override + protected final RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + + // consume all parameters first so we can return a correct HTTP status, + // not 400 + consumeParameters(request); + + // check if .opendistro_security index has been initialized + if (!ensureIndexExists()) { + return channel -> internalErrorResponse(channel, ErrorType.SECURITY_NOT_INITIALIZED.getMessage()); + } + + // check if request is authorized + String authError = restApiPrivilegesEvaluator.checkAccessPermissions(request, getEndpoint()); + + final User user = (User) threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final String userName = user == null ? null : user.getName(); + if (authError != null) { + log.error("No permission to access REST API: " + authError); + auditLog.logMissingPrivileges(authError, userName, request); + // for rest request + request.params().clear(); + return channel -> forbidden(channel, "No permission to access REST API: " + authError); + } else { + auditLog.logGrantedPrivileges(userName, request); + } + + final Object originalUser = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final Object originalRemoteAddress = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + final Object originalOrigin = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); + + return channel -> threadPool.generic().submit(() -> { + try (StoredContext ignore = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER, "true"); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, originalUser); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, originalRemoteAddress); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originalOrigin); + + handleApiRequest(channel, request, client); + } catch (Exception e) { + log.error("Error processing request {}", request, e); + try { + channel.sendResponse(new BytesRestResponse(channel, e)); + } catch (IOException ioe) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + }); + } + + protected boolean checkConfigUpdateResponse(final ConfigUpdateResponse response) { + + final int nodeCount = cs.state().getNodes().getNodes().size(); + final int expectedConfigCount = 1; + + boolean success = response.getNodes().size() == nodeCount; + if (!success) { + log.error("Expected " + nodeCount + " nodes to return response, but got only " + response.getNodes().size()); + } + + for (final String nodeId : response.getNodesMap().keySet()) { + final ConfigUpdateNodeResponse node = response.getNodesMap().get(nodeId); + final boolean successNode = node.getUpdatedConfigTypes() != null && node.getUpdatedConfigTypes().length == expectedConfigCount; + + if (!successNode) { + log.error( + "Expected " + + expectedConfigCount + + " config types for node " + + nodeId + + " but got only " + + Arrays.toString(node.getUpdatedConfigTypes()) + ); + } + + success = success && successNode; + } + + return success; + } + + protected static XContentBuilder convertToJson(RestChannel channel, ToXContent toxContent) { + try { + XContentBuilder builder = channel.newBuilder(); + toxContent.toXContent(builder, ToXContent.EMPTY_PARAMS); + return builder; + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected void response(RestChannel channel, RestStatus status, String message) { + try { + final XContentBuilder builder = channel.newBuilder(); + builder.startObject(); + builder.field("status", status.name()); + builder.field("message", message); + builder.endObject(); + channel.sendResponse(new BytesRestResponse(status, builder)); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } + + protected void successResponse(RestChannel channel, SecurityDynamicConfiguration response) { + channel.sendResponse(new BytesRestResponse(RestStatus.OK, convertToJson(channel, response))); + } + + protected void successResponse(RestChannel channel) { + try { + final XContentBuilder builder = channel.newBuilder(); + builder.startObject(); + builder.endObject(); + channel.sendResponse(new BytesRestResponse(RestStatus.OK, builder)); + } catch (IOException e) { + internalErrorResponse(channel, "Unable to fetch license: " + e.getMessage()); + log.error("Cannot fetch convert license to XContent due to", e); + } + } + + protected void badRequestResponse(RestChannel channel, AbstractConfigurationValidator validator) { + channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, validator.errorsAsXContent(channel))); + } + + protected void successResponse(RestChannel channel, String message) { + response(channel, RestStatus.OK, message); + } + + protected void createdResponse(RestChannel channel, String message) { + response(channel, RestStatus.CREATED, message); + } + + protected void badRequestResponse(RestChannel channel, String message) { + response(channel, RestStatus.BAD_REQUEST, message); + } + + protected void notFound(RestChannel channel, String message) { + response(channel, RestStatus.NOT_FOUND, message); + } + + protected void forbidden(RestChannel channel, String message) { + response(channel, RestStatus.FORBIDDEN, message); + } + + protected void internalErrorResponse(RestChannel channel, String message) { + response(channel, RestStatus.INTERNAL_SERVER_ERROR, message); + } + + protected void unprocessable(RestChannel channel, String message) { + response(channel, RestStatus.UNPROCESSABLE_ENTITY, message); + } + + protected void conflict(RestChannel channel, String message) { + response(channel, RestStatus.CONFLICT, message); + } + + protected void notImplemented(RestChannel channel, Method method) { + response(channel, RestStatus.NOT_IMPLEMENTED, "Method " + method.name() + " not supported for this action."); + } + + protected final boolean isReserved(SecurityDynamicConfiguration configuration, String resourceName) { + if (isStatic(configuration, resourceName)) { // static is also always reserved + return true; + } + + final Object o = configuration.getCEntry(resourceName); + return o != null && o instanceof Hideable && ((Hideable) o).isReserved(); + } + + protected final boolean isHidden(SecurityDynamicConfiguration configuration, String resourceName) { + return configuration.isHidden(resourceName) && !isSuperAdmin(); + } + + protected final boolean isStatic(SecurityDynamicConfiguration configuration, String resourceName) { + final Object o = configuration.getCEntry(resourceName); + return o != null && o instanceof StaticDefinable && ((StaticDefinable) o).isStatic(); + } + + /** + * Consume all defined parameters for the request. Before we handle the + * request in subclasses where we actually need the parameter, some global + * checks are performed, e.g. check whether the .security_index index exists. Thus, the + * parameter(s) have not been consumed, and OpenSearch will always return a 400 with + * an internal error message. + * + * @param request + */ + protected void consumeParameters(final RestRequest request) { + request.param("name"); + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + protected abstract Endpoint getEndpoint(); + + protected boolean isSuperAdmin() { + return restApiAdminPrivilegesEvaluator.isCurrentUserRestApiAdminFor(getEndpoint()); + } + + /** + * Resource is readonly if it is reserved and user is not super admin. + * @param existingConfiguration Configuration + * @param name + * @return True if resource readonly + */ + protected boolean isReadOnly(final SecurityDynamicConfiguration existingConfiguration, String name) { + return isSuperAdmin() ? false : isReserved(existingConfiguration, name); + } + + /** + * Checks if it is valid to add role to opendistro_security_roles or rolesmapping. + * Role can be mapped to user if it exists. Only superadmin can add hidden or reserved roles. + * + * @param channel Rest Channel for response + * @param role Name of the role + * @return True if role can be mapped + */ + protected boolean isValidRolesMapping(final RestChannel channel, final String role) { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + final SecurityDynamicConfiguration rolesMappingConfiguration = load(CType.ROLESMAPPING, false); + + if (!rolesConfiguration.exists(role)) { + notFound(channel, "Role '" + role + "' is not available for role-mapping."); + return false; + } + + if (isHidden(rolesConfiguration, role)) { + notFound(channel, "Role '" + role + "' is not available for role-mapping."); + return false; + } + + return isWriteable(channel, rolesMappingConfiguration, role); + } + + boolean isWriteable(final RestChannel channel, final SecurityDynamicConfiguration configuration, final String resourceName) { + if (isHidden(configuration, resourceName)) { + notFound(channel, "Resource '" + resourceName + "' is not available."); + return false; + } + + if (isReadOnly(configuration, resourceName)) { + forbidden(channel, "Resource '" + resourceName + "' is read-only."); + return false; + } + return true; + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java index e5121f939d..68446366bc 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AccountApiAction.java @@ -59,34 +59,37 @@ */ public class AccountApiAction extends AbstractApiAction { private static final String RESOURCE_NAME = "account"; - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/account"), - new Route(Method.PUT, "/account") - )); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(Method.GET, "/account"), new Route(Method.PUT, "/account")) + ); private final PrivilegesEvaluator privilegesEvaluator; private final ThreadContext threadContext; - public AccountApiAction(Settings settings, - Path configPath, - RestController controller, - Client client, - AdminDNs adminDNs, - ConfigurationRepository cl, - ClusterService cs, - PrincipalExtractor principalExtractor, - PrivilegesEvaluator privilegesEvaluator, - ThreadPool threadPool, - AuditLog auditLog) { + public AccountApiAction( + Settings settings, + Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + PrincipalExtractor principalExtractor, + PrivilegesEvaluator privilegesEvaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.privilegesEvaluator = privilegesEvaluator; this.threadContext = threadPool.getThreadContext(); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -138,14 +141,14 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client final SecurityDynamicConfiguration configuration = load(getConfigName(), false); builder.field("user_name", user.getName()) - .field("is_reserved", isReserved(configuration, user.getName())) - .field("is_hidden", configuration.isHidden(user.getName())) - .field("is_internal_user", configuration.exists(user.getName())) - .field("user_requested_tenant", user.getRequestedTenant()) - .field("backend_roles", user.getRoles()) - .field("custom_attribute_names", user.getCustomAttributesMap().keySet()) - .field("tenants", privilegesEvaluator.mapTenants(user, securityRoles)) - .field("roles", securityRoles); + .field("is_reserved", isReserved(configuration, user.getName())) + .field("is_hidden", configuration.isHidden(user.getName())) + .field("is_internal_user", configuration.exists(user.getName())) + .field("user_requested_tenant", user.getRequestedTenant()) + .field("backend_roles", user.getRoles()) + .field("custom_attribute_names", user.getCustomAttributesMap().keySet()) + .field("tenants", privilegesEvaluator.mapTenants(user, securityRoles)) + .field("roles", securityRoles); } builder.endObject(); @@ -153,9 +156,7 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client } catch (final Exception exception) { log.error(exception.toString()); - builder.startObject() - .field("error", exception.toString()) - .endObject(); + builder.startObject().field("error", exception.toString()).endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } @@ -185,7 +186,8 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client * @throws IOException */ @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); final String username = user.getName(); final SecurityDynamicConfiguration internalUser = load(CType.INTERNALUSERS, false); @@ -224,12 +226,18 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C internalUserEntry.setHash(hash); - AccountApiAction.saveAndUpdateConfigs(this.securityIndexName, client, CType.INTERNALUSERS, internalUser, new OnSucessActionListener(channel) { - @Override - public void onResponse(IndexResponse response) { - successResponse(channel, "'" + username + "' updated."); + AccountApiAction.saveAndUpdateConfigs( + this.securityIndexName, + client, + CType.INTERNALUSERS, + internalUser, + new OnSucessActionListener(channel) { + @Override + public void onResponse(IndexResponse response) { + successResponse(channel, "'" + username + "' updated."); + } } - }); + ); } @Override diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java index 23a3a451b9..94af3ad3af 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ActionGroupsApiAction.java @@ -44,109 +44,123 @@ public class ActionGroupsApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - // legacy mapping for backwards compatibility - // TODO: remove in next version - new Route(Method.GET, "/actiongroup/{name}"), - new Route(Method.GET, "/actiongroup/"), - new Route(Method.DELETE, "/actiongroup/{name}"), - new Route(Method.PUT, "/actiongroup/{name}"), - - // corrected mapping, introduced in OpenSearch Security - new Route(Method.GET, "/actiongroups/{name}"), - new Route(Method.GET, "/actiongroups/"), - new Route(Method.DELETE, "/actiongroups/{name}"), - new Route(Method.PUT, "/actiongroups/{name}"), - new Route(Method.PATCH, "/actiongroups/"), - new Route(Method.PATCH, "/actiongroups/{name}") - - )); - - @Override - protected Endpoint getEndpoint() { - return Endpoint.ACTIONGROUPS; - } - - @Inject - public ActionGroupsApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - public List routes() { - return routes; - } - - @Override - protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { - return new ActionGroupValidator(request, isSuperAdmin(), ref, this.settings, param); - } - - @Override - protected CType getConfigName() { - return CType.ACTIONGROUPS; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + // legacy mapping for backwards compatibility + // TODO: remove in next version + new Route(Method.GET, "/actiongroup/{name}"), + new Route(Method.GET, "/actiongroup/"), + new Route(Method.DELETE, "/actiongroup/{name}"), + new Route(Method.PUT, "/actiongroup/{name}"), + + // corrected mapping, introduced in OpenSearch Security + new Route(Method.GET, "/actiongroups/{name}"), + new Route(Method.GET, "/actiongroups/"), + new Route(Method.DELETE, "/actiongroups/{name}"), + new Route(Method.PUT, "/actiongroups/{name}"), + new Route(Method.PATCH, "/actiongroups/"), + new Route(Method.PATCH, "/actiongroups/{name}") + + ) + ); + + @Override + protected Endpoint getEndpoint() { + return Endpoint.ACTIONGROUPS; + } + + @Inject + public ActionGroupsApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected AbstractConfigurationValidator getValidator(final RestRequest request, BytesReference ref, Object... param) { + return new ActionGroupValidator(request, isSuperAdmin(), ref, this.settings, param); + } + + @Override + protected CType getConfigName() { + return CType.ACTIONGROUPS; + } + + @Override protected String getResourceName() { return "actiongroup"; - } - - @Override - protected void consumeParameters(final RestRequest request) { - request.param("name"); - } - - @Override - protected void handlePut(RestChannel channel, RestRequest request, Client client, JsonNode content) throws IOException { - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - // Prevent the case where action group and role share a same name. - SecurityDynamicConfiguration existingRolesConfig = load(CType.ROLES, false); - Set existingRoles = existingRolesConfig.getCEntries().keySet(); - if (existingRoles.contains(name)) { - badRequestResponse(channel, name + " is an existing role. A action group cannot be named with an existing role name."); - return; - } - - // Prevent the case where action group references to itself in the allowed_actions. - final SecurityDynamicConfiguration existingActionGroupsConfig = load(getConfigName(), false); - final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()); - existingActionGroupsConfig.putCObject(name, actionGroup); - if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) { - badRequestResponse(channel, name + " cannot be an allowed_action of itself"); - return; - } - // prevent creation of groups for REST admin api - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) { - forbidden(channel, "Not allowed"); - return; - } - super.handlePut(channel, request, client, content); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, - final Object content, - final String resourceName) throws IOException { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { - return false; - } - return true; - } - - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { - return true; - } - return super.isReadOnly(existingConfiguration, name); - } + } + + @Override + protected void consumeParameters(final RestRequest request) { + request.param("name"); + } + + @Override + protected void handlePut(RestChannel channel, RestRequest request, Client client, JsonNode content) throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + // Prevent the case where action group and role share a same name. + SecurityDynamicConfiguration existingRolesConfig = load(CType.ROLES, false); + Set existingRoles = existingRolesConfig.getCEntries().keySet(); + if (existingRoles.contains(name)) { + badRequestResponse(channel, name + " is an existing role. A action group cannot be named with an existing role name."); + return; + } + + // Prevent the case where action group references to itself in the allowed_actions. + final SecurityDynamicConfiguration existingActionGroupsConfig = load(getConfigName(), false); + final Object actionGroup = DefaultObjectMapper.readTree(content, existingActionGroupsConfig.getImplementingClass()); + existingActionGroupsConfig.putCObject(name, actionGroup); + if (hasActionGroupSelfReference(existingActionGroupsConfig, name)) { + badRequestResponse(channel, name + " cannot be an allowed_action of itself"); + return; + } + // prevent creation of groups for REST admin api + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(actionGroup)) { + forbidden(channel, "Not allowed"); + return; + } + super.handlePut(channel, request, client, content); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfiguration, + final Object content, + final String resourceName + ) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return false; + } + return true; + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return true; + } + return super.isReadOnly(existingConfiguration, name); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java index cefaeb5c6c..0c5b2775aa 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AllowlistApiAction.java @@ -9,7 +9,6 @@ * GitHub history for details. */ - package org.opensearch.security.dlic.rest.api; import java.io.IOException; @@ -84,24 +83,36 @@ */ public class AllowlistApiAction extends PatchableResourceApiAction { private static final List routes = ImmutableList.of( - new Route(RestRequest.Method.GET, "/_plugins/_security/api/allowlist"), - new Route(RestRequest.Method.PUT, "/_plugins/_security/api/allowlist"), - new Route(RestRequest.Method.PATCH, "/_plugins/_security/api/allowlist") + new Route(RestRequest.Method.GET, "/_plugins/_security/api/allowlist"), + new Route(RestRequest.Method.PUT, "/_plugins/_security/api/allowlist"), + new Route(RestRequest.Method.PATCH, "/_plugins/_security/api/allowlist") ); private static final String name = "config"; @Inject - public AllowlistApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public AllowlistApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -115,9 +126,7 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } @Override - protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) - throws IOException { - + protected void handleGet(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final SecurityDynamicConfiguration configuration = load(getConfigName(), true); filter(configuration); @@ -125,36 +134,46 @@ protected void handleGet(final RestChannel channel, RestRequest request, Client } @Override - protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, RestRequest.Method.DELETE); } @Override - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final SecurityDynamicConfiguration existingConfiguration = load(getConfigName(), false); if (existingConfiguration.getSeqNo() < 0) { - forbidden(channel, "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate."); + forbidden( + channel, + "Security index need to be updated to support '" + getConfigName().toLCString() + "'. Use SecurityAdmin to populate." + ); return; } boolean existed = existingConfiguration.exists(name); existingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, existingConfiguration.getImplementingClass())); - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), existingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (existed) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); + saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + existingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (existed) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } } } - }); + ); } - @Override public List routes() { return routes; diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java index e19f04d437..a61f66c6e3 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuditApiAction.java @@ -124,11 +124,13 @@ * [{"op": "replace", "path": "/config/compliance/internal_config", "value": "true"}] */ public class AuditApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(RestRequest.Method.GET, "/audit/"), new Route(RestRequest.Method.PUT, "/audit/{name}"), new Route(RestRequest.Method.PATCH, "/audit/") - )); + ) + ); private static final String RESOURCE_NAME = "config"; @VisibleForTesting @@ -139,24 +141,28 @@ public class AuditApiAction extends PatchableResourceApiAction { private final PrivilegesEvaluator privilegesEvaluator; private final ThreadContext threadContext; - public AuditApiAction(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDNs, - final ConfigurationRepository cl, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator privilegesEvaluator, - final ThreadPool threadPool, - final AuditLog auditLog) { + public AuditApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + final ThreadPool threadPool, + final AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.privilegesEvaluator = privilegesEvaluator; this.threadContext = threadPool.getThreadContext(); try { - this.readonlyFields = DefaultObjectMapper.YAML_MAPPER - .readValue(this.getClass().getResourceAsStream(STATIC_RESOURCE), new TypeReference>>() {}) - .get(READONLY_FIELD); + this.readonlyFields = DefaultObjectMapper.YAML_MAPPER.readValue( + this.getClass().getResourceAsStream(STATIC_RESOURCE), + new TypeReference>>() { + } + ).get(READONLY_FIELD); if (!AuditConfig.FIELD_PATHS.containsAll(this.readonlyFields)) { throw new StaticResourceException("Invalid read-only field paths provided in static resource file " + STATIC_RESOURCE); } @@ -166,9 +172,11 @@ public AuditApiAction(final Settings settings, } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -189,7 +197,8 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } @Override - protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (!RESOURCE_NAME.equals(request.param("name"))) { badRequestResponse(channel, "name must be config"); return; @@ -245,9 +254,7 @@ protected CType getConfigName() { @Override protected boolean isReadonlyFieldUpdated(final JsonNode existingResource, final JsonNode targetResource) { if (!isSuperAdmin()) { - return readonlyFields - .stream() - .anyMatch(path -> !existingResource.at(path).equals(targetResource.at(path))); + return readonlyFields.stream().anyMatch(path -> !existingResource.at(path).equals(targetResource.at(path))); } return false; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java index 497efcdf76..fa6d967624 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/AuthTokenProcessorAction.java @@ -41,69 +41,77 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class AuthTokenProcessorAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.POST, "/authtoken") - )); - - @Inject - public AuthTokenProcessorAction(final Settings settings, final Path configPath, final RestController controller, - final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { - return true; - } - - @Override - public List routes() { - return routes; - } - - @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - - // Just do nothing here. Eligible authenticators will intercept calls and - // provide own responses. - successResponse(channel,""); - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); - } - - @Override - protected String getResourceName() { - return "authtoken"; - } - - @Override + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.POST, "/authtoken"))); + + @Inject + public AuthTokenProcessorAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { + return true; + } + + @Override + public List routes() { + return routes; + } + + @Override + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + + // Just do nothing here. Eligible authenticators will intercept calls and + // provide own responses. + successResponse(channel, ""); + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new NoOpValidator(request, ref, this.settings, param); + } + + @Override + protected String getResourceName() { + return "authtoken"; + } + + @Override protected CType getConfigName() { - return null; - } + return null; + } - @Override - protected Endpoint getEndpoint() { - return Endpoint.AUTHTOKEN; - } + @Override + protected Endpoint getEndpoint() { + return Endpoint.AUTHTOKEN; + } + public static class Response { + private String authorization; - public static class Response { - private String authorization; + public String getAuthorization() { + return authorization; + } - public String getAuthorization() { - return authorization; - } - - public void setAuthorization(String authorization) { - this.authorization = authorization; - } - } + public void setAuthorization(String authorization) { + this.authorization = authorization; + } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java index 406b81679c..2f8a60aa65 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/FlushCacheApiAction.java @@ -45,103 +45,118 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class FlushCacheApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.DELETE, "/cache"), - new Route(Method.GET, "/cache"), - new Route(Method.PUT, "/cache"), - new Route(Method.POST, "/cache") - )); - - @Inject - public FlushCacheApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { - return true; - } - - @Override - public List routes() { - return routes; - } - - @Override - protected Endpoint getEndpoint() { - return Endpoint.CACHE; - } - - @Override - protected void handleDelete(RestChannel channel, - RestRequest request, Client client, final JsonNode content) throws IOException - { - - client.execute( - ConfigUpdateAction.INSTANCE, - new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])), - new ActionListener() { - - @Override - public void onResponse(ConfigUpdateResponse ur) { - if(ur.hasFailures()) { - log.error("Cannot flush cache due to", ur.failures().get(0)); - internalErrorResponse(channel, "Cannot flush cache due to "+ ur.failures().get(0).getMessage()+"."); - return; - } - successResponse(channel, "Cache flushed successfully."); - if (log.isDebugEnabled()) { - log.debug("cache flushed successfully"); - } - } - - @Override - public void onFailure(Exception e) { - log.error("Cannot flush cache due to", e); - internalErrorResponse(channel, "Cannot flush cache due to "+ e.getMessage()+"."); - } - - } - ); - } - - @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content)throws IOException { - notImplemented(channel, Method.POST); - } - - @Override - protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ - notImplemented(channel, Method.GET); - } - - @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ - notImplemented(channel, Method.PUT); - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new NoOpValidator(request, ref, this.settings, param); - } - - @Override - protected String getResourceName() { - // not needed - return null; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.DELETE, "/cache"), + new Route(Method.GET, "/cache"), + new Route(Method.PUT, "/cache"), + new Route(Method.POST, "/cache") + ) + ); + + @Inject + public FlushCacheApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { + return true; + } + + @Override + public List routes() { + return routes; + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.CACHE; + } + + @Override + protected void handleDelete(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { + + client.execute( + ConfigUpdateAction.INSTANCE, + new ConfigUpdateRequest(CType.lcStringValues().toArray(new String[0])), + new ActionListener() { + + @Override + public void onResponse(ConfigUpdateResponse ur) { + if (ur.hasFailures()) { + log.error("Cannot flush cache due to", ur.failures().get(0)); + internalErrorResponse(channel, "Cannot flush cache due to " + ur.failures().get(0).getMessage() + "."); + return; + } + successResponse(channel, "Cache flushed successfully."); + if (log.isDebugEnabled()) { + log.debug("cache flushed successfully"); + } + } + + @Override + public void onFailure(Exception e) { + log.error("Cannot flush cache due to", e); + internalErrorResponse(channel, "Cannot flush cache due to " + e.getMessage() + "."); + } + + } + ); + } + + @Override + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.POST); + } + + @Override + protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.GET); + } + + @Override + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + notImplemented(channel, Method.PUT); + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new NoOpValidator(request, ref, this.settings, param); + } + + @Override + protected String getResourceName() { + // not needed + return null; + } + + @Override protected CType getConfigName() { - return null; - } + return null; + } - @Override - protected void consumeParameters(final RestRequest request) { - // not needed - } + @Override + protected void consumeParameters(final RestRequest request) { + // not needed + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java index 6c1169f35a..3e041961b0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/InternalUsersApiAction.java @@ -52,10 +52,11 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { static final List RESTRICTED_FROM_USERNAME = ImmutableList.of( - ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 + ":" // Not allowed in basic auth, see https://stackoverflow.com/a/33391003/533057 ); - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/user/{name}"), new Route(Method.GET, "/user/"), new Route(Method.POST, "/user/{name}/authtoken"), @@ -70,24 +71,36 @@ public class InternalUsersApiAction extends PatchableResourceApiAction { new Route(Method.PUT, "/internalusers/{name}"), new Route(Method.PATCH, "/internalusers/"), new Route(Method.PATCH, "/internalusers/{name}") - )); + ) + ); UserService userService; @Inject - public InternalUsersApiAction(final Settings settings, final Path configPath, final RestController controller, - final Client client, final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, - ThreadPool threadPool, UserService userService, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); + public InternalUsersApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + UserService userService, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); this.userService = userService; } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -102,7 +115,8 @@ protected Endpoint getEndpoint() { } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final String username = request.param("name"); @@ -118,7 +132,7 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // Don't allow user to add non-existent role or a role for which role-mapping is hidden or reserved final List securityRoles = securityJsonNode.get("opendistro_security_roles").asList(); if (securityRoles != null) { - for (final String role: securityRoles) { + for (final String role : securityRoles) { if (!isValidRolesMapping(channel, role)) { return; } @@ -139,12 +153,10 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } ((ObjectNode) content).put("name", username); internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); - } - catch (UserServiceException ex) { + } catch (UserServiceException ex) { badRequestResponse(channel, ex.getMessage()); return; - } - catch (IOException ex) { + } catch (IOException ex) { throw new IOException(ex); } @@ -153,8 +165,10 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // sanity check, this should usually not happen final String hash = ((Hashed) internalUsersConfiguration.getCEntry(username)).getHash(); if (hash == null || hash.length() == 0) { - internalErrorResponse(channel, - "Existing user " + username + " has no password, and no new password or hash was specified."); + internalErrorResponse( + channel, + "Existing user " + username + " has no password, and no new password or hash was specified." + ); return; } contentAsNode.put("hash", hash); @@ -163,23 +177,27 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C internalUsersConfiguration.remove(username); // checks complete, create or update the user - Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()); + Object userData = DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass()); internalUsersConfiguration.putCObject(username, userData); + saveAndUpdateConfigs( + this.securityIndexName, + client, + CType.INTERNALUSERS, + internalUsersConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (userExisted) { + successResponse(channel, "'" + username + "' updated."); + } else { + createdResponse(channel, "'" + username + "' created."); + } - saveAndUpdateConfigs(this.securityIndexName,client, CType.INTERNALUSERS, internalUsersConfiguration, new OnSucessActionListener(channel) { - - - @Override - public void onResponse(IndexResponse response) { - if (userExisted) { - successResponse(channel, "'" + username + "' updated."); - } else { - createdResponse(channel, "'" + username + "' created."); } - } - }); + ); } /** @@ -192,7 +210,7 @@ public void onResponse(IndexResponse response) { * @throws IOException when parsing of configuration files fails (should not happen) */ @Override - protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ + protected void handlePost(final RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final String username = request.param("name"); @@ -215,7 +233,10 @@ protected void handlePost(final RestChannel channel, RestRequest request, Client String authToken = ""; try { - if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle auth token fetching + if (request.uri().contains("/internalusers/" + username + "/authtoken") && request.uri().endsWith("/authtoken")) { // Handle + // auth + // token + // fetching authToken = userService.generateAuthToken(username); } else { // Not an auth token request @@ -223,22 +244,20 @@ protected void handlePost(final RestChannel channel, RestRequest request, Client notImplemented(channel, Method.POST); return; } - } catch (UserServiceException ex) { + } catch (UserServiceException ex) { badRequestResponse(channel, ex.getMessage()); return; - } - catch (IOException ex) { + } catch (IOException ex) { throw new IOException(ex); } if (!authToken.isEmpty()) { - createdResponse(channel, "'" + username + "' authtoken generated " + authToken); + createdResponse(channel, "'" + username + "' authtoken generated " + authToken); } else { badRequestResponse(channel, "'" + username + "' authtoken failed to be created."); } } - @Override protected void filter(SecurityDynamicConfiguration builder) { super.filter(builder); @@ -249,8 +268,13 @@ protected void filter(SecurityDynamicConfiguration builder) { } @Override - protected AbstractConfigurationValidator postProcessApplyPatchResult(RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, - JsonNode updatedResourceAsJsonNode, String resourceName) { + protected AbstractConfigurationValidator postProcessApplyPatchResult( + RestChannel channel, + RestRequest request, + JsonNode existingResourceAsJsonNode, + JsonNode updatedResourceAsJsonNode, + String resourceName + ) { AbstractConfigurationValidator retVal = null; JsonNode passwordNode = updatedResourceAsJsonNode.get("password"); diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java index d252515f72..108e29b980 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MigrateApiAction.java @@ -71,14 +71,22 @@ // CS-ENFORCE-SINGLE public class MigrateApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.POST, "/migrate") - )); + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.POST, "/migrate"))); @Inject - public MigrateApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public MigrateApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @@ -93,9 +101,11 @@ protected Endpoint getEndpoint() { } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -111,12 +121,22 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien } final SecurityDynamicConfiguration configV6 = (SecurityDynamicConfiguration) loadedConfig; - final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load(CType.ACTIONGROUPS, true); - final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load(CType.INTERNALUSERS, true); + final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load( + CType.ACTIONGROUPS, + true + ); + final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load( + CType.INTERNALUSERS, + true + ); final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load(CType.ROLES, true); - final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load(CType.ROLESMAPPING, true); + final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load( + CType.ROLESMAPPING, + true + ); final SecurityDynamicConfiguration nodesDnV6 = (SecurityDynamicConfiguration) load(CType.NODESDN, true); - final SecurityDynamicConfiguration whitelistingSettingV6 = (SecurityDynamicConfiguration) load(CType.WHITELIST, true); + final SecurityDynamicConfiguration whitelistingSettingV6 = (SecurityDynamicConfiguration< + WhitelistingSettings>) load(CType.WHITELIST, true); final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load(CType.AUDIT, true); final ImmutableList.Builder> builder = ImmutableList.builder(); @@ -127,21 +147,29 @@ protected void handlePost(RestChannel channel, RestRequest request, Client clien builder.add(configV7); final SecurityDynamicConfiguration internalUsersV7 = Migration.migrateInternalUsers(internalUsersV6); builder.add(internalUsersV7); - final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles(rolesV6, - rolesmappingV6); + final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles( + rolesV6, + rolesmappingV6 + ); builder.add(rolesTenantsV7.v1()); builder.add(rolesTenantsV7.v2()); final SecurityDynamicConfiguration rolesmappingV7 = Migration.migrateRoleMappings(rolesmappingV6); builder.add(rolesmappingV7); final SecurityDynamicConfiguration nodesDnV7 = Migration.migrateNodesDn(nodesDnV6); builder.add(nodesDnV7); - final SecurityDynamicConfiguration whitelistingSettingV7 = Migration.migrateWhitelistingSetting(whitelistingSettingV6); + final SecurityDynamicConfiguration whitelistingSettingV7 = Migration.migrateWhitelistingSetting( + whitelistingSettingV6 + ); builder.add(whitelistingSettingV7); final SecurityDynamicConfiguration auditConfigV7 = Migration.migrateAudit(auditConfigV6); builder.add(auditConfigV7); final int replicas = cs.state().metadata().index(securityIndexName).getNumberOfReplicas(); - final String autoExpandReplicas = cs.state().metadata().index(securityIndexName).getSettings().get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); + final String autoExpandReplicas = cs.state() + .metadata() + .index(securityIndexName) + .getSettings() + .get(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS); final Builder securityIndexSettings = Settings.builder(); @@ -161,57 +189,78 @@ public void onResponse(AcknowledgedResponse response) { if (response.isAcknowledged()) { log.debug("opendistro_security index deleted successfully"); - client.admin().indices().prepareCreate(securityIndexName).setSettings(securityIndexSettings) - .execute(new ActionListener() { - - @Override - public void onResponse(CreateIndexResponse response) { - final List> dynamicConfigurations = builder.build(); - final ImmutableList.Builder cTypes = ImmutableList.builderWithExpectedSize(dynamicConfigurations.size()); - final BulkRequestBuilder br = client.prepareBulk(securityIndexName); - br.setRefreshPolicy(RefreshPolicy.IMMEDIATE); - try { - for (SecurityDynamicConfiguration dynamicConfiguration : dynamicConfigurations) { - final String id = dynamicConfiguration.getCType().toLCString(); - final BytesReference xContent = XContentHelper.toXContent(dynamicConfiguration, XContentType.JSON, false); - br.add(new IndexRequest().id(id).source(id, xContent)); - cTypes.add(id); - } - } catch (final IOException e1) { - log.error("Unable to create bulk request " + e1, e1); - internalErrorResponse(channel, "Unable to create bulk request."); - return; + client.admin() + .indices() + .prepareCreate(securityIndexName) + .setSettings(securityIndexSettings) + .execute(new ActionListener() { + + @Override + public void onResponse(CreateIndexResponse response) { + final List> dynamicConfigurations = builder.build(); + final ImmutableList.Builder cTypes = ImmutableList.builderWithExpectedSize( + dynamicConfigurations.size() + ); + final BulkRequestBuilder br = client.prepareBulk(securityIndexName); + br.setRefreshPolicy(RefreshPolicy.IMMEDIATE); + try { + for (SecurityDynamicConfiguration dynamicConfiguration : dynamicConfigurations) { + final String id = dynamicConfiguration.getCType().toLCString(); + final BytesReference xContent = XContentHelper.toXContent( + dynamicConfiguration, + XContentType.JSON, + false + ); + br.add(new IndexRequest().id(id).source(id, xContent)); + cTypes.add(id); } + } catch (final IOException e1) { + log.error("Unable to create bulk request " + e1, e1); + internalErrorResponse(channel, "Unable to create bulk request."); + return; + } - br.execute(new ConfigUpdatingActionListener(cTypes.build().toArray(new String[0]), client, new ActionListener() { + br.execute( + new ConfigUpdatingActionListener( + cTypes.build().toArray(new String[0]), + client, + new ActionListener() { + + @Override + public void onResponse(BulkResponse response) { + if (response.hasFailures()) { + log.error( + "Unable to upload migrated configuration because of " + + response.buildFailureMessage() + ); + internalErrorResponse( + channel, + "Unable to upload migrated configuration (bulk index failed)." + ); + } else { + log.debug("Migration completed"); + successResponse(channel, "Migration completed."); + } - @Override - public void onResponse(BulkResponse response) { - if (response.hasFailures()) { - log.error("Unable to upload migrated configuration because of " + response.buildFailureMessage()); - internalErrorResponse(channel, "Unable to upload migrated configuration (bulk index failed)."); - } else { - log.debug("Migration completed"); - successResponse(channel, "Migration completed."); } + @Override + public void onFailure(Exception e) { + log.error("Unable to upload migrated configuration because of " + e, e); + internalErrorResponse(channel, "Unable to upload migrated configuration."); + } } + ) + ); - @Override - public void onFailure(Exception e) { - log.error("Unable to upload migrated configuration because of " + e, e); - internalErrorResponse(channel, "Unable to upload migrated configuration."); - } - })); - - } + } - @Override - public void onFailure(Exception e) { - log.error("Unable to create opendistro_security index because of " + e, e); - internalErrorResponse(channel, "Unable to create opendistro_security index."); - } - }); + @Override + public void onFailure(Exception e) { + log.error("Unable to create opendistro_security index because of " + e, e); + internalErrorResponse(channel, "Unable to create opendistro_security index."); + } + }); } else { log.error("Unable to create opendistro_security index."); @@ -228,17 +277,20 @@ public void onFailure(Exception e) { } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleGet(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.GET); } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.PUT); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java index e5ec82c245..ef5be4634f 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/MultiTenancyConfigApiAction.java @@ -51,20 +51,16 @@ import static org.opensearch.rest.RestRequest.Method.PUT; import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - public class MultiTenancyConfigApiAction extends AbstractApiAction { private static final List ROUTES = addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/tenancy/config"), - new Route(PUT, "/tenancy/config") - ) + ImmutableList.of(new Route(GET, "/tenancy/config"), new Route(PUT, "/tenancy/config")) ); private final static Set ACCEPTABLE_DEFAULT_TENANTS = ImmutableSet.of( - ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME, - ConfigConstants.TENANCY_GLOBAL_TENANT_NAME, - ConfigConstants.TENANCY_PRIVATE_TENANT_NAME + ConfigConstants.TENANCY_GLOBAL_TENANT_DEFAULT_NAME, + ConfigConstants.TENANCY_GLOBAL_TENANT_NAME, + ConfigConstants.TENANCY_PRIVATE_TENANT_NAME ); @Override @@ -78,12 +74,18 @@ public List routes() { } public MultiTenancyConfigApiAction( - final Settings settings, final Path configPath, - final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, - final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, final ThreadPool threadPool, - final AuditLog auditLog) { + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @@ -108,31 +110,25 @@ protected CType getConfigName() { } @Override - protected void handleDelete(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleDelete(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, RestRequest.Method.DELETE); } private void multitenancyResponse(final ConfigV7 config, final RestChannel channel) { try (final XContentBuilder contentBuilder = channel.newBuilder()) { channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field( - MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, - config.dynamic.kibana.default_tenant - ).field( - MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, - config.dynamic.kibana.private_tenant_enabled - ).field( - MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, - config.dynamic.kibana.multitenancy_enabled - ).endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject() + .field(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY, config.dynamic.kibana.default_tenant) + .field( + MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY, + config.dynamic.kibana.private_tenant_enabled + ) + .field(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY, config.dynamic.kibana.multitenancy_enabled) + .endObject() + ) ); } catch (final Exception e) { internalErrorResponse(channel, e.getMessage()); @@ -140,61 +136,49 @@ private void multitenancyResponse(final ConfigV7 config, final RestChannel chann } } - @Override - protected void handleGet(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleGet(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { final SecurityDynamicConfiguration dynamicConfiguration = load(CType.CONFIG, false); final ConfigV7 config = (ConfigV7) dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); multitenancyResponse(config, channel); } @Override - protected void handlePut(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { - final SecurityDynamicConfiguration dynamicConfiguration = (SecurityDynamicConfiguration) - load(CType.CONFIG, false); + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final SecurityDynamicConfiguration dynamicConfiguration = (SecurityDynamicConfiguration) load( + CType.CONFIG, + false + ); final ConfigV7 config = dynamicConfiguration.getCEntry(CType.CONFIG.toLCString()); updateAndValidatesValues(config, content); dynamicConfiguration.putCEntry(CType.CONFIG.toLCString(), config); - saveAndUpdateConfigs( - this.securityIndexName, - client, - getConfigName(), - dynamicConfiguration, - new OnSucessActionListener<>(channel) { - @Override - public void onResponse(IndexResponse response) { - multitenancyResponse(config, channel); - } - } - ); + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), dynamicConfiguration, new OnSucessActionListener<>(channel) { + @Override + public void onResponse(IndexResponse response) { + multitenancyResponse(config, channel); + } + }); } private void updateAndValidatesValues(final ConfigV7 config, final JsonNode jsonContent) { if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY))) { - config.dynamic.kibana.default_tenant = - jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); + config.dynamic.kibana.default_tenant = jsonContent.findValue(MultiTenancyConfigValidator.DEFAULT_TENANT_JSON_PROPERTY).asText(); } if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.private_tenant_enabled = - jsonContent.findValue(MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY).booleanValue(); + config.dynamic.kibana.private_tenant_enabled = jsonContent.findValue( + MultiTenancyConfigValidator.PRIVATE_TENANT_ENABLED_JSON_PROPERTY + ).booleanValue(); } if (Objects.nonNull(jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY))) { - config.dynamic.kibana.multitenancy_enabled = - jsonContent.findValue(MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY).asBoolean(); + config.dynamic.kibana.multitenancy_enabled = jsonContent.findValue( + MultiTenancyConfigValidator.MULTITENANCY_ENABLED_JSON_PROPERTY + ).asBoolean(); } - final String defaultTenant = - Optional.ofNullable(config.dynamic.kibana.default_tenant) - .map(String::toLowerCase) - .orElse(""); + final String defaultTenant = Optional.ofNullable(config.dynamic.kibana.default_tenant).map(String::toLowerCase).orElse(""); - if (!config.dynamic.kibana.private_tenant_enabled - && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { + if (!config.dynamic.kibana.private_tenant_enabled && ConfigConstants.TENANCY_PRIVATE_TENANT_NAME.equals(defaultTenant)) { throw new IllegalArgumentException("Private tenant can not be disabled if it is the default tenant."); } @@ -202,15 +186,17 @@ private void updateAndValidatesValues(final ConfigV7 config, final JsonNode json return; } - final Set availableTenants = - cl.getConfiguration(CType.TENANTS) - .getCEntries() - .keySet() - .stream() - .map(String::toLowerCase) - .collect(Collectors.toSet()); + final Set availableTenants = cl.getConfiguration(CType.TENANTS) + .getCEntries() + .keySet() + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); if (!availableTenants.contains(defaultTenant)) { - throw new IllegalArgumentException(config.dynamic.kibana.default_tenant + " can not be set to default tenant. Default tenant should be selected from one of the available tenants."); + throw new IllegalArgumentException( + config.dynamic.kibana.default_tenant + + " can not be set to default tenant. Default tenant should be selected from one of the available tenants." + ); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java index 22897b8305..6d44e4073c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/NodesDnApiAction.java @@ -60,27 +60,41 @@ public class NodesDnApiAction extends PatchableResourceApiAction { public static final String STATIC_OPENSEARCH_YML_NODES_DN = "STATIC_OPENSEARCH_YML_NODES_DN"; private final List staticNodesDnFromEsYml; - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/nodesdn/{name}"), new Route(Method.GET, "/nodesdn/"), new Route(Method.DELETE, "/nodesdn/{name}"), new Route(Method.PUT, "/nodesdn/{name}"), new Route(Method.PATCH, "/nodesdn/"), new Route(Method.PATCH, "/nodesdn/{name}") - )); + ) + ); @Inject - public NodesDnApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public NodesDnApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); this.staticNodesDnFromEsYml = settings.getAsList(ConfigConstants.SECURITY_NODES_DN, Collections.emptyList()); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -144,7 +158,7 @@ private void putStaticEntry(SecurityDynamicConfiguration configuration) { if (NodesDn.class.equals(configuration.getImplementingClass())) { NodesDn nodesDn = new NodesDn(); nodesDn.setNodesDn(staticNodesDnFromEsYml); - ((SecurityDynamicConfiguration)configuration).putCEntry(STATIC_OPENSEARCH_YML_NODES_DN, nodesDn); + ((SecurityDynamicConfiguration) configuration).putCEntry(STATIC_OPENSEARCH_YML_NODES_DN, nodesDn); } else { throw new RuntimeException("Unknown class type - " + configuration.getImplementingClass()); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java index 796a25fa9f..e6d1f1744a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PatchableResourceApiAction.java @@ -54,16 +54,23 @@ public abstract class PatchableResourceApiAction extends AbstractApiAction { protected final Logger log = LogManager.getLogger(this.getClass()); - public PatchableResourceApiAction(Settings settings, Path configPath, RestController controller, Client client, - AdminDNs adminDNs, ConfigurationRepository cl, ClusterService cs, - PrincipalExtractor principalExtractor, PrivilegesEvaluator evaluator, ThreadPool threadPool, - AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, - auditLog); + public PatchableResourceApiAction( + Settings settings, + Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + PrincipalExtractor principalExtractor, + PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } - private void handlePatch(RestChannel channel, final RestRequest request, final Client client) - throws IOException { + private void handlePatch(RestChannel channel, final RestRequest request, final Client client) throws IOException { if (request.getXContentType() != XContentType.JSON) { badRequestResponse(channel, "PATCH accepts only application/json"); return; @@ -103,8 +110,15 @@ private void handlePatch(RestChannel channel, final RestRequest request, final C } } - private void handleSinglePatch(RestChannel channel, RestRequest request, Client client, String name, - SecurityDynamicConfiguration existingConfiguration, ObjectNode existingAsObjectNode, JsonNode jsonPatch) throws IOException { + private void handleSinglePatch( + RestChannel channel, + RestRequest request, + Client client, + String name, + SecurityDynamicConfiguration existingConfiguration, + ObjectNode existingAsObjectNode, + JsonNode jsonPatch + ) throws IOException { if (!isWriteable(channel, existingConfiguration, name)) { return; } @@ -126,9 +140,15 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client return; } - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult(channel, request, existingResourceAsJsonNode, patchedResourceAsJsonNode, name); + AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + channel, + request, + existingResourceAsJsonNode, + patchedResourceAsJsonNode, + name + ); - if(originalValidator != null) { + if (originalValidator != null) { if (!originalValidator.validate()) { request.params().clear(); badRequestResponse(channel, originalValidator); @@ -152,8 +172,13 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client JsonNode updatedAsJsonNode = existingAsObjectNode.deepCopy().set(name, patchedResourceAsJsonNode); - SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode(updatedAsJsonNode, existingConfiguration.getCType() - , existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm()); + SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode( + updatedAsJsonNode, + existingConfiguration.getCType(), + existingConfiguration.getVersion(), + existingConfiguration.getSeqNo(), + existingConfiguration.getPrimaryTerm() + ); if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) { if (hasActionGroupSelfReference(mdc, name)) { @@ -162,18 +187,24 @@ private void handleSinglePatch(RestChannel channel, RestRequest request, Client } } - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel){ + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), mdc, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { successResponse(channel, "'" + name + "' updated."); } - }); + }); } - private void handleBulkPatch(RestChannel channel, RestRequest request, Client client, - SecurityDynamicConfiguration existingConfiguration, ObjectNode existingAsObjectNode, JsonNode jsonPatch) throws IOException { + private void handleBulkPatch( + RestChannel channel, + RestRequest request, + Client client, + SecurityDynamicConfiguration existingConfiguration, + ObjectNode existingAsObjectNode, + JsonNode jsonPatch + ) throws IOException { JsonNode patchedAsJsonNode; @@ -193,16 +224,21 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl } } - for (Iterator fieldNamesIter = patchedAsJsonNode.fieldNames(); fieldNamesIter.hasNext();) { String resourceName = fieldNamesIter.next(); JsonNode oldResource = existingAsObjectNode.get(resourceName); JsonNode patchedResource = patchedAsJsonNode.get(resourceName); - AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult(channel, request, oldResource, patchedResource, resourceName); + AbstractConfigurationValidator originalValidator = postProcessApplyPatchResult( + channel, + request, + oldResource, + patchedResource, + resourceName + ); - if(originalValidator != null) { + if (originalValidator != null) { if (!originalValidator.validate()) { request.params().clear(); badRequestResponse(channel, originalValidator); @@ -232,25 +268,30 @@ private void handleBulkPatch(RestChannel channel, RestRequest request, Client cl } } } - SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode(patchedAsJsonNode, existingConfiguration.getCType() - , existingConfiguration.getVersion(), existingConfiguration.getSeqNo(), existingConfiguration.getPrimaryTerm()); + SecurityDynamicConfiguration mdc = SecurityDynamicConfiguration.fromNode( + patchedAsJsonNode, + existingConfiguration.getCType(), + existingConfiguration.getVersion(), + existingConfiguration.getSeqNo(), + existingConfiguration.getPrimaryTerm() + ); if (existingConfiguration.getCType().equals(CType.ACTIONGROUPS)) { for (String actiongroup : mdc.getCEntries().keySet()) { - if(hasActionGroupSelfReference(mdc, actiongroup)) { + if (hasActionGroupSelfReference(mdc, actiongroup)) { badRequestResponse(channel, actiongroup + " cannot be an allowed_action of itself"); return; } } } - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), mdc, new OnSucessActionListener(channel) { + saveAndUpdateConfigs(this.securityIndexName, client, getConfigName(), mdc, new OnSucessActionListener(channel) { @Override public void onResponse(IndexResponse response) { successResponse(channel, "Resource updated."); } - }); + }); } @@ -258,14 +299,19 @@ private JsonNode applyPatch(JsonNode jsonPatch, JsonNode existingResourceAsJsonN return JsonPatch.apply(jsonPatch, existingResourceAsJsonNode); } - protected AbstractConfigurationValidator postProcessApplyPatchResult(RestChannel channel, RestRequest request, JsonNode existingResourceAsJsonNode, JsonNode updatedResourceAsJsonNode, String resourceName) { + protected AbstractConfigurationValidator postProcessApplyPatchResult( + RestChannel channel, + RestRequest request, + JsonNode existingResourceAsJsonNode, + JsonNode updatedResourceAsJsonNode, + String resourceName + ) { // do nothing by default return null; } @Override - protected void handleApiRequest(RestChannel channel, final RestRequest request, final Client client) - throws IOException { + protected void handleApiRequest(RestChannel channel, final RestRequest request, final Client client) throws IOException { if (request.method() == Method.PATCH) { handlePatch(channel, request, client); @@ -274,10 +320,10 @@ protected void handleApiRequest(RestChannel channel, final RestRequest request, } } - private AbstractConfigurationValidator getValidator(RestRequest request, JsonNode patchedResource) - throws JsonProcessingException { + private AbstractConfigurationValidator getValidator(RestRequest request, JsonNode patchedResource) throws JsonProcessingException { BytesReference patchedResourceAsByteReference = new BytesArray( - DefaultObjectMapper.objectMapper.writeValueAsString(patchedResource).getBytes(StandardCharsets.UTF_8)); + DefaultObjectMapper.objectMapper.writeValueAsString(patchedResource).getBytes(StandardCharsets.UTF_8) + ); return getValidator(request, patchedResourceAsByteReference); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java index d07be301bd..f57c4eb59c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/PermissionsInfoAction.java @@ -49,85 +49,104 @@ * Provides the evaluated REST API permissions for the currently logged in user */ public class PermissionsInfoAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/permissionsinfo") - )); - - private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; - private final ThreadPool threadPool; - private final PrivilegesEvaluator privilegesEvaluator; - private final ConfigurationRepository configurationRepository; - - protected PermissionsInfoAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository configurationRepository, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator privilegesEvaluator, ThreadPool threadPool, AuditLog auditLog) { - super(); - this.threadPool = threadPool; - this.privilegesEvaluator = privilegesEvaluator; - this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator(settings, adminDNs, privilegesEvaluator, principalExtractor, configPath, threadPool); - this.configurationRepository = configurationRepository; - } - - @Override - public String getName() { - return getClass().getSimpleName(); - } - - @Override - public List routes() { - return routes; - } - - @Override - protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { - switch (request.method()) { - case GET: - return handleGet(request, client); - default: - throw new IllegalArgumentException(request.method() + " not supported"); - } - } - - private RestChannelConsumer handleGet(RestRequest request, NodeClient client) throws IOException { + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/permissionsinfo"))); + + private final RestApiPrivilegesEvaluator restApiPrivilegesEvaluator; + private final ThreadPool threadPool; + private final PrivilegesEvaluator privilegesEvaluator; + private final ConfigurationRepository configurationRepository; + + protected PermissionsInfoAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository configurationRepository, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(); + this.threadPool = threadPool; + this.privilegesEvaluator = privilegesEvaluator; + this.restApiPrivilegesEvaluator = new RestApiPrivilegesEvaluator( + settings, + adminDNs, + privilegesEvaluator, + principalExtractor, + configPath, + threadPool + ); + this.configurationRepository = configurationRepository; + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } + + @Override + public List routes() { + return routes; + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + switch (request.method()) { + case GET: + return handleGet(request, client); + default: + throw new IllegalArgumentException(request.method() + " not supported"); + } + } + + private RestChannelConsumer handleGet(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final TransportAddress remoteAddress = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); - Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); - Boolean hasApiAccess = restApiPrivilegesEvaluator.currentUserHasRestApiAccess(userRoles); - Map> disabledEndpoints = restApiPrivilegesEvaluator.getDisabledEndpointsForCurrentUser(user.getName(), userRoles); - if (!configurationRepository.isAuditHotReloadingEnabled()) { - disabledEndpoints.put(Endpoint.AUDIT, ImmutableList.copyOf(Method.values())); - } + final User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final TransportAddress remoteAddress = threadPool.getThreadContext() + .getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); + Boolean hasApiAccess = restApiPrivilegesEvaluator.currentUserHasRestApiAccess(userRoles); + Map> disabledEndpoints = restApiPrivilegesEvaluator.getDisabledEndpointsForCurrentUser( + user.getName(), + userRoles + ); + if (!configurationRepository.isAuditHotReloadingEnabled()) { + disabledEndpoints.put(Endpoint.AUDIT, ImmutableList.copyOf(Method.values())); + } builder.startObject(); - builder.field("user", user==null?null:user.toString()); - builder.field("user_name", user==null?null:user.getName()); //NOSONAR + builder.field("user", user == null ? null : user.toString()); + builder.field("user_name", user == null ? null : user.getName()); // NOSONAR builder.field("has_api_access", hasApiAccess); builder.startObject("disabled_endpoints"); - for(Entry> entry : disabledEndpoints.entrySet()) { - builder.field(entry.getKey().name(), entry.getValue()); + for (Entry> entry : disabledEndpoints.entrySet()) { + builder.field(entry.getKey().name(), entry.getValue()); } builder.endObject(); builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { e1.printStackTrace(); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -136,6 +155,6 @@ public void accept(RestChannel channel) throws Exception { } }; - } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java index c8e44ee4ca..b0a0d828cc 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiAdminPrivilegesEvaluator.java @@ -41,14 +41,11 @@ public class RestApiAdminPrivilegesEvaluator { private final static String REST_API_PERMISSION_PREFIX = "restapi:admin"; - private final static String REST_ENDPOINT_PERMISSION_PATTERN = - REST_API_PERMISSION_PREFIX + "/%s"; + private final static String REST_ENDPOINT_PERMISSION_PATTERN = REST_API_PERMISSION_PREFIX + "/%s"; - private final static String REST_ENDPOINT_ACTION_PERMISSION_PATTERN = - REST_API_PERMISSION_PREFIX + "/%s/%s"; + private final static String REST_ENDPOINT_ACTION_PERMISSION_PATTERN = REST_API_PERMISSION_PREFIX + "/%s/%s"; - private final static WildcardMatcher REST_API_PERMISSION_PREFIX_MATCHER = - WildcardMatcher.from(REST_API_PERMISSION_PREFIX + "/*"); + private final static WildcardMatcher REST_API_PERMISSION_PREFIX_MATCHER = WildcardMatcher.from(REST_API_PERMISSION_PREFIX + "/*"); @FunctionalInterface public interface PermissionBuilder { @@ -61,25 +58,25 @@ default String build() { } - public final static Map ENDPOINTS_WITH_PERMISSIONS = - ImmutableMap.builder() - .put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS)) - .put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST)) - .put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS)) - .put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN)) - .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES)) - .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING)) - .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS)) - .put(Endpoint.SSL, action -> { - switch (action) { - case CERTS_INFO_ACTION: - return buildEndpointActionPermission(Endpoint.SSL, "certs/info"); - case RELOAD_CERTS_ACTION: - return buildEndpointActionPermission(Endpoint.SSL, "certs/reload"); - default: - return null; - } - }).build(); + public final static Map ENDPOINTS_WITH_PERMISSIONS = ImmutableMap.builder() + .put(Endpoint.ACTIONGROUPS, action -> buildEndpointPermission(Endpoint.ACTIONGROUPS)) + .put(Endpoint.ALLOWLIST, action -> buildEndpointPermission(Endpoint.ALLOWLIST)) + .put(Endpoint.INTERNALUSERS, action -> buildEndpointPermission(Endpoint.INTERNALUSERS)) + .put(Endpoint.NODESDN, action -> buildEndpointPermission(Endpoint.NODESDN)) + .put(Endpoint.ROLES, action -> buildEndpointPermission(Endpoint.ROLES)) + .put(Endpoint.ROLESMAPPING, action -> buildEndpointPermission(Endpoint.ROLESMAPPING)) + .put(Endpoint.TENANTS, action -> buildEndpointPermission(Endpoint.TENANTS)) + .put(Endpoint.SSL, action -> { + switch (action) { + case CERTS_INFO_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/info"); + case RELOAD_CERTS_ACTION: + return buildEndpointActionPermission(Endpoint.SSL, "certs/reload"); + default: + return null; + } + }) + .build(); private final ThreadContext threadContext; @@ -90,10 +87,11 @@ default String build() { private final boolean restapiAdminEnabled; public RestApiAdminPrivilegesEvaluator( - final ThreadContext threadContext, - final PrivilegesEvaluator privilegesEvaluator, - final AdminDNs adminDNs, - final boolean restapiAdminEnabled) { + final ThreadContext threadContext, + final PrivilegesEvaluator privilegesEvaluator, + final AdminDNs adminDNs, + final boolean restapiAdminEnabled + ) { this.threadContext = threadContext; this.privilegesEvaluator = privilegesEvaluator; this.adminDNs = adminDNs; @@ -108,8 +106,10 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final Strin if (adminDNs.isAdmin(userAndRemoteAddress.getLeft())) { if (logger.isDebugEnabled()) { logger.debug( - "Security admin permissions required for endpoint {} but {} is not an admin", - endpoint, userAndRemoteAddress.getLeft().getName()); + "Security admin permissions required for endpoint {} but {} is not an admin", + endpoint, + userAndRemoteAddress.getLeft().getName() + ); } return true; } @@ -119,23 +119,23 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint, final Strin } final String permission = ENDPOINTS_WITH_PERMISSIONS.get(endpoint).build(action); final boolean hasAccess = privilegesEvaluator.hasRestAdminPermissions( - userAndRemoteAddress.getLeft(), - userAndRemoteAddress.getRight(), - permission + userAndRemoteAddress.getLeft(), + userAndRemoteAddress.getRight(), + permission ); if (logger.isDebugEnabled()) { logger.debug( - "User {} with permission {} {} access to endpoint {}", - userAndRemoteAddress.getLeft().getName(), - permission, - hasAccess ? "has" : "has no", - endpoint + "User {} with permission {} {} access to endpoint {}", + userAndRemoteAddress.getLeft().getName(), + permission, + hasAccess ? "has" : "has no", + endpoint ); logger.debug( - "{} set to {}. {} use access decision", - SECURITY_RESTAPI_ADMIN_ENABLED, - restapiAdminEnabled, - restapiAdminEnabled ? "Will" : "Will not" + "{} set to {}. {} use access decision", + SECURITY_RESTAPI_ADMIN_ENABLED, + restapiAdminEnabled, + restapiAdminEnabled ? "Will" : "Will not" ); } return hasAccess && restapiAdminEnabled; @@ -146,15 +146,9 @@ public boolean containsRestApiAdminPermissions(final Object configObject) { return false; } if (configObject instanceof RoleV7) { - return ((RoleV7) configObject) - .getCluster_permissions() - .stream() - .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + return ((RoleV7) configObject).getCluster_permissions().stream().anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); } else if (configObject instanceof ActionGroupsV7) { - return ((ActionGroupsV7) configObject) - .getAllowed_actions() - .stream() - .anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); + return ((ActionGroupsV7) configObject).getAllowed_actions().stream().anyMatch(REST_API_PERMISSION_PREFIX_MATCHER); } else { return false; } @@ -165,17 +159,11 @@ public boolean isCurrentUserRestApiAdminFor(final Endpoint endpoint) { } private static String buildEndpointActionPermission(final Endpoint endpoint, final String action) { - return String.format( - REST_ENDPOINT_ACTION_PERMISSION_PATTERN, - endpoint.name().toLowerCase(Locale.ROOT), - action); + return String.format(REST_ENDPOINT_ACTION_PERMISSION_PATTERN, endpoint.name().toLowerCase(Locale.ROOT), action); } private static String buildEndpointPermission(final Endpoint endpoint) { - return String.format( - REST_ENDPOINT_PERMISSION_PATTERN, - endpoint.name().toLowerCase(Locale.ROOT) - ); + return String.format(REST_ENDPOINT_PERMISSION_PATTERN, endpoint.name().toLowerCase(Locale.ROOT)); } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java index 04b4b31c77..96787bb4f5 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RestApiPrivilegesEvaluator.java @@ -45,390 +45,431 @@ // TODO: Make Singleton? public class RestApiPrivilegesEvaluator { - protected final Logger logger = LogManager.getLogger(this.getClass()); - - private final AdminDNs adminDNs; - private final PrivilegesEvaluator privilegesEvaluator; - private final PrincipalExtractor principalExtractor; - private final Path configPath; - private final ThreadPool threadPool; - private final Settings settings; - - private final Set allowedRoles = new HashSet<>(); - - // endpoints per role, read and cached from settings. Changes here require a - // node restart, so it's save to cache. - private final Map>> disabledEndpointsForRoles = new HashMap<>(); - - // endpoints per user, evaluated and cached dynamically. Changes here - // require a node restart, so it's save to cache. - private final Map>> disabledEndpointsForUsers = new HashMap<>(); - - // globally disabled endpoints and methods, will always be forbidden - Map> globallyDisabledEndpoints = new HashMap<>(); - - // all endpoints and methods, will be returned for users that do not have any access at all - Map> allEndpoints = new HashMap<>(); - - private final Boolean roleBasedAccessEnabled; - - public RestApiPrivilegesEvaluator(final Settings settings, - final AdminDNs adminDNs, - final PrivilegesEvaluator privilegesEvaluator, - final PrincipalExtractor principalExtractor, - final Path configPath, - ThreadPool threadPool) { - - this.adminDNs = adminDNs; - this.privilegesEvaluator = privilegesEvaluator; - this.principalExtractor = principalExtractor; - this.configPath = configPath; - this.threadPool = threadPool; - this.settings = settings; - // set up - // all endpoints and methods - Map> allEndpoints = new HashMap<>(); - for(Endpoint endpoint : Endpoint.values()) { - List allMethods = new LinkedList<>(); - allMethods.addAll(Arrays.asList(Method.values())); - allEndpoints.put(endpoint, allMethods); - } - this.allEndpoints = Collections.unmodifiableMap(allEndpoints); - - // setup role based permissions - allowedRoles.addAll(settings.getAsList(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED)); - - this.roleBasedAccessEnabled = !allowedRoles.isEmpty(); - - // globally disabled endpoints, disables access to Endpoint/Method combination for all roles - Settings globalSettings = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".global"); - if (!globalSettings.isEmpty()) { - globallyDisabledEndpoints = parseDisabledEndpoints(globalSettings); - } - - final boolean isDebugEnabled = logger.isDebugEnabled(); - if (isDebugEnabled) { - logger.debug("Globally disabled endpoints: {}", globallyDisabledEndpoints); - } - - for (String role : allowedRoles) { - Settings settingsForRole = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + "." + role); - if (settingsForRole.isEmpty()) { - if (isDebugEnabled) { - logger.debug("No disabled endpoints/methods for permitted role {} found, allowing all", role); - } - continue; - } - Map> disabledEndpointsForRole = parseDisabledEndpoints(settingsForRole); - if (!disabledEndpointsForRole.isEmpty()) { - disabledEndpointsForRoles.put(role, disabledEndpointsForRole); - } else { - logger.warn("Disabled endpoints/methods empty for role {}, please check configuration", role); - } - } - if (logger.isTraceEnabled()) { - logger.trace("Parsed permission set for endpoints: {}", disabledEndpointsForRoles); - } - } - - @SuppressWarnings({ "rawtypes" }) - private Map> parseDisabledEndpoints(Settings settings) { - - // Expects Setting like: 'ACTIONGROUPS=["GET", "POST"]' - if (settings == null || settings.isEmpty()) { - logger.error("Settings for disabled endpoint is null or empty: '{}', skipping.", settings); - return Collections.emptyMap(); - } - - final Map> disabledEndpoints = new HashMap>(); - - Map disabledEndpointsSettings = Utils.convertJsonToxToStructuredMap(settings); - - for (Entry value : disabledEndpointsSettings.entrySet()) { - // key is the endpoint, see if it is a valid one - String endpointString = value.getKey().toUpperCase(); - Endpoint endpoint = null; - try { - endpoint = Endpoint.valueOf(endpointString); - } catch (Exception e) { - logger.error("Unknown endpoint '{}' found in configuration, skipping.", endpointString); - continue; - } - // value must be non null - if (value.getValue() == null) { - logger.error("Disabled HTTP methods of endpoint '{}' is null, skipping.", endpointString); - continue; - } - - // value must be an array of methods - if (!(value.getValue() instanceof Collection)) { - logger.error("Disabled HTTP methods of endpoint '{}' must be an array, actually is '{}', skipping.", endpointString, (value.getValue().toString())); - } - List disabledMethods = new LinkedList<>(); - for (Object disabledMethodObj : (Collection) value.getValue()) { - if (disabledMethodObj == null) { - logger.error("Found null value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); - continue; - } - - if (!(disabledMethodObj instanceof String)) { - logger.error("Found non-String value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); - continue; - } - - String disabledMethodAsString = (String) disabledMethodObj; - - // Provide support for '*', means all methods - if (disabledMethodAsString.trim().equals("*")) { - disabledMethods.addAll(Arrays.asList(Method.values())); - break; - } - // no wild card, disabled method must be one of - // RestRequest.Method - Method disabledMethod = null; - try { - disabledMethod = Method.valueOf(disabledMethodAsString.toUpperCase()); - } catch (Exception e) { - logger.error("Invalid HTTP method '{}' found in disabled HTTP methods of endpoint '{}', skipping.", disabledMethodAsString.toUpperCase(), endpointString); - continue; - } - disabledMethods.add(disabledMethod); - } - - disabledEndpoints.put(endpoint, disabledMethods); - - } - return disabledEndpoints; - } - - /** - * Check if the current request is allowed to use the REST API and the - * requested end point. Using an admin certificate grants all permissions. A - * user/role can have restricted end points. - * - * @return an error message if user does not have access, null otherwise - * TODO: log failed attempt in audit log - */ - public String checkAccessPermissions(RestRequest request, Endpoint endpoint) throws IOException { - - if (logger.isDebugEnabled()) { - logger.debug("Checking admin access for endpoint {}, path {} and method {}", endpoint.name(), request.path(), request.method().name()); - } - - // Grant permission for Account endpoint. - // Return null to grant access. - if (endpoint == Endpoint.ACCOUNT) { - return null; - } - - String roleBasedAccessFailureReason = checkRoleBasedAccessPermissions(request, endpoint); - // Role based access granted - if (roleBasedAccessFailureReason == null) { - return null; - } - - String certBasedAccessFailureReason = checkAdminCertBasedAccessPermissions(request); - // TLS access granted, skip checking roles - if (certBasedAccessFailureReason == null) { - return null; - } - - - return constructAccessErrorMessage(roleBasedAccessFailureReason, certBasedAccessFailureReason); - } - - public Boolean currentUserHasRestApiAccess(Set userRoles) { - - // check if user has any role that grants access - return !Collections.disjoint(allowedRoles, userRoles); - - } - - public Map> getDisabledEndpointsForCurrentUser(String userPrincipal, Set userRoles) { - - final boolean isDebugEnabled = logger.isDebugEnabled(); - - // cache - if (disabledEndpointsForUsers.containsKey(userPrincipal)) { - return disabledEndpointsForUsers.get(userPrincipal); - } - - if (!currentUserHasRestApiAccess(userRoles)) { - return this.allEndpoints; - } - - // will contain the final list of disabled endpoints and methods - Map> finalEndpoints = new HashMap<>(); - - // List of all disabled endpoints for user. Disabled endpoints must be configured in all - // roles to take effect. If a role contains a disabled endpoint, but another role - // allows this endpoint (i.e. not contained in the disabled endpoints for this role), - // the access is allowed. - - // make list mutable - List remainingEndpoints = new LinkedList<>(Arrays.asList(Endpoint.values())); - - // only retain endpoints contained in all roles for user - boolean hasDisabledEndpoints = false; - for (String userRole : userRoles) { - Map> endpointsForRole = disabledEndpointsForRoles.get(userRole); - if (endpointsForRole == null || endpointsForRole.isEmpty()) { - continue; - } - Set disabledEndpoints = endpointsForRole.keySet(); - remainingEndpoints.retainAll(disabledEndpoints); - hasDisabledEndpoints = true; - } - - if (isDebugEnabled) { - logger.debug("Remaining endpoints for user {} after retaining all : {}", userPrincipal, remainingEndpoints); - } - - // if user does not have any disabled endpoints, only globally disabled endpoints apply - if (!hasDisabledEndpoints) { - - if (isDebugEnabled) { - logger.debug("No disabled endpoints for user {} at all, only globally disabledendpoints apply.", userPrincipal, remainingEndpoints); - } - disabledEndpointsForUsers.put(userPrincipal, addGloballyDisabledEndpoints(finalEndpoints)); - return finalEndpoints; - - } - - // one or more disabled remaining endpoints, keep only - // methods contained in all roles for each endpoint - for (Endpoint endpoint : remainingEndpoints) { - // make list mutable - List remainingMethodsForEndpoint = new LinkedList<>(Arrays.asList(Method.values())); - for (String userRole : userRoles) { - Map> endpoints = disabledEndpointsForRoles.get(userRole); - if (endpoints != null && !endpoints.isEmpty()) { - remainingMethodsForEndpoint.retainAll(endpoints.get(endpoint)); - } - } - - finalEndpoints.put(endpoint, remainingMethodsForEndpoint); - } - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, finalEndpoints); - } - - // add globally disabled endpoints and methods, will always be disabled - addGloballyDisabledEndpoints(finalEndpoints); - disabledEndpointsForUsers.put(userPrincipal, finalEndpoints); - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, disabledEndpointsForUsers.get(userPrincipal)); - } - - return disabledEndpointsForUsers.get(userPrincipal); - } - - private Map> addGloballyDisabledEndpoints(Map> endpoints) { - if(globallyDisabledEndpoints != null && !globallyDisabledEndpoints.isEmpty()) { - Set globalEndoints = globallyDisabledEndpoints.keySet(); - for(Endpoint endpoint : globalEndoints) { - endpoints.putIfAbsent(endpoint, new LinkedList<>()); - endpoints.get(endpoint).addAll(globallyDisabledEndpoints.get(endpoint)); - } - } - return endpoints; - } - - private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint endpoint) { - final boolean isTraceEnabled = logger.isTraceEnabled(); - if (isTraceEnabled) { - logger.trace("Checking role based admin access for endpoint {} and method {}", endpoint.name(), request.method().name()); - } - final boolean isDebugEnabled = logger.isDebugEnabled(); - // Role based access. Check that user has role suitable for admin access - // and that the role has also access to this endpoint. - if (this.roleBasedAccessEnabled) { - - // get current user and roles - final Pair userAndRemoteAddress = - Utils.userAndRemoteAddressFrom(threadPool.getThreadContext()); - final User user = userAndRemoteAddress.getLeft(); - final TransportAddress remoteAddress = userAndRemoteAddress.getRight(); - - // map the users Security roles - Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); - - // check if user has any role that grants access - if (currentUserHasRestApiAccess(userRoles)) { - // yes, calculate disabled end points. Since a user can have - // multiple roles, the endpoint - // needs to be disabled in all roles. - Map> disabledEndpointsForUser = getDisabledEndpointsForCurrentUser(user.getName(), userRoles); - - if (isDebugEnabled) { - logger.debug("Disabled endpoints for user {} : {} ", user, disabledEndpointsForUser); - } - - // check if we have any disabled methods for this endpoint - List disabledMethodsForEndpoint = disabledEndpointsForUser.get(endpoint); - - // no settings, all methods for this endpoint allowed - if (disabledMethodsForEndpoint == null || disabledMethodsForEndpoint.isEmpty()) { - if (isDebugEnabled) { - logger.debug("No disabled methods for user {} and endpoint {}, access allowed ", user, endpoint); - } - return null; - } - - // some methods disabled, check requested method - if (!disabledMethodsForEndpoint.contains(request.method())) { - if (isDebugEnabled) { - logger.debug("Request method {} for user {} and endpoint {} not restricted, access allowed ", request.method(), user, endpoint); - } - return null; - } - - logger.info("User {} with Security roles {} does not have access to endpoint {} and method {}, checking admin TLS certificate now.", user, userRoles, - endpoint.name(), request.method()); - return "User " + user.getName() + " with Security roles " + userRoles + " does not have any access to endpoint " + endpoint.name() + " and method " - + request.method().name(); - } else { - // no, but maybe the request contains a client certificate. - // Remember error reason for better response message later on. - logger.info("User {} with Security roles {} does not have any role privileged for admin access.", user, userRoles); - return "User " + user.getName() + " with Security roles " + userRoles + " does not have any role privileged for admin access"; - } - } - return "Role based access not enabled."; - } - - private String checkAdminCertBasedAccessPermissions(RestRequest request) throws IOException { - if (logger.isTraceEnabled()) { - logger.trace("Checking certificate based admin access for path {} and method {}", request.path(), request.method().name()); - } - - // Certificate based access, Check if we have an admin TLS certificate - SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); - - if (sslInfo == null) { - // here we log on error level, since authentication finally failed - logger.warn("No ssl info found in request."); - return "No ssl info found in request."; - } - - X509Certificate[] certs = sslInfo.getX509Certs(); - - if (certs == null || certs.length == 0) { - logger.warn("No client TLS certificate found in request"); - return "No client TLS certificate found in request"; - } - - if (!adminDNs.isAdminDN(sslInfo.getPrincipal())) { - logger.warn("Security admin permissions required but {} is not an admin", sslInfo.getPrincipal()); - return "Security admin permissions required but " + sslInfo.getPrincipal() + " is not an admin"; - } - return null; - } - - private String constructAccessErrorMessage(String roleBasedAccessFailure, String certBasedAccessFailure) { - return roleBasedAccessFailure + ". " + certBasedAccessFailure; - } + protected final Logger logger = LogManager.getLogger(this.getClass()); + + private final AdminDNs adminDNs; + private final PrivilegesEvaluator privilegesEvaluator; + private final PrincipalExtractor principalExtractor; + private final Path configPath; + private final ThreadPool threadPool; + private final Settings settings; + + private final Set allowedRoles = new HashSet<>(); + + // endpoints per role, read and cached from settings. Changes here require a + // node restart, so it's save to cache. + private final Map>> disabledEndpointsForRoles = new HashMap<>(); + + // endpoints per user, evaluated and cached dynamically. Changes here + // require a node restart, so it's save to cache. + private final Map>> disabledEndpointsForUsers = new HashMap<>(); + + // globally disabled endpoints and methods, will always be forbidden + Map> globallyDisabledEndpoints = new HashMap<>(); + + // all endpoints and methods, will be returned for users that do not have any access at all + Map> allEndpoints = new HashMap<>(); + + private final Boolean roleBasedAccessEnabled; + + public RestApiPrivilegesEvaluator( + final Settings settings, + final AdminDNs adminDNs, + final PrivilegesEvaluator privilegesEvaluator, + final PrincipalExtractor principalExtractor, + final Path configPath, + ThreadPool threadPool + ) { + + this.adminDNs = adminDNs; + this.privilegesEvaluator = privilegesEvaluator; + this.principalExtractor = principalExtractor; + this.configPath = configPath; + this.threadPool = threadPool; + this.settings = settings; + // set up + // all endpoints and methods + Map> allEndpoints = new HashMap<>(); + for (Endpoint endpoint : Endpoint.values()) { + List allMethods = new LinkedList<>(); + allMethods.addAll(Arrays.asList(Method.values())); + allEndpoints.put(endpoint, allMethods); + } + this.allEndpoints = Collections.unmodifiableMap(allEndpoints); + + // setup role based permissions + allowedRoles.addAll(settings.getAsList(ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED)); + + this.roleBasedAccessEnabled = !allowedRoles.isEmpty(); + + // globally disabled endpoints, disables access to Endpoint/Method combination for all roles + Settings globalSettings = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".global"); + if (!globalSettings.isEmpty()) { + globallyDisabledEndpoints = parseDisabledEndpoints(globalSettings); + } + + final boolean isDebugEnabled = logger.isDebugEnabled(); + if (isDebugEnabled) { + logger.debug("Globally disabled endpoints: {}", globallyDisabledEndpoints); + } + + for (String role : allowedRoles) { + Settings settingsForRole = settings.getAsSettings(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + "." + role); + if (settingsForRole.isEmpty()) { + if (isDebugEnabled) { + logger.debug("No disabled endpoints/methods for permitted role {} found, allowing all", role); + } + continue; + } + Map> disabledEndpointsForRole = parseDisabledEndpoints(settingsForRole); + if (!disabledEndpointsForRole.isEmpty()) { + disabledEndpointsForRoles.put(role, disabledEndpointsForRole); + } else { + logger.warn("Disabled endpoints/methods empty for role {}, please check configuration", role); + } + } + if (logger.isTraceEnabled()) { + logger.trace("Parsed permission set for endpoints: {}", disabledEndpointsForRoles); + } + } + + @SuppressWarnings({ "rawtypes" }) + private Map> parseDisabledEndpoints(Settings settings) { + + // Expects Setting like: 'ACTIONGROUPS=["GET", "POST"]' + if (settings == null || settings.isEmpty()) { + logger.error("Settings for disabled endpoint is null or empty: '{}', skipping.", settings); + return Collections.emptyMap(); + } + + final Map> disabledEndpoints = new HashMap>(); + + Map disabledEndpointsSettings = Utils.convertJsonToxToStructuredMap(settings); + + for (Entry value : disabledEndpointsSettings.entrySet()) { + // key is the endpoint, see if it is a valid one + String endpointString = value.getKey().toUpperCase(); + Endpoint endpoint = null; + try { + endpoint = Endpoint.valueOf(endpointString); + } catch (Exception e) { + logger.error("Unknown endpoint '{}' found in configuration, skipping.", endpointString); + continue; + } + // value must be non null + if (value.getValue() == null) { + logger.error("Disabled HTTP methods of endpoint '{}' is null, skipping.", endpointString); + continue; + } + + // value must be an array of methods + if (!(value.getValue() instanceof Collection)) { + logger.error( + "Disabled HTTP methods of endpoint '{}' must be an array, actually is '{}', skipping.", + endpointString, + (value.getValue().toString()) + ); + } + List disabledMethods = new LinkedList<>(); + for (Object disabledMethodObj : (Collection) value.getValue()) { + if (disabledMethodObj == null) { + logger.error("Found null value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); + continue; + } + + if (!(disabledMethodObj instanceof String)) { + logger.error("Found non-String value in disabled HTTP methods of endpoint '{}', skipping.", endpointString); + continue; + } + + String disabledMethodAsString = (String) disabledMethodObj; + + // Provide support for '*', means all methods + if (disabledMethodAsString.trim().equals("*")) { + disabledMethods.addAll(Arrays.asList(Method.values())); + break; + } + // no wild card, disabled method must be one of + // RestRequest.Method + Method disabledMethod = null; + try { + disabledMethod = Method.valueOf(disabledMethodAsString.toUpperCase()); + } catch (Exception e) { + logger.error( + "Invalid HTTP method '{}' found in disabled HTTP methods of endpoint '{}', skipping.", + disabledMethodAsString.toUpperCase(), + endpointString + ); + continue; + } + disabledMethods.add(disabledMethod); + } + + disabledEndpoints.put(endpoint, disabledMethods); + + } + return disabledEndpoints; + } + + /** + * Check if the current request is allowed to use the REST API and the + * requested end point. Using an admin certificate grants all permissions. A + * user/role can have restricted end points. + * + * @return an error message if user does not have access, null otherwise + * TODO: log failed attempt in audit log + */ + public String checkAccessPermissions(RestRequest request, Endpoint endpoint) throws IOException { + + if (logger.isDebugEnabled()) { + logger.debug( + "Checking admin access for endpoint {}, path {} and method {}", + endpoint.name(), + request.path(), + request.method().name() + ); + } + + // Grant permission for Account endpoint. + // Return null to grant access. + if (endpoint == Endpoint.ACCOUNT) { + return null; + } + + String roleBasedAccessFailureReason = checkRoleBasedAccessPermissions(request, endpoint); + // Role based access granted + if (roleBasedAccessFailureReason == null) { + return null; + } + + String certBasedAccessFailureReason = checkAdminCertBasedAccessPermissions(request); + // TLS access granted, skip checking roles + if (certBasedAccessFailureReason == null) { + return null; + } + + return constructAccessErrorMessage(roleBasedAccessFailureReason, certBasedAccessFailureReason); + } + + public Boolean currentUserHasRestApiAccess(Set userRoles) { + + // check if user has any role that grants access + return !Collections.disjoint(allowedRoles, userRoles); + + } + + public Map> getDisabledEndpointsForCurrentUser(String userPrincipal, Set userRoles) { + + final boolean isDebugEnabled = logger.isDebugEnabled(); + + // cache + if (disabledEndpointsForUsers.containsKey(userPrincipal)) { + return disabledEndpointsForUsers.get(userPrincipal); + } + + if (!currentUserHasRestApiAccess(userRoles)) { + return this.allEndpoints; + } + + // will contain the final list of disabled endpoints and methods + Map> finalEndpoints = new HashMap<>(); + + // List of all disabled endpoints for user. Disabled endpoints must be configured in all + // roles to take effect. If a role contains a disabled endpoint, but another role + // allows this endpoint (i.e. not contained in the disabled endpoints for this role), + // the access is allowed. + + // make list mutable + List remainingEndpoints = new LinkedList<>(Arrays.asList(Endpoint.values())); + + // only retain endpoints contained in all roles for user + boolean hasDisabledEndpoints = false; + for (String userRole : userRoles) { + Map> endpointsForRole = disabledEndpointsForRoles.get(userRole); + if (endpointsForRole == null || endpointsForRole.isEmpty()) { + continue; + } + Set disabledEndpoints = endpointsForRole.keySet(); + remainingEndpoints.retainAll(disabledEndpoints); + hasDisabledEndpoints = true; + } + + if (isDebugEnabled) { + logger.debug("Remaining endpoints for user {} after retaining all : {}", userPrincipal, remainingEndpoints); + } + + // if user does not have any disabled endpoints, only globally disabled endpoints apply + if (!hasDisabledEndpoints) { + + if (isDebugEnabled) { + logger.debug( + "No disabled endpoints for user {} at all, only globally disabledendpoints apply.", + userPrincipal, + remainingEndpoints + ); + } + disabledEndpointsForUsers.put(userPrincipal, addGloballyDisabledEndpoints(finalEndpoints)); + return finalEndpoints; + + } + + // one or more disabled remaining endpoints, keep only + // methods contained in all roles for each endpoint + for (Endpoint endpoint : remainingEndpoints) { + // make list mutable + List remainingMethodsForEndpoint = new LinkedList<>(Arrays.asList(Method.values())); + for (String userRole : userRoles) { + Map> endpoints = disabledEndpointsForRoles.get(userRole); + if (endpoints != null && !endpoints.isEmpty()) { + remainingMethodsForEndpoint.retainAll(endpoints.get(endpoint)); + } + } + + finalEndpoints.put(endpoint, remainingMethodsForEndpoint); + } + + if (isDebugEnabled) { + logger.debug("Disabled endpoints for user {} after retaining all : {}", userPrincipal, finalEndpoints); + } + + // add globally disabled endpoints and methods, will always be disabled + addGloballyDisabledEndpoints(finalEndpoints); + disabledEndpointsForUsers.put(userPrincipal, finalEndpoints); + + if (isDebugEnabled) { + logger.debug( + "Disabled endpoints for user {} after retaining all : {}", + userPrincipal, + disabledEndpointsForUsers.get(userPrincipal) + ); + } + + return disabledEndpointsForUsers.get(userPrincipal); + } + + private Map> addGloballyDisabledEndpoints(Map> endpoints) { + if (globallyDisabledEndpoints != null && !globallyDisabledEndpoints.isEmpty()) { + Set globalEndoints = globallyDisabledEndpoints.keySet(); + for (Endpoint endpoint : globalEndoints) { + endpoints.putIfAbsent(endpoint, new LinkedList<>()); + endpoints.get(endpoint).addAll(globallyDisabledEndpoints.get(endpoint)); + } + } + return endpoints; + } + + private String checkRoleBasedAccessPermissions(RestRequest request, Endpoint endpoint) { + final boolean isTraceEnabled = logger.isTraceEnabled(); + if (isTraceEnabled) { + logger.trace("Checking role based admin access for endpoint {} and method {}", endpoint.name(), request.method().name()); + } + final boolean isDebugEnabled = logger.isDebugEnabled(); + // Role based access. Check that user has role suitable for admin access + // and that the role has also access to this endpoint. + if (this.roleBasedAccessEnabled) { + + // get current user and roles + final Pair userAndRemoteAddress = Utils.userAndRemoteAddressFrom(threadPool.getThreadContext()); + final User user = userAndRemoteAddress.getLeft(); + final TransportAddress remoteAddress = userAndRemoteAddress.getRight(); + + // map the users Security roles + Set userRoles = privilegesEvaluator.mapRoles(user, remoteAddress); + + // check if user has any role that grants access + if (currentUserHasRestApiAccess(userRoles)) { + // yes, calculate disabled end points. Since a user can have + // multiple roles, the endpoint + // needs to be disabled in all roles. + Map> disabledEndpointsForUser = getDisabledEndpointsForCurrentUser(user.getName(), userRoles); + + if (isDebugEnabled) { + logger.debug("Disabled endpoints for user {} : {} ", user, disabledEndpointsForUser); + } + + // check if we have any disabled methods for this endpoint + List disabledMethodsForEndpoint = disabledEndpointsForUser.get(endpoint); + + // no settings, all methods for this endpoint allowed + if (disabledMethodsForEndpoint == null || disabledMethodsForEndpoint.isEmpty()) { + if (isDebugEnabled) { + logger.debug("No disabled methods for user {} and endpoint {}, access allowed ", user, endpoint); + } + return null; + } + + // some methods disabled, check requested method + if (!disabledMethodsForEndpoint.contains(request.method())) { + if (isDebugEnabled) { + logger.debug( + "Request method {} for user {} and endpoint {} not restricted, access allowed ", + request.method(), + user, + endpoint + ); + } + return null; + } + + logger.info( + "User {} with Security roles {} does not have access to endpoint {} and method {}, checking admin TLS certificate now.", + user, + userRoles, + endpoint.name(), + request.method() + ); + return "User " + + user.getName() + + " with Security roles " + + userRoles + + " does not have any access to endpoint " + + endpoint.name() + + " and method " + + request.method().name(); + } else { + // no, but maybe the request contains a client certificate. + // Remember error reason for better response message later on. + logger.info("User {} with Security roles {} does not have any role privileged for admin access.", user, userRoles); + return "User " + + user.getName() + + " with Security roles " + + userRoles + + " does not have any role privileged for admin access"; + } + } + return "Role based access not enabled."; + } + + private String checkAdminCertBasedAccessPermissions(RestRequest request) throws IOException { + if (logger.isTraceEnabled()) { + logger.trace("Checking certificate based admin access for path {} and method {}", request.path(), request.method().name()); + } + + // Certificate based access, Check if we have an admin TLS certificate + SSLRequestHelper.SSLInfo sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor); + + if (sslInfo == null) { + // here we log on error level, since authentication finally failed + logger.warn("No ssl info found in request."); + return "No ssl info found in request."; + } + + X509Certificate[] certs = sslInfo.getX509Certs(); + + if (certs == null || certs.length == 0) { + logger.warn("No client TLS certificate found in request"); + return "No client TLS certificate found in request"; + } + + if (!adminDNs.isAdminDN(sslInfo.getPrincipal())) { + logger.warn("Security admin permissions required but {} is not an admin", sslInfo.getPrincipal()); + return "Security admin permissions required but " + sslInfo.getPrincipal() + " is not an admin"; + } + return null; + } + + private String constructAccessErrorMessage(String roleBasedAccessFailure, String certBasedAccessFailure) { + return roleBasedAccessFailure + ". " + certBasedAccessFailure; + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java index 7b2676c246..65dbaac05e 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesApiAction.java @@ -39,62 +39,79 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class RolesApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/roles/"), - new Route(Method.GET, "/roles/{name}"), - new Route(Method.DELETE, "/roles/{name}"), - new Route(Method.PUT, "/roles/{name}"), - new Route(Method.PATCH, "/roles/"), - new Route(Method.PATCH, "/roles/{name}") - )); + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.GET, "/roles/"), + new Route(Method.GET, "/roles/{name}"), + new Route(Method.DELETE, "/roles/{name}"), + new Route(Method.PUT, "/roles/{name}"), + new Route(Method.PATCH, "/roles/"), + new Route(Method.PATCH, "/roles/{name}") + ) + ); - @Inject - public RolesApiAction(Settings settings, final Path configPath, RestController controller, Client client, AdminDNs adminDNs, ConfigurationRepository cl, - ClusterService cs, final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } + @Inject + public RolesApiAction( + Settings settings, + final Path configPath, + RestController controller, + Client client, + AdminDNs adminDNs, + ConfigurationRepository cl, + ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } - @Override - public List routes() { - return routes; - } + @Override + public List routes() { + return routes; + } - @Override - protected Endpoint getEndpoint() { - return Endpoint.ROLES; - } + @Override + protected Endpoint getEndpoint() { + return Endpoint.ROLES; + } - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesValidator(request, isSuperAdmin(), ref, this.settings, param); - } + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new RolesValidator(request, isSuperAdmin(), ref, this.settings, param); + } - @Override - protected String getResourceName() { - return "role"; - } + @Override + protected String getResourceName() { + return "role"; + } - @Override + @Override protected CType getConfigName() { return CType.ROLES; - } + } - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfiguration, final Object content, final String resourceName) throws IOException { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { - return isSuperAdmin(); - } else { - return true; - } - } + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfiguration, + final Object content, + final String resourceName + ) throws IOException { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(content)) { + return isSuperAdmin(); + } else { + return true; + } + } - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { - return !isSuperAdmin(); - } else { - return super.isReadOnly(existingConfiguration, name); - } - } + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(existingConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java index 72928cd0ad..627dabfaa2 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/RolesMappingApiAction.java @@ -43,102 +43,125 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class RolesMappingApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(Method.GET, "/rolesmapping/"), - new Route(Method.GET, "/rolesmapping/{name}"), - new Route(Method.DELETE, "/rolesmapping/{name}"), - new Route(Method.PUT, "/rolesmapping/{name}"), - new Route(Method.PATCH, "/rolesmapping/"), - new Route(Method.PATCH, "/rolesmapping/{name}") - )); - - @Inject - public RolesMappingApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { - super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); - } - - @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { - final String name = request.param("name"); - - if (name == null || name.length() == 0) { - badRequestResponse(channel, "No " + getResourceName() + " specified."); - return; - } - - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - final SecurityDynamicConfiguration rolesMappingConfiguration = load(getConfigName(), false); - final boolean rolesMappingExists = rolesMappingConfiguration.exists(name); - - if (!isValidRolesMapping(channel, name)) return; - - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { - if (!isSuperAdmin()) { - forbidden(channel, "No permissions"); - return; - } - } - rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); - - saveAndUpdateConfigs(this.securityIndexName,client, getConfigName(), rolesMappingConfiguration, new OnSucessActionListener(channel) { - - @Override - public void onResponse(IndexResponse response) { - if (rolesMappingExists) { - successResponse(channel, "'" + name + "' updated."); - } else { - createdResponse(channel, "'" + name + "' created."); - } - - } - }); - } - - @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, final Object content, final String resourceName) throws IOException { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(resourceName))) { - return isSuperAdmin(); - } else { - return true; - } - } - - @Override - protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { - final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); - if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { - return !isSuperAdmin(); - } else { - return super.isReadOnly(existingConfiguration, name); - } - } - - @Override - public List routes() { - return routes; - } - - @Override - protected Endpoint getEndpoint() { - return Endpoint.ROLESMAPPING; - } - - @Override - protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { - return new RolesMappingValidator(request, isSuperAdmin(), ref, this.settings, param); - } - - @Override - protected String getResourceName() { - return "rolesmapping"; - } - - @Override + private static final List routes = addRoutesPrefix( + ImmutableList.of( + new Route(Method.GET, "/rolesmapping/"), + new Route(Method.GET, "/rolesmapping/{name}"), + new Route(Method.DELETE, "/rolesmapping/{name}"), + new Route(Method.PUT, "/rolesmapping/{name}"), + new Route(Method.PATCH, "/rolesmapping/"), + new Route(Method.PATCH, "/rolesmapping/{name}") + ) + ); + + @Inject + public RolesMappingApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { + super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); + } + + @Override + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { + final String name = request.param("name"); + + if (name == null || name.length() == 0) { + badRequestResponse(channel, "No " + getResourceName() + " specified."); + return; + } + + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + final SecurityDynamicConfiguration rolesMappingConfiguration = load(getConfigName(), false); + final boolean rolesMappingExists = rolesMappingConfiguration.exists(name); + + if (!isValidRolesMapping(channel, name)) return; + + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + if (!isSuperAdmin()) { + forbidden(channel, "No permissions"); + return; + } + } + rolesMappingConfiguration.putCObject(name, DefaultObjectMapper.readTree(content, rolesMappingConfiguration.getImplementingClass())); + + saveAndUpdateConfigs( + this.securityIndexName, + client, + getConfigName(), + rolesMappingConfiguration, + new OnSucessActionListener(channel) { + + @Override + public void onResponse(IndexResponse response) { + if (rolesMappingExists) { + successResponse(channel, "'" + name + "' updated."); + } else { + createdResponse(channel, "'" + name + "' created."); + } + + } + } + ); + } + + @Override + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) throws IOException { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(resourceName))) { + return isSuperAdmin(); + } else { + return true; + } + } + + @Override + protected boolean isReadOnly(SecurityDynamicConfiguration existingConfiguration, String name) { + final SecurityDynamicConfiguration rolesConfiguration = load(CType.ROLES, false); + if (restApiAdminPrivilegesEvaluator.containsRestApiAdminPermissions(rolesConfiguration.getCEntry(name))) { + return !isSuperAdmin(); + } else { + return super.isReadOnly(existingConfiguration, name); + } + } + + @Override + public List routes() { + return routes; + } + + @Override + protected Endpoint getEndpoint() { + return Endpoint.ROLESMAPPING; + } + + @Override + protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) { + return new RolesMappingValidator(request, isSuperAdmin(), ref, this.settings, param); + } + + @Override + protected String getResourceName() { + return "rolesmapping"; + } + + @Override protected CType getConfigName() { return CType.ROLESMAPPING; - } + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java index 66888bc126..9584a34c06 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityConfigAction.java @@ -44,26 +44,30 @@ public class SecurityConfigAction extends PatchableResourceApiAction { - private static final List getRoutes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/securityconfig/") - )); - - private static final List allRoutes = new ImmutableList.Builder() - .addAll(getRoutes) - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(Method.PUT, "/securityconfig/{name}"), - new Route(Method.PATCH, "/securityconfig/") - ) - )) - .build(); + private static final List getRoutes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/securityconfig/"))); + + private static final List allRoutes = new ImmutableList.Builder().addAll(getRoutes) + .addAll( + addRoutesPrefix(ImmutableList.of(new Route(Method.PUT, "/securityconfig/{name}"), new Route(Method.PATCH, "/securityconfig/"))) + ) + .build(); private final boolean allowPutOrPatch; @Inject - public SecurityConfigAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public SecurityConfigAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); allowPutOrPatch = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false); @@ -75,14 +79,16 @@ public List routes() { } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @Override - protected void handleGet(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException{ + protected void handleGet(RestChannel channel, RestRequest request, Client client, final JsonNode content) throws IOException { final SecurityDynamicConfiguration configuration = load(getConfigName(), true); filter(configuration); @@ -90,8 +96,6 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client successResponse(channel, configuration); } - - @Override protected void handleApiRequest(RestChannel channel, RestRequest request, Client client) throws IOException { if (request.method() == Method.PATCH && !allowPutOrPatch) { @@ -102,10 +106,11 @@ protected void handleApiRequest(RestChannel channel, RestRequest request, Client } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (allowPutOrPatch) { - if(!"config".equals(request.param("name"))) { + if (!"config".equals(request.param("name"))) { badRequestResponse(channel, "name must be config"); return; } @@ -117,12 +122,14 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C } @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException{ + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.DELETE); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java index cd489cab4d..e7d68a8677 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecurityRestApiActions.java @@ -33,39 +33,296 @@ public class SecurityRestApiActions { - public static Collection getHandler(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDns, - final ConfigurationRepository cr, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, - final ThreadPool threadPool, - final AuditLog auditLog, - final SecurityKeyStore securityKeyStore, - final UserService userService, - final boolean certificatesReloadEnabled) { + public static Collection getHandler( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDns, + final ConfigurationRepository cr, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final UserService userService, + final boolean certificatesReloadEnabled + ) { final List handlers = new ArrayList(16); - handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, userService, auditLog)); - handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new ActionGroupsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new FlushCacheApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new SecurityConfigAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new PermissionsInfoAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AuthTokenProcessorAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new TenantsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new MigrateApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new ValidateApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AccountApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new NodesDnApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new WhitelistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AllowlistApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new AuditApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new MultiTenancyConfigApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog)); - handlers.add(new SecuritySSLCertsAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog, securityKeyStore, certificatesReloadEnabled)); + handlers.add( + new InternalUsersApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + userService, + auditLog + ) + ); + handlers.add( + new RolesMappingApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new RolesApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new ActionGroupsApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new FlushCacheApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new SecurityConfigAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new PermissionsInfoAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AuthTokenProcessorAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new TenantsApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new MigrateApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new ValidateApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AccountApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new NodesDnApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new WhitelistApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AllowlistApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new AuditApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new MultiTenancyConfigApiAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog + ) + ); + handlers.add( + new SecuritySSLCertsAction( + settings, + configPath, + controller, + client, + adminDns, + cr, + cs, + principalExtractor, + evaluator, + threadPool, + auditLog, + securityKeyStore, + certificatesReloadEnabled + ) + ); return Collections.unmodifiableCollection(handlers); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java index 1c1fe9b815..4949dedad9 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/SecuritySSLCertsAction.java @@ -51,7 +51,6 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - /** * Rest API action to get SSL certificate information related to http and transport encryption. * Only super admin users are allowed to access this API. @@ -60,10 +59,7 @@ */ public class SecuritySSLCertsAction extends AbstractApiAction { private static final List ROUTES = addRoutesPrefix( - ImmutableList.of( - new Route(Method.GET, "/ssl/certs"), - new Route(Method.PUT, "/ssl/{certType}/reloadcerts") - ) + ImmutableList.of(new Route(Method.GET, "/ssl/certs"), new Route(Method.PUT, "/ssl/{certType}/reloadcerts")) ); private final Logger log = LogManager.getLogger(this.getClass()); @@ -74,19 +70,21 @@ public class SecuritySSLCertsAction extends AbstractApiAction { private final boolean httpsEnabled; - public SecuritySSLCertsAction(final Settings settings, - final Path configPath, - final RestController controller, - final Client client, - final AdminDNs adminDNs, - final ConfigurationRepository cl, - final ClusterService cs, - final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator privilegesEvaluator, - final ThreadPool threadPool, - final AuditLog auditLog, - final SecurityKeyStore securityKeyStore, - final boolean certificatesReloadEnabled) { + public SecuritySSLCertsAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator privilegesEvaluator, + final ThreadPool threadPool, + final AuditLog auditLog, + final SecurityKeyStore securityKeyStore, + final boolean certificatesReloadEnabled + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, privilegesEvaluator, threadPool, auditLog); this.securityKeyStore = securityKeyStore; this.certificatesReloadEnabled = certificatesReloadEnabled; @@ -94,9 +92,11 @@ public SecuritySSLCertsAction(final Settings settings, } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -122,13 +122,13 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req } if (!certificatesReloadEnabled) { badRequestResponse( - channel, - String.format( - "no handler found for uri [%s] and method [%s]. In order to use SSL reload functionality set %s to true", - request.path(), - request.method(), - ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED - ) + channel, + String.format( + "no handler found for uri [%s] and method [%s]. In order to use SSL reload functionality set %s to true", + request.path(), + request.method(), + ConfigConstants.SECURITY_SSL_CERT_RELOAD_ENABLED + ) ); return; } @@ -172,28 +172,21 @@ protected void handleApiRequest(final RestChannel channel, final RestRequest req * @throws IOException */ @Override - protected void handleGet(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handleGet(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (securityKeyStore == null) { noKeyStoreResponse(channel); return; } try (final XContentBuilder contentBuilder = channel.newBuilder()) { channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field( - "http_certificates_list", - httpsEnabled ? generateCertDetailList(securityKeyStore.getHttpCerts()) : null - ).field( - "transport_certificates_list", - generateCertDetailList(securityKeyStore.getTransportCerts()) - ).endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject() + .field("http_certificates_list", httpsEnabled ? generateCertDetailList(securityKeyStore.getHttpCerts()) : null) + .field("transport_certificates_list", generateCertDetailList(securityKeyStore.getTransportCerts())) + .endObject() + ) ); } catch (final Exception e) { internalErrorResponse(channel, e.getMessage()); @@ -219,10 +212,8 @@ protected void handleGet(final RestChannel channel, * @throws IOException */ @Override - protected void handlePut(final RestChannel channel, - final RestRequest request, - final Client client, - final JsonNode content) throws IOException { + protected void handlePut(final RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { if (securityKeyStore == null) { noKeyStoreResponse(channel); return; @@ -237,44 +228,37 @@ protected void handlePut(final RestChannel channel, } securityKeyStore.initHttpSSLConfig(); channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field("message", "updated http certs") - .endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject().field("message", "updated http certs").endObject() + ) ); break; case "transport": securityKeyStore.initTransportSSLConfig(); channel.sendResponse( - new BytesRestResponse( - RestStatus.OK, - contentBuilder - .startObject() - .field("message", "updated transport certs") - .endObject() - ) + new BytesRestResponse( + RestStatus.OK, + contentBuilder.startObject().field("message", "updated transport certs").endObject() + ) ); break; default: - forbidden(channel, - "invalid uri path, please use /_plugins/_security/api/ssl/http/reload or " - + "/_plugins/_security/api/ssl/transport/reload" + forbidden( + channel, + "invalid uri path, please use /_plugins/_security/api/ssl/http/reload or " + + "/_plugins/_security/api/ssl/transport/reload" ); break; } } catch (final Exception e) { log.error("Reload of certificates for {} failed", certType, e); try (final XContentBuilder contentBuilder = channel.newBuilder()) { - channel.sendResponse(new BytesRestResponse( - RestStatus.INTERNAL_SERVER_ERROR, - contentBuilder - .startObject() - .field("error", e.toString()) - .endObject() - ) + channel.sendResponse( + new BytesRestResponse( + RestStatus.INTERNAL_SERVER_ERROR, + contentBuilder.startObject().field("error", e.toString()).endObject() + ) ); } } @@ -284,25 +268,27 @@ private List> generateCertDetailList(final X509Certificate[] if (certs == null) { return null; } - return Arrays - .stream(certs) - .map(cert -> { - final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName() : ""; - final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName() : ""; + return Arrays.stream(certs).map(cert -> { + final String issuerDn = cert != null && cert.getIssuerX500Principal() != null ? cert.getIssuerX500Principal().getName() : ""; + final String subjectDn = cert != null && cert.getSubjectX500Principal() != null ? cert.getSubjectX500Principal().getName() : ""; - final String san = securityKeyStore.getSubjectAlternativeNames(cert); + final String san = securityKeyStore.getSubjectAlternativeNames(cert); - final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString() : ""; - final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString() : ""; - return ImmutableMap.of( - "issuer_dn", issuerDn, - "subject_dn", subjectDn, - "san", san, - "not_before", notBefore, - "not_after", notAfter - ); - }) - .collect(Collectors.toList()); + final String notBefore = cert != null && cert.getNotBefore() != null ? cert.getNotBefore().toInstant().toString() : ""; + final String notAfter = cert != null && cert.getNotAfter() != null ? cert.getNotAfter().toInstant().toString() : ""; + return ImmutableMap.of( + "issuer_dn", + issuerDn, + "subject_dn", + subjectDn, + "san", + san, + "not_before", + notBefore, + "not_after", + notAfter + ); + }).collect(Collectors.toList()); } private void noKeyStoreResponse(final RestChannel channel) throws IOException { diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java index ef8d1d3b9f..5fbb907ecf 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/TenantsApiAction.java @@ -54,26 +54,40 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class TenantsApiAction extends PatchableResourceApiAction { - private static final List routes = addRoutesPrefix(ImmutableList.of( + private static final List routes = addRoutesPrefix( + ImmutableList.of( new Route(Method.GET, "/tenants/{name}"), new Route(Method.GET, "/tenants/"), new Route(Method.DELETE, "/tenants/{name}"), new Route(Method.PUT, "/tenants/{name}"), new Route(Method.PATCH, "/tenants/"), new Route(Method.PATCH, "/tenants/{name}") - )); + ) + ); @Inject - public TenantsApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public TenantsApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java index 8e2222cab0..de79b131b3 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/ValidateApiAction.java @@ -54,23 +54,32 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; - public class ValidateApiAction extends AbstractApiAction { - private static final List routes = addRoutesPrefix(Collections.singletonList( - new Route(Method.GET, "/validate") - )); + private static final List routes = addRoutesPrefix(Collections.singletonList(new Route(Method.GET, "/validate"))); @Inject - public ValidateApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor, - final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public ValidateApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } @Override - protected boolean hasPermissionsToCreate(final SecurityDynamicConfiguration dynamicConfigFactory, - final Object content, - final String resourceName) { + protected boolean hasPermissionsToCreate( + final SecurityDynamicConfiguration dynamicConfigFactory, + final Object content, + final String resourceName + ) { return true; } @@ -99,17 +108,36 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client try { final SecurityDynamicConfiguration configV6 = (SecurityDynamicConfiguration) loadedConfig; - final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load(CType.ACTIONGROUPS, true, acceptInvalid); - final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load(CType.INTERNALUSERS, true, acceptInvalid); - final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load(CType.ROLES, true, acceptInvalid); - final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load(CType.ROLESMAPPING, true, acceptInvalid); - final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load(CType.AUDIT, true); + final SecurityDynamicConfiguration actionGroupsV6 = (SecurityDynamicConfiguration) load( + CType.ACTIONGROUPS, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration internalUsersV6 = (SecurityDynamicConfiguration) load( + CType.INTERNALUSERS, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration rolesV6 = (SecurityDynamicConfiguration) load( + CType.ROLES, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration rolesmappingV6 = (SecurityDynamicConfiguration) load( + CType.ROLESMAPPING, + true, + acceptInvalid + ); + final SecurityDynamicConfiguration auditConfigV6 = (SecurityDynamicConfiguration) load( + CType.AUDIT, + true + ); final SecurityDynamicConfiguration actionGroupsV7 = Migration.migrateActionGroups(actionGroupsV6); final SecurityDynamicConfiguration configV7 = Migration.migrateConfig(configV6); final SecurityDynamicConfiguration internalUsersV7 = Migration.migrateInternalUsers(internalUsersV6); - final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration.migrateRoles(rolesV6, - rolesmappingV6); + final Tuple, SecurityDynamicConfiguration> rolesTenantsV7 = Migration + .migrateRoles(rolesV6, rolesmappingV6); final SecurityDynamicConfiguration rolesmappingV7 = Migration.migrateRoleMappings(rolesmappingV6); final SecurityDynamicConfiguration auditConfigV7 = Migration.migrateAudit(auditConfigV6); @@ -120,17 +148,20 @@ protected void handleGet(RestChannel channel, RestRequest request, Client client } @Override - protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handleDelete(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.POST); } @Override - protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePost(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.GET); } @Override - protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) throws IOException { + protected void handlePut(RestChannel channel, final RestRequest request, final Client client, final JsonNode content) + throws IOException { notImplemented(channel, Method.PUT); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java index 6f55a2d762..d3bc92959c 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java +++ b/src/main/java/org/opensearch/security/dlic/rest/api/WhitelistApiAction.java @@ -74,16 +74,40 @@ *

*/ public class WhitelistApiAction extends AllowlistApiAction { - private static final List routes = addDeprecatedRoutesPrefix(ImmutableList.of( - new DeprecatedRoute(RestRequest.Method.GET, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead."), - new DeprecatedRoute(RestRequest.Method.PUT, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead."), - new DeprecatedRoute(RestRequest.Method.PATCH, "/whitelist", "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead.") - )); + private static final List routes = addDeprecatedRoutesPrefix( + ImmutableList.of( + new DeprecatedRoute( + RestRequest.Method.GET, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ), + new DeprecatedRoute( + RestRequest.Method.PUT, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ), + new DeprecatedRoute( + RestRequest.Method.PATCH, + "/whitelist", + "[/whitelist] is a deprecated endpoint. Please use [/allowlist] instead." + ) + ) + ); @Inject - public WhitelistApiAction(final Settings settings, final Path configPath, final RestController controller, final Client client, - final AdminDNs adminDNs, final ConfigurationRepository cl, final ClusterService cs, - final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) { + public WhitelistApiAction( + final Settings settings, + final Path configPath, + final RestController controller, + final Client client, + final AdminDNs adminDNs, + final ConfigurationRepository cl, + final ClusterService cs, + final PrincipalExtractor principalExtractor, + final PrivilegesEvaluator evaluator, + ThreadPool threadPool, + AuditLog auditLog + ) { super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java index aba2807846..74908dbf60 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java +++ b/src/main/java/org/opensearch/security/dlic/rest/support/Utils.java @@ -69,7 +69,10 @@ public static Map convertJsonToxToStructuredMap(ToXContent jsonC } public static Map convertJsonToxToStructuredMap(String jsonContent) { - try (XContentParser parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, jsonContent)) { + try ( + XContentParser parser = XContentType.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, THROW_UNSUPPORTED_OPERATION, jsonContent) + ) { return parser.map(); } catch (IOException e1) { throw ExceptionsHelper.convertToOpenSearchException(e1); @@ -179,7 +182,8 @@ public static Map byteArrayToMutableJsonMap(byte[] jsonBytes) th return AccessController.doPrivileged(new PrivilegedExceptionAction>() { @Override public Map run() throws Exception { - return internalMapper.readValue(jsonBytes, new TypeReference>() {}); + return internalMapper.readValue(jsonBytes, new TypeReference>() { + }); } }); } catch (final PrivilegedActionException e) { @@ -215,10 +219,7 @@ public static String hash(final char[] clearTextPassword) { * @return new set of fields resource paths */ public static Set generateFieldResourcePaths(final Set fields, final String prefix) { - return fields - .stream() - .map(field -> prefix + field) - .collect(ImmutableSet.toImmutableSet()); + return fields.stream().map(field -> prefix + field).collect(ImmutableSet.toImmutableSet()); } /** @@ -227,7 +228,7 @@ public static Set generateFieldResourcePaths(final Set fields, f * @return new list of API routes prefixed with _opendistro... and _plugins... *Total number of routes is expanded as twice as the number of routes passed in */ - public static List addRoutesPrefix(List routes){ + public static List addRoutesPrefix(List routes) { return addRoutesPrefix(routes, "/_opendistro/_security/api", "/_plugins/_security/api"); } @@ -238,12 +239,10 @@ public static List addRoutesPrefix(List routes){ * @return new list of API routes prefixed with the strings listed in prefixes * Total number of routes will be expanded len(prefixes) as much comparing to the list passed in */ - public static List addRoutesPrefix(List routes, final String... prefixes){ + public static List addRoutesPrefix(List routes, final String... prefixes) { return routes.stream() - .flatMap( - r -> Arrays.stream(prefixes) - .map(p -> new Route(r.getMethod(), p + r.getPath()))) - .collect(ImmutableList.toImmutableList()); + .flatMap(r -> Arrays.stream(prefixes).map(p -> new Route(r.getMethod(), p + r.getPath()))) + .collect(ImmutableList.toImmutableList()); } /** @@ -252,7 +251,7 @@ public static List addRoutesPrefix(List routes, final String... pr * @return new list of API routes prefixed with _opendistro... and _plugins... *Total number of routes is expanded as twice as the number of routes passed in */ - public static List addDeprecatedRoutesPrefix(List deprecatedRoutes){ + public static List addDeprecatedRoutesPrefix(List deprecatedRoutes) { return addDeprecatedRoutesPrefix(deprecatedRoutes, "/_opendistro/_security/api", "/_plugins/_security/api"); } @@ -263,12 +262,10 @@ public static List addDeprecatedRoutesPrefix(List addDeprecatedRoutesPrefix(List deprecatedRoutes, final String... prefixes){ + public static List addDeprecatedRoutesPrefix(List deprecatedRoutes, final String... prefixes) { return deprecatedRoutes.stream() - .flatMap( - r -> Arrays.stream(prefixes) - .map(p -> new DeprecatedRoute(r.getMethod(), p + r.getPath(), r.getDeprecationMessage()))) - .collect(ImmutableList.toImmutableList()); + .flatMap(r -> Arrays.stream(prefixes).map(p -> new DeprecatedRoute(r.getMethod(), p + r.getPath(), r.getDeprecationMessage()))) + .collect(ImmutableList.toImmutableList()); } public static Pair userAndRemoteAddressFrom(final ThreadContext threadContext) { diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java index e3221de7e6..51d58d75f6 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AbstractConfigurationValidator.java @@ -39,7 +39,6 @@ import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.support.ConfigConstants; - public abstract class AbstractConfigurationValidator { JsonFactory factory = new JsonFactory(); @@ -91,7 +90,12 @@ public abstract class AbstractConfigurationValidator { private JsonNode contentAsNode; - public AbstractConfigurationValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { + public AbstractConfigurationValidator( + final RestRequest request, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { this.content = ref; this.method = request.method(); this.opensearchSettings = opensearchSettings; @@ -113,19 +117,19 @@ public boolean validate() { return true; } - if(this.payloadMandatory && content.length() == 0) { + if (this.payloadMandatory && content.length() == 0) { this.errorType = ErrorType.PAYLOAD_MANDATORY; return false; } - if(!this.payloadMandatory && content.length() == 0) { + if (!this.payloadMandatory && content.length() == 0) { return true; } - if(this.payloadMandatory && content.length() > 0) { + if (this.payloadMandatory && content.length() > 0) { try { - if(DefaultObjectMapper.readTree(content.utf8ToString()).size() == 0) { + if (DefaultObjectMapper.readTree(content.utf8ToString()).size() == 0) { this.errorType = ErrorType.PAYLOAD_MANDATORY; return false; } @@ -184,7 +188,7 @@ public boolean validate() { return false; } - //null element in the values of all the possible keys with DataType as ARRAY + // null element in the values of all the possible keys with DataType as ARRAY for (Entry allowedKey : allowedKeys.entrySet()) { JsonNode value = contentAsNode.get(allowedKey.getKey()); if (value != null) { @@ -249,18 +253,21 @@ public XContentBuilder errorsAsXContent(RestChannel channel) { break; case INVALID_PASSWORD: builder.field("status", "error"); - builder.field("reason", opensearchSettings.get( + builder.field( + "reason", + opensearchSettings.get( ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, - "Password does not match minimum criteria")); + "Password does not match minimum criteria" + ) + ); break; case WEAK_PASSWORD: case SIMILAR_PASSWORD: builder.field("status", "error"); builder.field( - "reason", - opensearchSettings.get( - ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, - errorType.message)); + "reason", + opensearchSettings.get(ConfigConstants.SECURITY_RESTAPI_PASSWORD_VALIDATION_ERROR_MESSAGE, errorType.message) + ); break; case WRONG_DATATYPE: builder.field("status", "error"); @@ -295,7 +302,10 @@ private void addErrorMessage(final XContentBuilder builder, final String message } public static enum DataType { - STRING, ARRAY, OBJECT, BOOLEAN; + STRING, + ARRAY, + OBJECT, + BOOLEAN; } public static enum ErrorType { @@ -307,7 +317,8 @@ public static enum ErrorType { WRONG_DATATYPE("Wrong datatype"), BODY_NOT_PARSEABLE("Could not parse content of request."), PAYLOAD_NOT_ALLOWED("Request body not allowed for this action."), - PAYLOAD_MANDATORY("Request body required for this action."), SECURITY_NOT_INITIALIZED("Security index not initialized"), + PAYLOAD_MANDATORY("Request body required for this action."), + SECURITY_NOT_INITIALIZED("Security index not initialized"), NULL_ARRAY_ELEMENT("`null` is not allowed as json array element"); private String message; @@ -326,8 +337,8 @@ protected final boolean hasParams() { } private boolean hasNullArrayElement(JsonNode node) { - for (JsonNode element: node) { - if(element.isNull()) { + for (JsonNode element : node) { + if (element.isNull()) { if (node.isArray()) { return true; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java index ee1ab61238..a9f298fb15 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/ActionGroupValidator.java @@ -17,15 +17,21 @@ public class ActionGroupValidator extends AbstractConfigurationValidator { - public ActionGroupValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("allowed_actions", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - allowedKeys.put("type", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved" , DataType.BOOLEAN); + public ActionGroupValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("allowed_actions", DataType.ARRAY); + allowedKeys.put("description", DataType.STRING); + allowedKeys.put("type", DataType.STRING); + if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - mandatoryKeys.add("allowed_actions"); - } + mandatoryKeys.add("allowed_actions"); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java index 6cc2aaca1b..1bff373c0d 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/AuditValidator.java @@ -25,29 +25,26 @@ public class AuditValidator extends AbstractConfigurationValidator { private static final Set DISABLED_REST_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES ); private static final Set DISABLED_TRANSPORT_CATEGORIES = ImmutableSet.of( - AuditCategory.BAD_HEADERS, - AuditCategory.SSL_EXCEPTION, - AuditCategory.AUTHENTICATED, - AuditCategory.FAILED_LOGIN, - AuditCategory.GRANTED_PRIVILEGES, - AuditCategory.MISSING_PRIVILEGES, - AuditCategory.INDEX_EVENT, - AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT + AuditCategory.BAD_HEADERS, + AuditCategory.SSL_EXCEPTION, + AuditCategory.AUTHENTICATED, + AuditCategory.FAILED_LOGIN, + AuditCategory.GRANTED_PRIVILEGES, + AuditCategory.MISSING_PRIVILEGES, + AuditCategory.INDEX_EVENT, + AuditCategory.OPENDISTRO_SECURITY_INDEX_ATTEMPT ); - public AuditValidator(final RestRequest request, - final BytesReference ref, - final Settings opensearchSettings, - final Object... param) { + public AuditValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, final Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; this.allowedKeys.put("enabled", DataType.BOOLEAN); @@ -62,8 +59,8 @@ public boolean validate() { } if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 0) { + && this.content != null + && this.content.length() > 0) { try { // try parsing to target type final AuditConfig auditConfig = DefaultObjectMapper.readTree(getContentAsNode(), AuditConfig.class); diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java index ff1addc11e..a0f67c97ce 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/CredentialsValidator.java @@ -29,10 +29,7 @@ public class CredentialsValidator extends AbstractConfigurationValidator { private final PasswordValidator passwordValidator; - public CredentialsValidator(final RestRequest request, - final BytesReference ref, - final Settings opensearchSettings, - Object... param) { + public CredentialsValidator(final RestRequest request, final BytesReference ref, final Settings opensearchSettings, Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; this.passwordValidator = PasswordValidator.of(opensearchSettings); @@ -50,8 +47,8 @@ public boolean validate() { return false; } if ((request.method() == RestRequest.Method.PUT || request.method() == RestRequest.Method.PATCH) - && this.content != null - && this.content.length() > 1) { + && this.content != null + && this.content.length() > 1) { try { final Map contentAsMap = XContentHelper.convertToMap(this.content, false, XContentType.JSON).v2(); final String password = (String) contentAsMap.get("password"); @@ -75,7 +72,7 @@ public boolean validate() { } } } catch (NotXContentException e) { - //this.content is not valid json/yaml + // this.content is not valid json/yaml log.error("Invalid xContent: " + e, e); return false; } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java index 5f9828eba4..9681c47232 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/InternalUsersValidator.java @@ -20,8 +20,13 @@ */ public class InternalUsersValidator extends CredentialsValidator { - public InternalUsersValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, - Object... param) { + public InternalUsersValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { super(request, ref, opensearchSettings, param); allowedKeys.put("backend_roles", DataType.ARRAY); allowedKeys.put("attributes", DataType.OBJECT); diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java index 42870f1c13..42f86dbee5 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/MultiTenancyConfigValidator.java @@ -20,7 +20,6 @@ public class MultiTenancyConfigValidator extends AbstractConfigurationValidator public static final String PRIVATE_TENANT_ENABLED_JSON_PROPERTY = "private_tenant_enabled"; public static final String MULTITENANCY_ENABLED_JSON_PROPERTY = "multitenancy_enabled"; - public MultiTenancyConfigValidator(RestRequest request, BytesReference ref, Settings opensearchSettings, Object... param) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java index ec85c1fae8..7c64102091 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/NoOpValidator.java @@ -17,8 +17,8 @@ public class NoOpValidator extends AbstractConfigurationValidator { - public NoOpValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - } + public NoOpValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { + super(request, ref, opensearchSettings, param); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java index f5ae9bee49..ac521dee8a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/PasswordValidator.java @@ -46,8 +46,8 @@ public class PasswordValidator { * are similar * "user_inputs" - is a default dictionary zxcvbn creates for checking similarity */ - private final static Predicate USERNAME_SIMILARITY_CHECK = m -> - m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary && "user_inputs".equals(m.dictionaryName); + private final static Predicate USERNAME_SIMILARITY_CHECK = m -> m.pattern == com.nulabinc.zxcvbn.Pattern.Dictionary + && "user_inputs".equals(m.dictionaryName); private final Logger logger = LogManager.getLogger(this.getClass()); @@ -59,9 +59,7 @@ public class PasswordValidator { private final Zxcvbn zxcvbn; - private PasswordValidator(final int minPasswordLength, - final Pattern passwordRegexpPattern, - final ScoreStrength scoreStrength) { + private PasswordValidator(final int minPasswordLength, final Pattern passwordRegexpPattern, final ScoreStrength scoreStrength) { this.minPasswordLength = minPasswordLength; this.passwordRegexpPattern = passwordRegexpPattern; this.scoreStrength = scoreStrength; @@ -71,49 +69,47 @@ private PasswordValidator(final int minPasswordLength, public static PasswordValidator of(final Settings settings) { final String passwordRegex = settings.get(SECURITY_RESTAPI_PASSWORD_VALIDATION_REGEX, null); final ScoreStrength scoreStrength = ScoreStrength.fromConfiguration( - settings.get(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, ScoreStrength.STRONG.name()) - ); + settings.get(SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, ScoreStrength.STRONG.name()) + ); final int minPasswordLength = settings.getAsInt(SECURITY_RESTAPI_PASSWORD_MIN_LENGTH, -1); return new PasswordValidator( - minPasswordLength, - !Strings.isNullOrEmpty(passwordRegex) ? Pattern.compile(String.format("^%s$", passwordRegex)) : null, - scoreStrength); + minPasswordLength, + !Strings.isNullOrEmpty(passwordRegex) ? Pattern.compile(String.format("^%s$", passwordRegex)) : null, + scoreStrength + ); } ErrorType validate(final String username, final String password) { if (minPasswordLength > 0 && password.length() < minPasswordLength) { logger.debug( - "Password is too short, the minimum required length is {}, but current length is {}", - minPasswordLength, - password.length() + "Password is too short, the minimum required length is {}, but current length is {}", + minPasswordLength, + password.length() ); return ErrorType.INVALID_PASSWORD; } if (password.length() > MAX_LENGTH) { logger.debug( - "Password is too long, the maximum required length is {}, but current length is {}", - MAX_LENGTH, - password.length() + "Password is too long, the maximum required length is {}, but current length is {}", + MAX_LENGTH, + password.length() ); return ErrorType.INVALID_PASSWORD; } - if (Objects.nonNull(passwordRegexpPattern) - && !passwordRegexpPattern.matcher(password).matches()) { + if (Objects.nonNull(passwordRegexpPattern) && !passwordRegexpPattern.matcher(password).matches()) { logger.debug("Regex does not match password"); return ErrorType.INVALID_PASSWORD; } final Strength strength = zxcvbn.measure(password, ImmutableList.of(username)); if (strength.getScore() < scoreStrength.score()) { logger.debug( - "Password is weak the required score is {}, but current is {}", - scoreStrength, - ScoreStrength.fromScore(strength.getScore()) + "Password is weak the required score is {}, but current is {}", + scoreStrength, + ScoreStrength.fromScore(strength.getScore()) ); return ErrorType.WEAK_PASSWORD; } - final boolean similar = strength.getSequence() - .stream() - .anyMatch(USERNAME_SIMILARITY_CHECK); + final boolean similar = strength.getSequence().stream().anyMatch(USERNAME_SIMILARITY_CHECK); if (similar) { logger.debug("Password is too similar to the user name {}", username); return ErrorType.SIMILAR_PASSWORD; @@ -137,12 +133,10 @@ public enum ScoreStrength { static final List CONFIGURATION_VALUES = ImmutableList.of(FAIR, STRONG, VERY_STRONG); - static final String EXPECTED_CONFIGURATION_VALUES = - new StringJoiner(",") - .add(FAIR.name().toLowerCase(Locale.ROOT)) - .add(STRONG.name().toLowerCase(Locale.ROOT)) - .add(VERY_STRONG.name().toLowerCase(Locale.ROOT)) - .toString(); + static final String EXPECTED_CONFIGURATION_VALUES = new StringJoiner(",").add(FAIR.name().toLowerCase(Locale.ROOT)) + .add(STRONG.name().toLowerCase(Locale.ROOT)) + .add(VERY_STRONG.name().toLowerCase(Locale.ROOT)) + .toString(); private ScoreStrength(final int score, final String description) { this.score = score; @@ -151,24 +145,22 @@ private ScoreStrength(final int score, final String description) { public static ScoreStrength fromScore(final int score) { for (final ScoreStrength strength : values()) { - if (strength.score == score) - return strength; + if (strength.score == score) return strength; } throw new IllegalArgumentException("Unknown score " + score); } public static ScoreStrength fromConfiguration(final String value) { for (final ScoreStrength strength : CONFIGURATION_VALUES) { - if (strength.name().equalsIgnoreCase(value)) - return strength; + if (strength.name().equalsIgnoreCase(value)) return strength; } throw new IllegalArgumentException( - String.format( - "Setting [%s] cannot be used with the configured: %s. Expected one of [%s]", - SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, - value, - EXPECTED_CONFIGURATION_VALUES - ) + String.format( + "Setting [%s] cannot be used with the configured: %s. Expected one of [%s]", + SECURITY_RESTAPI_PASSWORD_SCORE_BASED_VALIDATION_STRENGTH, + value, + EXPECTED_CONFIGURATION_VALUES + ) ); } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java index 0f36371176..728c2e0ca0 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesMappingValidator.java @@ -17,19 +17,25 @@ public class RolesMappingValidator extends AbstractConfigurationValidator { - public RolesMappingValidator(final RestRequest request, boolean isSuperAdmin, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("backend_roles", DataType.ARRAY); - allowedKeys.put("and_backend_roles", DataType.ARRAY); - allowedKeys.put("hosts", DataType.ARRAY); - allowedKeys.put("users", DataType.ARRAY); - allowedKeys.put("description", DataType.STRING); - if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); + public RolesMappingValidator( + final RestRequest request, + boolean isSuperAdmin, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("backend_roles", DataType.ARRAY); + allowedKeys.put("and_backend_roles", DataType.ARRAY); + allowedKeys.put("hosts", DataType.ARRAY); + allowedKeys.put("users", DataType.ARRAY); + allowedKeys.put("description", DataType.STRING); + if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - mandatoryOrKeys.add("backend_roles"); - mandatoryOrKeys.add("and_backend_roles"); - mandatoryOrKeys.add("hosts"); - mandatoryOrKeys.add("users"); - } + mandatoryOrKeys.add("backend_roles"); + mandatoryOrKeys.add("and_backend_roles"); + mandatoryOrKeys.add("hosts"); + mandatoryOrKeys.add("users"); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java index 07708c6615..2e57730e41 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/RolesValidator.java @@ -24,17 +24,23 @@ public class RolesValidator extends AbstractConfigurationValidator { - private static final Salt SALT = new Salt(new byte[] {1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,6}); - - public RolesValidator(final RestRequest request, boolean isSuperAdmin, final BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; + private static final Salt SALT = new Salt(new byte[] { 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 6 }); + + public RolesValidator( + final RestRequest request, + boolean isSuperAdmin, + final BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; allowedKeys.put("cluster_permissions", DataType.ARRAY); allowedKeys.put("tenant_permissions", DataType.ARRAY); allowedKeys.put("index_permissions", DataType.ARRAY); allowedKeys.put("description", DataType.STRING); if (isSuperAdmin) allowedKeys.put("reserved", DataType.BOOLEAN); - } + } @Override public boolean validate() { @@ -43,7 +49,7 @@ public boolean validate() { return false; } - boolean valid=true; + boolean valid = true; if (this.content != null && this.content.length() > 0) { @@ -60,8 +66,8 @@ public boolean validate() { } } - if(!valid) { - this.errorType = ErrorType.WRONG_DATATYPE; + if (!valid) { + this.errorType = ErrorType.WRONG_DATATYPE; } return valid; @@ -71,7 +77,7 @@ private boolean validateMaskedFieldSyntax(String mf) { try { new MaskedField(mf, SALT).isValid(); } catch (Exception e) { - wrongDatatypes.put("Masked field not valid: "+mf, e.getMessage()); + wrongDatatypes.put("Masked field not valid: " + mf, e.getMessage()); return false; } return true; diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java index 1a8db220b9..cd2ee56b4a 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/SecurityConfigValidator.java @@ -17,10 +17,10 @@ public class SecurityConfigValidator extends AbstractConfigurationValidator { - public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { - super(request, ref, opensearchSettings, param); - this.payloadMandatory = true; - allowedKeys.put("dynamic", DataType.OBJECT); - } + public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings opensearchSettings, Object... param) { + super(request, ref, opensearchSettings, param); + this.payloadMandatory = true; + allowedKeys.put("dynamic", DataType.OBJECT); + } } diff --git a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java b/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java index 15068500e8..51e0e97264 100644 --- a/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java +++ b/src/main/java/org/opensearch/security/dlic/rest/validation/TenantValidator.java @@ -33,7 +33,13 @@ public class TenantValidator extends AbstractConfigurationValidator { - public TenantValidator(final RestRequest request, boolean isSuperAdmin, BytesReference ref, final Settings opensearchSettings, Object... param) { + public TenantValidator( + final RestRequest request, + boolean isSuperAdmin, + BytesReference ref, + final Settings opensearchSettings, + Object... param + ) { super(request, ref, opensearchSettings, param); this.payloadMandatory = true; allowedKeys.put("description", DataType.STRING); diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 4dd629c010..38675d97c5 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -113,9 +113,18 @@ public class SecurityFilter implements ActionFilter { private final RolesInjector rolesInjector; private final UserInjector userInjector; - public SecurityFilter(final Settings settings, final PrivilegesEvaluator evalp, final AdminDNs adminDns, - DlsFlsRequestValve dlsFlsValve, AuditLog auditLog, ThreadPool threadPool, ClusterService cs, - final CompatConfig compatConfig, final IndexResolverReplacer indexResolverReplacer, final XFFResolver xffResolver) { + public SecurityFilter( + final Settings settings, + final PrivilegesEvaluator evalp, + final AdminDNs adminDns, + DlsFlsRequestValve dlsFlsValve, + AuditLog auditLog, + ThreadPool threadPool, + ClusterService cs, + final CompatConfig compatConfig, + final IndexResolverReplacer indexResolverReplacer, + final XFFResolver xffResolver + ) { this.evalp = evalp; this.adminDns = adminDns; this.dlsFlsValve = dlsFlsValve; @@ -125,7 +134,9 @@ public SecurityFilter(final Settings settings, final PrivilegesEvaluator evalp, this.compatConfig = compatConfig; this.indexResolverReplacer = indexResolverReplacer; this.xffResolver = xffResolver; - this.immutableIndicesMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList())); + this.immutableIndicesMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_COMPLIANCE_IMMUTABLE_INDICES, Collections.emptyList()) + ); this.rolesInjector = new RolesInjector(auditLog); this.userInjector = new UserInjector(settings, threadPool, auditLog, xffResolver); log.info("{} indices are made immutable.", immutableIndicesMatcher); @@ -142,9 +153,14 @@ public int order() { } @Override - public void apply(Task task, final String action, Request request, - ActionListener listener, ActionFilterChain chain) { - try (StoredContext ctx = threadContext.newStoredContext(true)){ + public void apply( + Task task, + final String action, + Request request, + ActionListener listener, + ActionFilterChain chain + ) { + try (StoredContext ctx = threadContext.newStoredContext(true)) { org.apache.logging.log4j.ThreadContext.clearAll(); apply0(task, action, request, listener, chain); } @@ -154,11 +170,16 @@ private static Set alias2Name(Set aliases) { return aliases.stream().map(a -> a.name()).collect(ImmutableSet.toImmutableSet()); } - private void apply0(Task task, final String action, Request request, - ActionListener listener, ActionFilterChain chain) { + private void apply0( + Task task, + final String action, + Request request, + ActionListener listener, + ActionFilterChain chain + ) { try { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.LOCAL.toString()); } @@ -169,7 +190,7 @@ private void ap final Set injectedRoles = rolesInjector.injectUserAndRoles(request, action, task, threadContext); boolean enforcePrivilegesEvaluation = false; User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if(user == null && (user = userInjector.getInjectedUser()) != null) { + if (user == null && (user = userInjector.getInjectedUser()) != null) { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); // since there is no support for TransportClient auth/auth in 2.0 anymore, usually we // can skip any checks on transport in case of trusted requests. @@ -180,14 +201,14 @@ private void ap final boolean userIsAdmin = isUserAdmin(user, adminDns); final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext); final boolean trustedClusterRequest = HeaderHelper.isTrustedClusterRequest(threadContext); - final boolean confRequest = "true".equals(HeaderHelper.getSafeFromHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER)); - final boolean passThroughRequest = action.startsWith("indices:admin/seq_no") - || action.equals(WhoAmIAction.NAME); + final boolean confRequest = "true".equals( + HeaderHelper.getSafeFromHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONF_REQUEST_HEADER) + ); + final boolean passThroughRequest = action.startsWith("indices:admin/seq_no") || action.equals(WhoAmIAction.NAME); - final boolean internalRequest = - (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) - && action.startsWith("internal:") - && !action.startsWith("internal:transport/proxy"); + final boolean internalRequest = (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) + && action.startsWith("internal:") + && !action.startsWith("internal:transport/proxy"); if (user != null) { org.apache.logging.log4j.ThreadContext.put("user", user.getName()); @@ -196,35 +217,72 @@ private void ap if (isActionTraceEnabled()) { String count = ""; - if(request instanceof BulkRequest) { - count = ""+((BulkRequest) request).requests().size(); + if (request instanceof BulkRequest) { + count = "" + ((BulkRequest) request).requests().size(); } - if(request instanceof MultiGetRequest) { - count = ""+((MultiGetRequest) request).getItems().size(); + if (request instanceof MultiGetRequest) { + count = "" + ((MultiGetRequest) request).getItems().size(); } - if(request instanceof MultiSearchRequest) { - count = ""+((MultiSearchRequest) request).requests().size(); + if (request instanceof MultiSearchRequest) { + count = "" + ((MultiSearchRequest) request).requests().size(); } - traceAction("Node "+cs.localNode().getName()+" -> "+action+" ("+count+"): userIsAdmin="+userIsAdmin+"/conRequest="+confRequest+"/internalRequest="+internalRequest - +"origin="+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+"/directRequest="+HeaderHelper.isDirectRequest(threadContext)+"/remoteAddress="+request.remoteAddress()); - - - threadContext.putHeader("_opendistro_security_trace"+System.currentTimeMillis()+"#"+UUID.randomUUID().toString(), Thread.currentThread().getName()+" FILTER -> "+"Node "+cs.localNode().getName()+" -> "+action+" userIsAdmin="+userIsAdmin+"/conRequest="+confRequest+"/internalRequest="+internalRequest - +"origin="+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+"/directRequest="+HeaderHelper.isDirectRequest(threadContext)+"/remoteAddress="+request.remoteAddress()+" "+threadContext.getHeaders().entrySet().stream().filter(p->!p.getKey().startsWith("_opendistro_security_trace")).collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()))); - + traceAction( + "Node " + + cs.localNode().getName() + + " -> " + + action + + " (" + + count + + "): userIsAdmin=" + + userIsAdmin + + "/conRequest=" + + confRequest + + "/internalRequest=" + + internalRequest + + "origin=" + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + "/directRequest=" + + HeaderHelper.isDirectRequest(threadContext) + + "/remoteAddress=" + + request.remoteAddress() + ); + + threadContext.putHeader( + "_opendistro_security_trace" + System.currentTimeMillis() + "#" + UUID.randomUUID().toString(), + Thread.currentThread().getName() + + " FILTER -> " + + "Node " + + cs.localNode().getName() + + " -> " + + action + + " userIsAdmin=" + + userIsAdmin + + "/conRequest=" + + confRequest + + "/internalRequest=" + + internalRequest + + "origin=" + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + "/directRequest=" + + HeaderHelper.isDirectRequest(threadContext) + + "/remoteAddress=" + + request.remoteAddress() + + " " + + threadContext.getHeaders() + .entrySet() + .stream() + .filter(p -> !p.getKey().startsWith("_opendistro_security_trace")) + .collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue())) + ); } + if (userIsAdmin || confRequest || internalRequest || passThroughRequest) { - if(userIsAdmin - || confRequest - || internalRequest - || passThroughRequest){ - - if(userIsAdmin && !confRequest && !internalRequest && !passThroughRequest) { + if (userIsAdmin && !confRequest && !internalRequest && !passThroughRequest) { auditLog.logGrantedPrivileges(action, request, task); auditLog.logIndexEvent(action, request, task); } @@ -233,15 +291,14 @@ private void ap return; } - - if(immutableIndicesMatcher != WildcardMatcher.NONE) { + if (immutableIndicesMatcher != WildcardMatcher.NONE) { boolean isImmutable = false; - if(request instanceof BulkShardRequest) { - for(BulkItemRequest bsr: ((BulkShardRequest) request).items()) { + if (request instanceof BulkShardRequest) { + for (BulkItemRequest bsr : ((BulkShardRequest) request).items()) { isImmutable = checkImmutableIndices(bsr.request(), listener); - if(isImmutable) { + if (isImmutable) { break; } } @@ -249,50 +306,67 @@ private void ap isImmutable = checkImmutableIndices(request, listener); } - if(isImmutable) { + if (isImmutable) { return; } } - if(Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) - && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) - && (injectedRoles == null) - && !enforcePrivilegesEvaluation - ) { + if (Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) + && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) + && (injectedRoles == null) + && !enforcePrivilegesEvaluation) { chain.proceed(task, action, request, listener); return; } - if(user == null) { + if (user == null) { - if(action.startsWith("cluster:monitor/state")) { + if (action.startsWith("cluster:monitor/state")) { chain.proceed(task, action, request, listener); return; } - boolean skipSecurityIfDualMode = threadContext.getTransient(ConfigConstants.SECURITY_SSL_DUAL_MODE_SKIP_SECURITY) == Boolean.TRUE; - if((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null) && !compatConfig.transportInterClusterAuthEnabled()) { + boolean skipSecurityIfDualMode = threadContext.getTransient( + ConfigConstants.SECURITY_SSL_DUAL_MODE_SKIP_SECURITY + ) == Boolean.TRUE; + if ((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null) + && !compatConfig.transportInterClusterAuthEnabled()) { chain.proceed(task, action, request, listener); return; - } else if((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null || skipSecurityIfDualMode) && compatConfig.transportInterClusterPassiveAuthEnabled()) { - log.info("Transport auth in passive mode and no user found. Injecting default user"); - user = User.DEFAULT_TRANSPORT_USER; - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); - } else { - log.error("No user found for "+ action+" from "+request.remoteAddress()+" "+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)+" via "+threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE)+" "+threadContext.getHeaders()); - listener.onFailure(new OpenSearchSecurityException("No user found for "+action, RestStatus.INTERNAL_SERVER_ERROR)); - return; - } + } else if ((interClusterRequest || trustedClusterRequest || request.remoteAddress() == null || skipSecurityIfDualMode) + && compatConfig.transportInterClusterPassiveAuthEnabled()) { + log.info("Transport auth in passive mode and no user found. Injecting default user"); + user = User.DEFAULT_TRANSPORT_USER; + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + } else { + log.error( + "No user found for " + + action + + " from " + + request.remoteAddress() + + " " + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) + + " via " + + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) + + " " + + threadContext.getHeaders() + ); + listener.onFailure( + new OpenSearchSecurityException("No user found for " + action, RestStatus.INTERNAL_SERVER_ERROR) + ); + return; + } } final PrivilegesEvaluator eval = evalp; if (!eval.isInitialized()) { log.error("OpenSearch Security not initialized for {}", action); - listener.onFailure(new OpenSearchSecurityException("OpenSearch Security not initialized for " - + action, RestStatus.SERVICE_UNAVAILABLE)); + listener.onFailure( + new OpenSearchSecurityException("OpenSearch Security not initialized for " + action, RestStatus.SERVICE_UNAVAILABLE) + ); return; } @@ -317,18 +391,30 @@ private void ap chain.proceed(task, action, request, listener); } else { CreateIndexRequest createIndexRequest = createIndexRequestBuilder.request(); - log.info("Request {} requires new tenant index {} with aliases {}", - request.getClass().getSimpleName(), createIndexRequest.index(), alias2Name(createIndexRequest.aliases())); + log.info( + "Request {} requires new tenant index {} with aliases {}", + request.getClass().getSimpleName(), + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()) + ); createIndexRequestBuilder.execute(new ActionListener() { @Override public void onResponse(CreateIndexResponse createIndexResponse) { if (createIndexResponse.isAcknowledged()) { - log.debug("Request to create index {} with aliases {} acknowledged, proceeding with {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName()); + log.debug( + "Request to create index {} with aliases {} acknowledged, proceeding with {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName() + ); chain.proceed(task, action, request, listener); } else { - String message = LoggerMessageFormat.format("Request to create index {} with aliases {} was not acknowledged, failing {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName()); + String message = LoggerMessageFormat.format( + "Request to create index {} with aliases {} was not acknowledged, failing {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName() + ); log.error(message); listener.onFailure(new OpenSearchException(message)); } @@ -338,12 +424,22 @@ public void onResponse(CreateIndexResponse createIndexResponse) { public void onFailure(Exception e) { Throwable cause = ExceptionsHelper.unwrapCause(e); if (cause instanceof ResourceAlreadyExistsException) { - log.warn("Request to create index {} with aliases {} failed as the resource already exists, proceeding with {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName(), e); + log.warn( + "Request to create index {} with aliases {} failed as the resource already exists, proceeding with {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName(), + e + ); chain.proceed(task, action, request, listener); } else { - log.error("Request to create index {} with aliases {} failed, failing {}", - createIndexRequest.index(), alias2Name(createIndexRequest.aliases()), request.getClass().getSimpleName(), e); + log.error( + "Request to create index {} with aliases {} failed, failing {}", + createIndexRequest.index(), + alias2Name(createIndexRequest.aliases()), + request.getClass().getSimpleName(), + e + ); listener.onFailure(e); } } @@ -352,12 +448,16 @@ public void onFailure(Exception e) { } else { auditLog.logMissingPrivileges(action, request, task); String err; - if(!pres.getMissingSecurityRoles().isEmpty()) { + if (!pres.getMissingSecurityRoles().isEmpty()) { err = String.format("No mapping for %s on roles %s", user, pres.getMissingSecurityRoles()); } else { - err = (injectedRoles != null) ? - String.format("no permissions for %s and associated roles %s", pres.getMissingPrivileges(), pres.getResolvedSecurityRoles()) : - String.format("no permissions for %s and %s", pres.getMissingPrivileges(), user); + err = (injectedRoles != null) + ? String.format( + "no permissions for %s and associated roles %s", + pres.getMissingPrivileges(), + pres.getResolvedSecurityRoles() + ) + : String.format("no permissions for %s and %s", pres.getMissingPrivileges(), user); } log.debug(err); listener.onFailure(new OpenSearchSecurityException(err, RestStatus.FORBIDDEN)); @@ -370,7 +470,7 @@ public void onFailure(Exception e) { } listener.onFailure(e); } catch (Throwable e) { - log.error("Unexpected exception "+e, e); + log.error("Unexpected exception " + e, e); listener.onFailure(new OpenSearchSecurityException("Unexpected exception " + action, RestStatus.INTERNAL_SERVER_ERROR)); } } @@ -385,13 +485,13 @@ private static boolean isUserAdmin(User user, final AdminDNs adminDns) { private void attachSourceFieldContext(ActionRequest request) { - if(request instanceof SearchRequest && SourceFieldsContext.isNeeded((SearchRequest) request)) { - if(threadContext.getHeader("_opendistro_security_source_field_context") == null) { + if (request instanceof SearchRequest && SourceFieldsContext.isNeeded((SearchRequest) request)) { + if (threadContext.getHeader("_opendistro_security_source_field_context") == null) { final String serializedSourceFieldContext = Base64Helper.serializeObject(new SourceFieldsContext((SearchRequest) request)); threadContext.putHeader("_opendistro_security_source_field_context", serializedSourceFieldContext); } } else if (request instanceof GetRequest && SourceFieldsContext.isNeeded((GetRequest) request)) { - if(threadContext.getHeader("_opendistro_security_source_field_context") == null) { + if (threadContext.getHeader("_opendistro_security_source_field_context") == null) { final String serializedSourceFieldContext = Base64Helper.serializeObject(new SourceFieldsContext((GetRequest) request)); threadContext.putHeader("_opendistro_security_source_field_context", serializedSourceFieldContext); } @@ -401,13 +501,13 @@ private void attachSourceFieldContext(ActionRequest request) { @SuppressWarnings("rawtypes") private boolean checkImmutableIndices(Object request, ActionListener listener) { final boolean isModifyIndexRequest = request instanceof DeleteRequest - || request instanceof UpdateRequest - || request instanceof UpdateByQueryRequest - || request instanceof DeleteByQueryRequest - || request instanceof DeleteIndexRequest - || request instanceof RestoreSnapshotRequest - || request instanceof CloseIndexRequest - || request instanceof IndicesAliasesRequest; + || request instanceof UpdateRequest + || request instanceof UpdateByQueryRequest + || request instanceof DeleteByQueryRequest + || request instanceof DeleteIndexRequest + || request instanceof RestoreSnapshotRequest + || request instanceof CloseIndexRequest + || request instanceof IndicesAliasesRequest; if (isModifyIndexRequest && isRequestIndexImmutable(request)) { listener.onFailure(new OpenSearchSecurityException("Index is immutable", RestStatus.FORBIDDEN)); diff --git a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java index a5a23957ed..80bba54ea2 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityRestFilter.java @@ -83,13 +83,18 @@ public class SecurityRestFilter { 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 String REGEX_PATH_PREFIX = "/(" + LEGACY_OPENDISTRO_PREFIX + "|" + PLUGINS_PREFIX + ")/" + "(.*)"; private static final Pattern PATTERN_PATH_PREFIX = Pattern.compile(REGEX_PATH_PREFIX); - - public SecurityRestFilter(final BackendRegistry registry, final AuditLog auditLog, - final ThreadPool threadPool, final PrincipalExtractor principalExtractor, - final Settings settings, final Path configPath, final CompatConfig compatConfig) { + public SecurityRestFilter( + final BackendRegistry registry, + final AuditLog auditLog, + final ThreadPool threadPool, + final PrincipalExtractor principalExtractor, + final Settings settings, + final Path configPath, + final CompatConfig compatConfig + ) { super(); this.registry = registry; this.auditLog = auditLog; @@ -123,7 +128,9 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c org.apache.logging.log4j.ThreadContext.clearAll(); if (!checkAndAuthenticateRequest(request, channel, client)) { User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - if (userIsSuperAdmin(user, adminDNs) || (whitelistingSettings.checkRequestIsAllowed(request, channel, client) && allowlistingSettings.checkRequestIsAllowed(request, channel, client))) { + if (userIsSuperAdmin(user, adminDNs) + || (whitelistingSettings.checkRequestIsAllowed(request, channel, client) + && allowlistingSettings.checkRequestIsAllowed(request, channel, client))) { original.handleRequest(request, channel, client); } } @@ -138,12 +145,11 @@ private boolean userIsSuperAdmin(User user, AdminDNs adminDNs) { return user != null && adminDNs.isAdmin(user); } - private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel channel, - NodeClient client) throws Exception { + private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception { threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.REST.toString()); - if(HTTPHelper.containsBadHeader(request)) { + if (HTTPHelper.containsBadHeader(request)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); auditLog.logBadHeaders(request); @@ -151,7 +157,7 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha return true; } - if(SSLRequestHelper.containsBadHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX)) { + if (SSLRequestHelper.containsBadHeader(threadContext, ConfigConstants.OPENDISTRO_SECURITY_CONFIG_PREFIX)) { final OpenSearchException exception = ExceptionUtils.createBadHeaderException(); log.error(exception.toString()); auditLog.logBadHeaders(request); @@ -161,13 +167,13 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha final SSLInfo sslInfo; try { - if((sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor)) != null) { - if(sslInfo.getPrincipal() != null) { + if ((sslInfo = SSLRequestHelper.getSSLInfo(settings, configPath, request, principalExtractor)) != null) { + if (sslInfo.getPrincipal() != null) { threadContext.putTransient("_opendistro_security_ssl_principal", sslInfo.getPrincipal()); } - if(sslInfo.getX509Certs() != null) { - threadContext.putTransient("_opendistro_security_ssl_peer_certificates", sslInfo.getX509Certs()); + if (sslInfo.getX509Certs() != null) { + threadContext.putTransient("_opendistro_security_ssl_peer_certificates", sslInfo.getX509Certs()); } threadContext.putTransient("_opendistro_security_ssl_protocol", sslInfo.getProtocol()); threadContext.putTransient("_opendistro_security_ssl_cipher", sslInfo.getCipher()); @@ -179,22 +185,23 @@ private boolean checkAndAuthenticateRequest(RestRequest request, RestChannel cha return true; } - if(!compatConfig.restAuthEnabled()) { + if (!compatConfig.restAuthEnabled()) { return false; } 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)) - && !(WHO_AM_I_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"); return true; } else { // make it possible to filter logs by username - org.apache.logging.log4j.ThreadContext.put("user", ((User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)).getName()); + org.apache.logging.log4j.ThreadContext.put( + "user", + ((User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)).getName() + ); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java index 30e6134381..88ac128828 100644 --- a/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPBasicAuthenticator.java @@ -55,7 +55,7 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte final boolean forceLogin = request.paramAsBoolean("force_login", false); - if(forceLogin) { + if (forceLogin) { return null; } diff --git a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java index 373919669d..b1e5d4ef40 100644 --- a/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPClientCertAuthenticator.java @@ -71,16 +71,16 @@ public AuthCredentials extractCredentials(final RestRequest request, final Threa String username = principal.trim(); String[] backendRoles = null; - if(usernameAttribute != null && usernameAttribute.length() > 0) { + if (usernameAttribute != null && usernameAttribute.length() > 0) { final List usernames = getDnAttribute(rfc2253dn, usernameAttribute); - if(usernames.isEmpty() == false) { + if (usernames.isEmpty() == false) { username = usernames.get(0); } } - if(rolesAttribute != null && rolesAttribute.length() > 0) { + if (rolesAttribute != null && rolesAttribute.length() > 0) { final List roles = getDnAttribute(rfc2253dn, rolesAttribute); - if(roles.isEmpty() == false) { + if (roles.isEmpty() == false) { backendRoles = roles.toArray(new String[0]); } } diff --git a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java index 348811b694..a58a842394 100644 --- a/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/HTTPProxyAuthenticator.java @@ -52,13 +52,13 @@ public class HTTPProxyAuthenticator implements HTTPAuthenticator { public HTTPProxyAuthenticator(Settings settings, final Path configPath) { super(); this.settings = settings; - this.rolesSeparator = Pattern.compile(settings.get("roles_separator", ",")); + this.rolesSeparator = Pattern.compile(settings.get("roles_separator", ",")); } @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext context) { - if(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) != Boolean.TRUE) { + if (context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) != Boolean.TRUE) { throw new OpenSearchSecurityException("xff not done"); } @@ -76,11 +76,10 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte String[] backendRoles = null; if (!Strings.isNullOrEmpty(rolesHeader) && !Strings.isNullOrEmpty((String) request.header(rolesHeader))) { - backendRoles = rolesSeparator - .splitAsStream((String) request.header(rolesHeader)) - .map(String::trim) - .filter(Predicates.not(String::isEmpty)) - .toArray(String[]::new); + backendRoles = rolesSeparator.splitAsStream((String) request.header(rolesHeader)) + .map(String::trim) + .filter(Predicates.not(String::isEmpty)) + .toArray(String[]::new); } return new AuthCredentials((String) request.header(userHeader), backendRoles).markComplete(); } else { diff --git a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java index 5d9e933c8f..f464c0653a 100644 --- a/src/main/java/org/opensearch/security/http/RemoteIpDetector.java +++ b/src/main/java/org/opensearch/security/http/RemoteIpDetector.java @@ -73,21 +73,23 @@ final class RemoteIpDetector { * @return array of String (non null) */ protected static String[] commaDelimitedListToStringArray(String commaDelimitedStrings) { - return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) ? new String[0] : commaSeparatedValuesPattern - .split(commaDelimitedStrings); + return (commaDelimitedStrings == null || commaDelimitedStrings.length() == 0) + ? new String[0] + : commaSeparatedValuesPattern.split(commaDelimitedStrings); } /** * @see #setInternalProxies(String) */ private Pattern internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"); + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ); /** * @see #setRemoteIpHeader(String) @@ -113,31 +115,30 @@ public String getRemoteIpHeader() { return remoteIpHeader; } - String detect(RestRequest request, ThreadContext threadContext){ - final String originalRemoteAddr = ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getAddress().getHostAddress(); + String detect(RestRequest request, ThreadContext threadContext) { + final String originalRemoteAddr = ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress().getHostAddress(); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { log.trace("originalRemoteAddr {}", originalRemoteAddr); } - //X-Forwarded-For: client1, proxy1, proxy2 - // ^^^^^^ originalRemoteAddr + // X-Forwarded-For: client1, proxy1, proxy2 + // ^^^^^^ originalRemoteAddr - //originalRemoteAddr need to be in the list of internalProxies - if (internalProxies !=null && - internalProxies.matcher(originalRemoteAddr).matches()) { + // originalRemoteAddr need to be in the list of internalProxies + if (internalProxies != null && internalProxies.matcher(originalRemoteAddr).matches()) { String remoteIp = null; final StringBuilder concatRemoteIpHeaderValue = new StringBuilder(); - //client1, proxy1, proxy2 - final List remoteIpHeaders = request.getHeaders().get(remoteIpHeader); //X-Forwarded-For + // client1, proxy1, proxy2 + final List remoteIpHeaders = request.getHeaders().get(remoteIpHeader); // X-Forwarded-For - if(remoteIpHeaders == null || remoteIpHeaders.isEmpty()) { + if (remoteIpHeaders == null || remoteIpHeaders.isEmpty()) { return originalRemoteAddr; } - for (String rh:remoteIpHeaders) { + for (String rh : remoteIpHeaders) { if (concatRemoteIpHeaderValue.length() > 0) { concatRemoteIpHeaderValue.append(", "); } @@ -172,8 +173,15 @@ String detect(RestRequest request, ThreadContext threadContext){ if (remoteIp != null) { if (isTraceEnabled) { - final String originalRemoteHost = ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getAddress().getHostName(); - log.trace("Incoming request {} with originalRemoteAddr '{}', originalRemoteHost='{}', will be seen as newRemoteAddr='{}'", request.uri(), originalRemoteAddr, originalRemoteHost, remoteIp); + final String originalRemoteHost = ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getAddress() + .getHostName(); + log.trace( + "Incoming request {} with originalRemoteAddr '{}', originalRemoteHost='{}', will be seen as newRemoteAddr='{}'", + request.uri(), + originalRemoteAddr, + originalRemoteHost, + remoteIp + ); } threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE, Boolean.TRUE); @@ -185,7 +193,11 @@ String detect(RestRequest request, ThreadContext threadContext){ } else { if (isTraceEnabled) { - log.trace("Skip RemoteIpDetector for request {} with originalRemoteAddr '{}' cause no internal proxy matches", request.uri(), request.getHttpChannel().getRemoteAddress()); + log.trace( + "Skip RemoteIpDetector for request {} with originalRemoteAddr '{}' cause no internal proxy matches", + request.uri(), + request.getHttpChannel().getRemoteAddress() + ); } } diff --git a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java index 6f2f57053f..e9487a49a9 100644 --- a/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityHttpServerTransport.java @@ -40,9 +40,29 @@ public class SecurityHttpServerTransport extends SecuritySSLNettyHttpServerTransport { - public SecurityHttpServerTransport(final Settings settings, final NetworkService networkService, - final BigArrays bigArrays, final ThreadPool threadPool, final SecurityKeyStore odsks, - final SslExceptionHandler sslExceptionHandler, final NamedXContentRegistry namedXContentRegistry, final ValidatingDispatcher dispatcher, final ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { - super(settings, networkService, bigArrays, threadPool, odsks, namedXContentRegistry, dispatcher, sslExceptionHandler, clusterSettings, sharedGroupFactory); + public SecurityHttpServerTransport( + final Settings settings, + final NetworkService networkService, + final BigArrays bigArrays, + final ThreadPool threadPool, + final SecurityKeyStore odsks, + final SslExceptionHandler sslExceptionHandler, + final NamedXContentRegistry namedXContentRegistry, + final ValidatingDispatcher dispatcher, + final ClusterSettings clusterSettings, + SharedGroupFactory sharedGroupFactory + ) { + super( + settings, + networkService, + bigArrays, + threadPool, + odsks, + namedXContentRegistry, + dispatcher, + sslExceptionHandler, + clusterSettings, + sharedGroupFactory + ); } } diff --git a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java index 3c1dedc55e..1c21f0c4a2 100644 --- a/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java +++ b/src/main/java/org/opensearch/security/http/SecurityNonSslHttpServerTransport.java @@ -41,10 +41,16 @@ public class SecurityNonSslHttpServerTransport extends Netty4HttpServerTransport { - - public SecurityNonSslHttpServerTransport(final Settings settings, final NetworkService networkService, final BigArrays bigArrays, - final ThreadPool threadPool, final NamedXContentRegistry namedXContentRegistry, final Dispatcher dispatcher, - ClusterSettings clusterSettings, SharedGroupFactory sharedGroupFactory) { + public SecurityNonSslHttpServerTransport( + final Settings settings, + final NetworkService networkService, + final BigArrays bigArrays, + final ThreadPool threadPool, + final NamedXContentRegistry namedXContentRegistry, + final Dispatcher dispatcher, + ClusterSettings clusterSettings, + SharedGroupFactory sharedGroupFactory + ) { super(settings, networkService, bigArrays, threadPool, namedXContentRegistry, dispatcher, clusterSettings, sharedGroupFactory); } diff --git a/src/main/java/org/opensearch/security/http/XFFResolver.java b/src/main/java/org/opensearch/security/http/XFFResolver.java index c44e98537d..aff5043f61 100644 --- a/src/main/java/org/opensearch/security/http/XFFResolver.java +++ b/src/main/java/org/opensearch/security/http/XFFResolver.java @@ -59,39 +59,48 @@ public TransportAddress resolve(final RestRequest request) throws OpenSearchSecu log.trace("resolve {}", request.getHttpChannel().getRemoteAddress()); } - if(enabled && request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress && request.getHttpChannel() instanceof Netty4HttpChannel) { + if (enabled + && request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress + && request.getHttpChannel() instanceof Netty4HttpChannel) { - final InetSocketAddress isa = new InetSocketAddress(detector.detect(request, threadContext), ((InetSocketAddress)request.getHttpChannel().getRemoteAddress()).getPort()); + final InetSocketAddress isa = new InetSocketAddress( + detector.detect(request, threadContext), + ((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).getPort() + ); - if(isa.isUnresolved()) { - throw new OpenSearchSecurityException("Cannot resolve address "+isa.getHostString()); + if (isa.isUnresolved()) { + throw new OpenSearchSecurityException("Cannot resolve address " + isa.getHostString()); } - if (isTraceEnabled) { - if(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) == Boolean.TRUE) { + if (threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_XFF_DONE) == Boolean.TRUE) { log.trace("xff resolved {} to {}", request.getHttpChannel().getRemoteAddress(), isa); } else { - log.trace("no xff done for {}",request.getClass()); + log.trace("no xff done for {}", request.getClass()); } } return new TransportAddress(isa); - } else if(request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress){ + } else if (request.getHttpChannel().getRemoteAddress() instanceof InetSocketAddress) { if (isTraceEnabled) { - log.trace("no xff done (enabled or no netty request) {},{},{},{}",enabled, request.getClass()); + log.trace("no xff done (enabled or no netty request) {},{},{},{}", enabled, request.getClass()); } - return new TransportAddress((InetSocketAddress)request.getHttpChannel().getRemoteAddress()); + return new TransportAddress((InetSocketAddress) request.getHttpChannel().getRemoteAddress()); } else { - throw new OpenSearchSecurityException("Cannot handle this request. Remote address is "+request.getHttpChannel().getRemoteAddress()+" with request class "+request.getClass()); + throw new OpenSearchSecurityException( + "Cannot handle this request. Remote address is " + + request.getHttpChannel().getRemoteAddress() + + " with request class " + + request.getClass() + ); } } @Subscribe public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { enabled = dcm.isXffEnabled(); - if(enabled) { + if (enabled) { detector = new RemoteIpDetector(); detector.setInternalProxies(dcm.getInternalProxies()); detector.setRemoteIpHeader(dcm.getRemoteIpHeader()); diff --git a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java index e98f26d85a..ef20374d69 100644 --- a/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java +++ b/src/main/java/org/opensearch/security/http/proxy/HTTPExtendedProxyAuthenticator.java @@ -42,7 +42,7 @@ import org.opensearch.security.http.HTTPProxyAuthenticator; import org.opensearch.security.user.AuthCredentials; -public class HTTPExtendedProxyAuthenticator extends HTTPProxyAuthenticator{ +public class HTTPExtendedProxyAuthenticator extends HTTPProxyAuthenticator { private static final String ATTR_PROXY = "attr.proxy."; private static final String ATTR_PROXY_USERNAME = "attr.proxy.username"; @@ -56,16 +56,16 @@ public HTTPExtendedProxyAuthenticator(Settings settings, final Path configPath) @Override public AuthCredentials extractCredentials(final RestRequest request, ThreadContext context) { - AuthCredentials credentials = super.extractCredentials(request, context); - if(credentials == null) { - return null; - } + AuthCredentials credentials = super.extractCredentials(request, context); + if (credentials == null) { + return null; + } String attrHeaderPrefix = settings.get("attr_header_prefix"); - if(Strings.isNullOrEmpty(attrHeaderPrefix)) { + if (Strings.isNullOrEmpty(attrHeaderPrefix)) { log.debug("attr_header_prefix is null. Skipping additional attribute extraction"); return credentials; - } else if(log.isDebugEnabled()) { + } else if (log.isDebugEnabled()) { log.debug("attrHeaderPrefix {}", attrHeaderPrefix); } @@ -73,10 +73,10 @@ public AuthCredentials extractCredentials(final RestRequest request, ThreadConte attrHeaderPrefix = attrHeaderPrefix.toLowerCase(); for (Entry> entry : request.getHeaders().entrySet()) { String key = entry.getKey().toLowerCase(); - if(key.startsWith(attrHeaderPrefix)) { + if (key.startsWith(attrHeaderPrefix)) { key = ATTR_PROXY + key.substring(attrHeaderPrefix.length()); credentials.addAttribute(key, Joiner.on(",").join(entry.getValue().iterator())); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("Found user custom attribute '{}'", key); } } diff --git a/src/main/java/org/opensearch/security/httpclient/HttpClient.java b/src/main/java/org/opensearch/security/httpclient/HttpClient.java index ad507ea47c..ba788a2c13 100644 --- a/src/main/java/org/opensearch/security/httpclient/HttpClient.java +++ b/src/main/java/org/opensearch/security/httpclient/HttpClient.java @@ -117,8 +117,18 @@ public HttpClientBuilder setSupportedCipherSuites(String[] cipherSuites) { } public HttpClient build() throws Exception { - return new HttpClient(trustStore, basicCredentials, keystore, keyPassword, keystoreAlias, verifyHostnames, ssl, - supportedProtocols, supportedCipherSuites, servers); + return new HttpClient( + trustStore, + basicCredentials, + keystore, + keyPassword, + keystoreAlias, + verifyHostnames, + ssl, + supportedProtocols, + supportedCipherSuites, + servers + ); } private static String encodeBasicHeader(final String username, final String password) { @@ -143,10 +153,19 @@ public static HttpClientBuilder builder(final String... servers) { private String[] supportedProtocols; private String[] supportedCipherSuites; - private HttpClient(final KeyStore trustStore, final String basicCredentials, final KeyStore keystore, - final char[] keyPassword, final String keystoreAlias, final boolean verifyHostnames, final boolean ssl, String[] supportedProtocols, String[] supportedCipherSuites, final String... servers) - throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, - IOException { + private HttpClient( + final KeyStore trustStore, + final String basicCredentials, + final KeyStore keystore, + final char[] keyPassword, + final String keystoreAlias, + final boolean verifyHostnames, + final boolean ssl, + String[] supportedProtocols, + String[] supportedCipherSuites, + final String... servers + ) throws UnrecoverableKeyException, KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, + IOException { super(); this.trustStore = trustStore; this.basicCredentials = basicCredentials; @@ -159,13 +178,13 @@ private HttpClient(final KeyStore trustStore, final String basicCredentials, fin this.keystoreAlias = keystoreAlias; HttpHost[] hosts = Arrays.stream(servers) - .map(s->s.split(":")) - .map(s->new HttpHost(ssl?"https":"http", s[0], Integer.parseInt(s[1]))) - .collect(Collectors.toList()).toArray(new HttpHost[0]); - + .map(s -> s.split(":")) + .map(s -> new HttpHost(ssl ? "https" : "http", s[0], Integer.parseInt(s[1]))) + .collect(Collectors.toList()) + .toArray(new HttpHost[0]); RestClientBuilder builder = RestClient.builder(hosts); - //builder.setMaxRetryTimeoutMillis(10000); + // builder.setMaxRetryTimeoutMillis(10000); builder.setFailureListener(new RestClient.FailureListener() { @Override @@ -181,7 +200,7 @@ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpCli try { return asyncClientBuilder(httpClientBuilder); } catch (Exception e) { - log.error("Unable to build http client",e); + log.error("Unable to build http client", e); throw new RuntimeException(e); } } @@ -192,24 +211,25 @@ public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpCli public boolean index(final String content, final String index, final String type, final boolean refresh) { - try { + try { - final IndexRequest ir = new IndexRequest(index); + final IndexRequest ir = new IndexRequest(index); - final IndexResponse response = rclient.index(ir - .setRefreshPolicy(refresh?RefreshPolicy.IMMEDIATE:RefreshPolicy.NONE) - .source(content, XContentType.JSON), RequestOptions.DEFAULT); + final IndexResponse response = rclient.index( + ir.setRefreshPolicy(refresh ? RefreshPolicy.IMMEDIATE : RefreshPolicy.NONE).source(content, XContentType.JSON), + RequestOptions.DEFAULT + ); - return response.getShardInfo().getSuccessful() > 0 && response.getShardInfo().getFailed() == 0; + return response.getShardInfo().getSuccessful() > 0 && response.getShardInfo().getFailed() == 0; - } catch (Exception e) { - log.error(e.toString(),e); - return false; - } + } catch (Exception e) { + log.error(e.toString(), e); + return false; + } } - private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder httpClientBuilder) - throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { + private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder httpClientBuilder) throws NoSuchAlgorithmException, + KeyStoreException, UnrecoverableKeyException, KeyManagementException { // basic auth // pki auth @@ -231,11 +251,11 @@ private final HttpAsyncClientBuilder asyncClientBuilder(HttpAsyncClientBuilder h @Override public String chooseAlias(Map aliases, SSLParameters sslParameters) { - if(aliases == null || aliases.isEmpty()) { + if (aliases == null || aliases.isEmpty()) { return keystoreAlias; } - if(keystoreAlias == null || keystoreAlias.isEmpty()) { + if (keystoreAlias == null || keystoreAlias.isEmpty()) { return aliases.keySet().iterator().next(); } @@ -248,35 +268,36 @@ public String chooseAlias(Map aliases, SSLParameters final SSLContext sslContext = sslContextBuilder.build(); TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() - .setSslContext(sslContext) - .setTlsVersions(supportedProtocols) - .setCiphers(supportedCipherSuites) - .setHostnameVerifier(hnv) - // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 - .setTlsDetailsFactory(new Factory() { - @Override - public TlsDetails create(final SSLEngine sslEngine) { - return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); - } - }) - .build(); + .setSslContext(sslContext) + .setTlsVersions(supportedProtocols) + .setCiphers(supportedCipherSuites) + .setHostnameVerifier(hnv) + // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 + .setTlsDetailsFactory(new Factory() { + @Override + public TlsDetails create(final SSLEngine sslEngine) { + return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol()); + } + }) + .build(); - final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create() - .setTlsStrategy(tlsStrategy) - .build(); + final AsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(tlsStrategy).build(); httpClientBuilder.setConnectionManager(cm); } if (basicCredentials != null) { - httpClientBuilder.setDefaultHeaders(Lists.newArrayList(new BasicHeader(HttpHeaders.AUTHORIZATION, "Basic " + basicCredentials))); + httpClientBuilder.setDefaultHeaders( + Lists.newArrayList(new BasicHeader(HttpHeaders.AUTHORIZATION, "Basic " + basicCredentials)) + ); } // TODO: set a timeout until we have a proper way to deal with back pressure int timeout = 5; RequestConfig config = RequestConfig.custom() - .setConnectTimeout(timeout, TimeUnit.SECONDS) - .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS).build(); + .setConnectTimeout(timeout, TimeUnit.SECONDS) + .setConnectionRequestTimeout(timeout, TimeUnit.SECONDS) + .build(); httpClientBuilder.setDefaultRequestConfig(config); diff --git a/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java index b146365d57..57c1c18414 100644 --- a/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PitPrivilegesEvaluator.java @@ -30,7 +30,6 @@ import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.user.User; - /** * This class evaluates privileges for point in time (Delete and List all) operations. * For aliases - users must have either alias permission or backing index permissions @@ -38,13 +37,18 @@ */ public class PitPrivilegesEvaluator { - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final ClusterService clusterService, - final User user, final SecurityRoles securityRoles, final String action, - final IndexNameExpressionResolver resolver, - final PrivilegesEvaluatorResponse presponse, - final IndexResolverReplacer irr) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final ClusterService clusterService, + final User user, + final SecurityRoles securityRoles, + final String action, + final IndexNameExpressionResolver resolver, + final PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr + ) { - if(!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) { + if (!(request instanceof DeletePitRequest || request instanceof PitSegmentsRequest)) { return presponse; } List pitIds = new ArrayList<>(); @@ -52,7 +56,7 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final C if (request instanceof DeletePitRequest) { DeletePitRequest deletePitRequest = (DeletePitRequest) request; pitIds = deletePitRequest.getPitIds(); - } else if(request instanceof PitSegmentsRequest) { + } else if (request instanceof PitSegmentsRequest) { PitSegmentsRequest pitSegmentsRequest = (PitSegmentsRequest) request; pitIds = pitSegmentsRequest.getPitIds(); } @@ -60,29 +64,32 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final C if (pitIds.size() == 1 && "_all".equals(pitIds.get(0))) { return presponse; } else { - return handlePitsAccess(pitIds, clusterService, user, securityRoles, - action, resolver, presponse, irr); + return handlePitsAccess(pitIds, clusterService, user, securityRoles, action, resolver, presponse, irr); } } /** * Handle access for delete operation / pit segments operation where PIT IDs are explicitly passed */ - private PrivilegesEvaluatorResponse handlePitsAccess(List pitIds, ClusterService clusterService, - User user, SecurityRoles securityRoles, final String action, - IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse, - final IndexResolverReplacer irr) { - Map pitToIndicesMap = OpenSearchSecurityPlugin. - GuiceHolder.getPitService().getIndicesForPits(pitIds); + private PrivilegesEvaluatorResponse handlePitsAccess( + List pitIds, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + final String action, + IndexNameExpressionResolver resolver, + PrivilegesEvaluatorResponse presponse, + final IndexResolverReplacer irr + ) { + Map pitToIndicesMap = OpenSearchSecurityPlugin.GuiceHolder.getPitService().getIndicesForPits(pitIds); Set pitIndices = new HashSet<>(); // add indices across all PITs to a set and evaluate if user has access to all indices - for(String[] indices: pitToIndicesMap.values()) { + for (String[] indices : pitToIndicesMap.values()) { pitIndices.addAll(Arrays.asList(indices)); } - Set allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user, - securityRoles, action, resolver, irr); + Set allPermittedIndices = getPermittedIndices(pitIndices, clusterService, user, securityRoles, action, resolver, irr); // Only if user has access to all PIT's indices, allow operation, otherwise continue evaluation in PrivilegesEvaluator. - if(allPermittedIndices.containsAll(pitIndices)) { + if (allPermittedIndices.containsAll(pitIndices)) { presponse.allowed = true; presponse.markComplete(); } @@ -92,14 +99,18 @@ private PrivilegesEvaluatorResponse handlePitsAccess(List pitIds, Cluste /** * This method returns list of permitted indices for the PIT indices passed */ - private Set getPermittedIndices(Set pitIndices, ClusterService clusterService, - User user, SecurityRoles securityRoles, final String action, - IndexNameExpressionResolver resolver, final IndexResolverReplacer irr) { + private Set getPermittedIndices( + Set pitIndices, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + final String action, + IndexNameExpressionResolver resolver, + final IndexResolverReplacer irr + ) { String[] indicesArr = new String[pitIndices.size()]; - CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true, - pitIndices.toArray(indicesArr)); + CreatePitRequest req = new CreatePitRequest(new TimeValue(1, TimeUnit.DAYS), true, pitIndices.toArray(indicesArr)); final IndexResolverReplacer.Resolved pitResolved = irr.resolveRequest(req); - return securityRoles.reduce(pitResolved, - user, new String[]{action}, resolver, clusterService); + return securityRoles.reduce(pitResolved, user, new String[] { action }, resolver, clusterService); } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 278dc86b7c..b118a62e5d 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -105,7 +105,7 @@ public class PrivilegesEvaluator { private static final WildcardMatcher ACTION_MATCHER = WildcardMatcher.from("indices:data/read/*search*"); private static final Pattern DNFOF_PATTERNS = Pattern.compile( - "indices:(data/read/.*|(admin/(mappings/fields/get.*|shards/search_shards|resolve/index)))" + "indices:(data/read/.*|(admin/(mappings/fields/get.*|shards/search_shards|resolve/index)))" ); private static final IndicesOptions ALLOW_EMPTY = IndicesOptions.fromOptions(true, true, false, false); @@ -135,10 +135,19 @@ public class PrivilegesEvaluator { private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; - public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool threadPool, - final ConfigurationRepository configurationRepository, final IndexNameExpressionResolver resolver, - AuditLog auditLog, final Settings settings, final PrivilegesInterceptor privilegesInterceptor, final ClusterInfoHolder clusterInfoHolder, - final IndexResolverReplacer irr, boolean dlsFlsEnabled, NamedXContentRegistry namedXContentRegistry) { + public PrivilegesEvaluator( + final ClusterService clusterService, + final ThreadPool threadPool, + final ConfigurationRepository configurationRepository, + final IndexNameExpressionResolver resolver, + AuditLog auditLog, + final Settings settings, + final PrivilegesInterceptor privilegesInterceptor, + final ClusterInfoHolder clusterInfoHolder, + final IndexResolverReplacer irr, + boolean dlsFlsEnabled, + NamedXContentRegistry namedXContentRegistry + ) { super(); this.clusterService = clusterService; @@ -148,9 +157,10 @@ public PrivilegesEvaluator(final ClusterService clusterService, final ThreadPool this.threadContext = threadPool.getThreadContext(); this.privilegesInterceptor = privilegesInterceptor; - - this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean(ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, - ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES); + this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean( + ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, + ConfigConstants.SECURITY_DEFAULT_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES + ); this.clusterInfoHolder = clusterInfoHolder; this.irr = irr; @@ -178,9 +188,7 @@ private SecurityRoles getSecurityRoles(Set roles) { return configModel.getSecurityRoles().filter(roles); } - public boolean hasRestAdminPermissions(final User user, - final TransportAddress remoteAddress, - final String permissions) { + public boolean hasRestAdminPermissions(final User user, final TransportAddress remoteAddress, final String permissions) { final Set userRoles = mapRoles(user, remoteAddress); return hasRestAdminPermissions(userRoles, permissions); } @@ -191,7 +199,7 @@ private boolean hasRestAdminPermissions(final Set roles, String permissi } public boolean isInitialized() { - return configModel !=null && configModel.getSecurityRoles() != null && dcm != null; + return configModel != null && configModel.getSecurityRoles() != null && dcm != null; } private void setUserInfoInThreadContext(User user, Set mappedRoles) { @@ -208,14 +216,19 @@ private void setUserInfoInThreadContext(User user, Set mappedRoles) { } } - public PrivilegesEvaluatorResponse evaluate(final User user, String action0, final ActionRequest request, - Task task, final Set injectedRoles) { + public PrivilegesEvaluatorResponse evaluate( + final User user, + String action0, + final ActionRequest request, + Task task, + final Set injectedRoles + ) { if (!isInitialized()) { throw new OpenSearchSecurityException("OpenSearch Security is not initialized."); } - if(action0.startsWith("internal:indices/admin/upgrade")) { + if (action0.startsWith("internal:indices/admin/upgrade")) { action0 = "indices:admin/upgrade"; } @@ -231,10 +244,12 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); Set mappedRoles = (injectedRoles == null) ? mapRoles(user, caller) : injectedRoles; - final String injectedRolesValidationString = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION); - if(injectedRolesValidationString != null) { + final String injectedRolesValidationString = threadContext.getTransient( + ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION + ); + if (injectedRolesValidationString != null) { HashSet injectedRolesValidationSet = new HashSet<>(Arrays.asList(injectedRolesValidationString.split(","))); - if(!mappedRoles.containsAll(injectedRolesValidationSet)) { + if (!mappedRoles.containsAll(injectedRolesValidationSet)) { presponse.allowed = false; presponse.missingSecurityRoles.addAll(injectedRolesValidationSet); log.info("Roles {} are not mapped to the user {}", injectedRolesValidationSet, user); @@ -257,15 +272,23 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant()))) { - // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action indices:data/write/bulk[s]). - // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default tenants. - // No further access check for the default tenant is necessary, as access will be also checked on the TransportShardBulkAction level. + // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action + // indices:data/write/bulk[s]). + // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default + // tenants. + // No further access check for the default tenant is necessary, as access will be also checked on the TransportShardBulkAction + // level. if (!securityRoles.impliesClusterPermissionPermission(action0)) { presponse.missingPrivileges.add(action0); presponse.allowed = false; - log.info("No cluster-level perm match for {} [Action [{}]] [RolesChecked {}]. No permissions for {}", user, action0, - securityRoles.getRoleNames(), presponse.missingPrivileges); + log.info( + "No cluster-level perm match for {} [Action [{}]] [RolesChecked {}]. No permissions for {}", + user, + action0, + securityRoles.getRoleNames(), + presponse.missingPrivileges + ); } else { presponse.allowed = true; } @@ -275,7 +298,6 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin final Resolved requestedResolved = irr.resolveRequest(request); presponse.resolved = requestedResolved; - if (isDebugEnabled) { log.debug("RequestedResolved : {}", requestedResolved); } @@ -296,8 +318,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } // check access for point in time requests - if(pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, - action0, resolver, presponse, irr).isComplete()) { + if (pitPrivilegesEvaluator.evaluate(request, clusterService, user, securityRoles, action0, resolver, presponse, irr).isComplete()) { return presponse; } @@ -308,27 +329,44 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin log.trace("dnfof enabled? {}", dnfofEnabled); } - presponse.evaluatedDlsFlsConfig = getSecurityRoles(mappedRoles).getDlsFls(user, dfmEmptyOverwritesAll, resolver, clusterService, namedXContentRegistry); - + presponse.evaluatedDlsFlsConfig = getSecurityRoles(mappedRoles).getDlsFls( + user, + dfmEmptyOverwritesAll, + resolver, + clusterService, + namedXContentRegistry + ); if (isClusterPerm(action0)) { - if(!securityRoles.impliesClusterPermissionPermission(action0)) { + if (!securityRoles.impliesClusterPermissionPermission(action0)) { presponse.missingPrivileges.add(action0); presponse.allowed = false; - log.info("No cluster-level perm match for {} {} [Action [{}]] [RolesChecked {}]. No permissions for {}", user, requestedResolved, action0, - securityRoles.getRoleNames(), presponse.missingPrivileges); + log.info( + "No cluster-level perm match for {} {} [Action [{}]] [RolesChecked {}]. No permissions for {}", + user, + requestedResolved, + action0, + securityRoles.getRoleNames(), + presponse.missingPrivileges + ); return presponse; } else { - if(request instanceof RestoreSnapshotRequest && checkSnapshotRestoreWritePrivileges) { + if (request instanceof RestoreSnapshotRequest && checkSnapshotRestoreWritePrivileges) { if (isDebugEnabled) { log.debug("Normally allowed but we need to apply some extra checks for a restore request."); } } else { - if(privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { + if (privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { - final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex(request, action0, user, dcm, requestedResolved, - mapTenants(user, mappedRoles)); + final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex( + request, + action0, + user, + dcm, + requestedResolved, + mapTenants(user, mappedRoles) + ); if (isDebugEnabled) { log.debug("Result from privileges interceptor for cluster perm: {}", replaceResult); @@ -345,26 +383,28 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } } - if (dnfofEnabled - && (action0.startsWith("indices:data/read/")) - && !requestedResolved.getAllIndices().isEmpty() - ) { + if (dnfofEnabled && (action0.startsWith("indices:data/read/")) && !requestedResolved.getAllIndices().isEmpty()) { - if(requestedResolved.getAllIndices().isEmpty()) { + if (requestedResolved.getAllIndices().isEmpty()) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; } + Set reduced = securityRoles.reduce( + requestedResolved, + user, + new String[] { action0 }, + resolver, + clusterService + ); - Set reduced = securityRoles.reduce(requestedResolved, user, new String[]{action0}, resolver, clusterService); - - if(reduced.isEmpty()) { + if (reduced.isEmpty()) { presponse.allowed = false; return presponse; } - if(irr.replace(request, true, reduced.toArray(new String[0]))) { + if (irr.replace(request, true, reduced.toArray(new String[0]))) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; @@ -386,7 +426,8 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin } // term aggregations - if (termsAggregationEvaluator.evaluate(requestedResolved, request, clusterService, user, securityRoles, resolver, presponse) .isComplete()) { + if (termsAggregationEvaluator.evaluate(requestedResolved, request, clusterService, user, securityRoles, resolver, presponse) + .isComplete()) { return presponse; } @@ -405,11 +446,18 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin log.debug("Security roles: {}", securityRoles.getRoleNames()); } - //TODO exclude Security index + // TODO exclude Security index - if(privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { + if (privilegesInterceptor.getClass() != PrivilegesInterceptor.class) { - final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex(request, action0, user, dcm, requestedResolved, mapTenants(user, mappedRoles)); + final PrivilegesInterceptor.ReplaceResult replaceResult = privilegesInterceptor.replaceDashboardsIndex( + request, + action0, + user, + dcm, + requestedResolved, + mapTenants(user, mappedRoles) + ); if (isDebugEnabled) { log.debug("Result from privileges interceptor: {}", replaceResult); @@ -428,7 +476,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin if (dnfofEnabled && DNFOF_PATTERNS.matcher(action0).matches()) { - if(requestedResolved.getAllIndices().isEmpty()) { + if (requestedResolved.getAllIndices().isEmpty()) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; @@ -436,18 +484,18 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin Set reduced = securityRoles.reduce(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); - if(reduced.isEmpty()) { - if(dcm.isDnfofForEmptyResultsEnabled() && request instanceof IndicesRequest.Replaceable) { + if (reduced.isEmpty()) { + if (dcm.isDnfofForEmptyResultsEnabled() && request instanceof IndicesRequest.Replaceable) { ((IndicesRequest.Replaceable) request).indices(new String[0]); presponse.missingPrivileges.clear(); presponse.allowed = true; - if(request instanceof SearchRequest) { + if (request instanceof SearchRequest) { ((SearchRequest) request).indicesOptions(ALLOW_EMPTY); - } else if(request instanceof ClusterSearchShardsRequest) { + } else if (request instanceof ClusterSearchShardsRequest) { ((ClusterSearchShardsRequest) request).indicesOptions(ALLOW_EMPTY); - } else if(request instanceof GetFieldMappingsRequest) { + } else if (request instanceof GetFieldMappingsRequest) { ((GetFieldMappingsRequest) request).indicesOptions(ALLOW_EMPTY); } @@ -457,16 +505,14 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin return presponse; } - - if(irr.replace(request, true, reduced.toArray(new String[0]))) { + if (irr.replace(request, true, reduced.toArray(new String[0]))) { presponse.missingPrivileges.clear(); presponse.allowed = true; return presponse; } } - - //not bulk, mget, etc request here + // not bulk, mget, etc request here boolean permGiven = false; if (isDebugEnabled) { @@ -475,19 +521,25 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin if (dcm.isMultiRolespanEnabled()) { permGiven = securityRoles.impliesTypePermGlobal(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); - } else { + } else { permGiven = securityRoles.get(requestedResolved, user, allIndexPermsRequiredA, resolver, clusterService); } - if (!permGiven) { - log.info("No {}-level perm match for {} {} [Action [{}]] [RolesChecked {}]", "index" , user, requestedResolved, action0, - securityRoles.getRoleNames()); + if (!permGiven) { + log.info( + "No {}-level perm match for {} {} [Action [{}]] [RolesChecked {}]", + "index", + user, + requestedResolved, + action0, + securityRoles.getRoleNames() + ); log.info("No permissions for {}", presponse.missingPrivileges); } else { - if(checkFilteredAliases(requestedResolved, action0, isDebugEnabled)) { - presponse.allowed=false; + if (checkFilteredAliases(requestedResolved, action0, isDebugEnabled)) { + presponse.allowed = false; return presponse; } @@ -505,26 +557,21 @@ public Set mapRoles(final User user, final TransportAddress caller) { return this.configModel.mapSecurityRoles(user, caller); } - public Map mapTenants(final User user, Set roles) { return this.configModel.mapTenants(user, roles); } - - public Set getAllConfiguredTenantNames() { return configModel.getAllConfiguredTenantNames(); } public boolean multitenancyEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDashboardsMultitenancyEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDashboardsMultitenancyEnabled(); } public boolean privateTenantEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDashboardsPrivateTenantEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDashboardsPrivateTenantEnabled(); } public String dashboardsDefaultTenant() { @@ -532,8 +579,7 @@ public String dashboardsDefaultTenant() { } public boolean notFailOnForbiddenEnabled() { - return privilegesInterceptor.getClass() != PrivilegesInterceptor.class - && dcm.isDnfofEnabled(); + return privilegesInterceptor.getClass() != PrivilegesInterceptor.class && dcm.isDnfofEnabled(); } public String dashboardsIndex() { @@ -549,10 +595,10 @@ public String dashboardsOpenSearchRole() { } private Set evaluateAdditionalIndexPermissions(final ActionRequest request, final String originalAction) { - //--- check inner bulk requests + // --- check inner bulk requests final Set additionalPermissionsRequired = new HashSet<>(); - if(!isClusterPerm(originalAction)) { + if (!isClusterPerm(originalAction)) { additionalPermissionsRequired.add(originalAction); } @@ -564,18 +610,18 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque BulkShardRequest bsr = (BulkShardRequest) request; for (BulkItemRequest bir : bsr.items()) { switch (bir.request().opType()) { - case CREATE: - additionalPermissionsRequired.add(IndexAction.NAME); - break; - case INDEX: - additionalPermissionsRequired.add(IndexAction.NAME); - break; - case DELETE: - additionalPermissionsRequired.add(DeleteAction.NAME); - break; - case UPDATE: - additionalPermissionsRequired.add(UpdateAction.NAME); - break; + case CREATE: + additionalPermissionsRequired.add(IndexAction.NAME); + break; + case INDEX: + additionalPermissionsRequired.add(IndexAction.NAME); + break; + case DELETE: + additionalPermissionsRequired.add(DeleteAction.NAME); + break; + case UPDATE: + additionalPermissionsRequired.add(UpdateAction.NAME); + break; } } } @@ -584,11 +630,11 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque IndicesAliasesRequest bsr = (IndicesAliasesRequest) request; for (AliasActions bir : bsr.getAliasActions()) { switch (bir.actionType()) { - case REMOVE_INDEX: - additionalPermissionsRequired.add(DeleteIndexAction.NAME); - break; - default: - break; + case REMOVE_INDEX: + additionalPermissionsRequired.add(DeleteIndexAction.NAME); + break; + default: + break; } } } @@ -616,9 +662,9 @@ private Set evaluateAdditionalIndexPermissions(final ActionRequest reque } public static boolean isClusterPerm(String action0) { - return ( action0.startsWith("cluster:") - || action0.startsWith("indices:admin/template/") - || action0.startsWith("indices:admin/index_template/") + return (action0.startsWith("cluster:") + || action0.startsWith("indices:admin/template/") + || action0.startsWith("indices:admin/index_template/") || action0.startsWith(SearchScrollAction.NAME) || (action0.equals(BulkAction.NAME)) || (action0.equals(MultiGetAction.NAME)) @@ -626,7 +672,7 @@ public static boolean isClusterPerm(String action0) { || (action0.equals(MultiTermVectorsAction.NAME)) || (action0.equals(ReindexAction.NAME)) - ) ; + ); } @SuppressWarnings("unchecked") @@ -667,20 +713,20 @@ public Iterator iterator() { indexMetaDataCollection = indexMetaDataSet; } - //check filtered aliases + // check filtered aliases for (IndexMetadata indexMetaData : indexMetaDataCollection) { final List filteredAliases = new ArrayList(); final Map aliases = indexMetaData.getAliases(); - if(aliases != null && aliases.size() > 0) { + if (aliases != null && aliases.size() > 0) { if (isDebugEnabled) { log.debug("Aliases for {}: {}", indexMetaData.getIndex().getName(), aliases); } final Iterator it = aliases.keySet().iterator(); - while(it.hasNext()) { + while (it.hasNext()) { final String alias = it.next(); final AliasMetadata aliasMetadata = aliases.get(alias); @@ -697,18 +743,21 @@ public Iterator iterator() { } } - if(filteredAliases.size() > 1 && ACTION_MATCHER.test(action)) { - //TODO add queries as dls queries (works only if dls module is installed) - log.error("More than one ({}) filtered alias found for same index ({}). This is currently not supported. Aliases: {}", - filteredAliases.size(), indexMetaData.getIndex().getName(), toString(filteredAliases)); + if (filteredAliases.size() > 1 && ACTION_MATCHER.test(action)) { + // TODO add queries as dls queries (works only if dls module is installed) + log.error( + "More than one ({}) filtered alias found for same index ({}). This is currently not supported. Aliases: {}", + filteredAliases.size(), + indexMetaData.getIndex().getName(), + toString(filteredAliases) + ); return true; } - } //end-for + } // end-for return false; } - private boolean checkDocAllowListHeader(User user, String action, ActionRequest request) { String docAllowListHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_DOC_ALLOWLIST_HEADER); @@ -741,14 +790,14 @@ private boolean checkDocAllowListHeader(User user, String action, ActionRequest } private List toString(List aliases) { - if(aliases == null || aliases.size() == 0) { + if (aliases == null || aliases.size() == 0) { return Collections.emptyList(); } final List ret = new ArrayList<>(aliases.size()); - for(final AliasMetadata amd: aliases) { - if(amd != null) { + for (final AliasMetadata amd : aliases) { + if (amd != null) { ret.add(amd.alias()); } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java index 31ce7095d2..eb082a4a9f 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java @@ -44,19 +44,24 @@ public class PrivilegesEvaluatorResponse { CreateIndexRequestBuilder createIndexRequestBuilder; public Resolved getResolved() { - return resolved; - } + return resolved; + } public boolean isAllowed() { return allowed; } + public Set getMissingPrivileges() { return new HashSet(missingPrivileges); } - public Set getMissingSecurityRoles() {return new HashSet<>(missingSecurityRoles); } + public Set getMissingSecurityRoles() { + return new HashSet<>(missingSecurityRoles); + } - public Set getResolvedSecurityRoles() {return new HashSet<>(resolvedSecurityRoles); } + public Set getResolvedSecurityRoles() { + return new HashSet<>(resolvedSecurityRoles); + } public EvaluatedDlsFlsConfig getEvaluatedDlsFlsConfig() { return evaluatedDlsFlsConfig; @@ -86,8 +91,13 @@ public boolean isPending() { @Override public String toString() { - return "PrivEvalResponse [allowed=" + allowed + ", missingPrivileges=" + missingPrivileges + ", evaluatedDlsFlsConfig=" - + evaluatedDlsFlsConfig + "]"; + return "PrivEvalResponse [allowed=" + + allowed + + ", missingPrivileges=" + + missingPrivileges + + ", evaluatedDlsFlsConfig=" + + evaluatedDlsFlsConfig + + "]"; } public static enum PrivilegesEvaluatorResponseState { diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java index dd569b05fb..f177596573 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesInterceptor.java @@ -56,6 +56,7 @@ private ReplaceResult(boolean continueEvaluation, boolean accessDenied, CreateIn public static final ReplaceResult CONTINUE_EVALUATION_REPLACE_RESULT = new ReplaceResult(true, false, null); public static final ReplaceResult ACCESS_DENIED_REPLACE_RESULT = new ReplaceResult(false, true, null); public static final ReplaceResult ACCESS_GRANTED_REPLACE_RESULT = new ReplaceResult(false, false, null); + protected static ReplaceResult newAccessGrantedReplaceResult(CreateIndexRequestBuilder createIndexRequestBuilder) { return new ReplaceResult(false, false, createIndexRequestBuilder); } @@ -65,16 +66,26 @@ protected static ReplaceResult newAccessGrantedReplaceResult(CreateIndexRequestB protected final Client client; protected final ThreadPool threadPool; - public PrivilegesInterceptor(final IndexNameExpressionResolver resolver, final ClusterService clusterService, - final Client client, ThreadPool threadPool) { + public PrivilegesInterceptor( + final IndexNameExpressionResolver resolver, + final ClusterService clusterService, + final Client client, + ThreadPool threadPool + ) { this.resolver = resolver; this.clusterService = clusterService; this.client = client; this.threadPool = threadPool; } - public ReplaceResult replaceDashboardsIndex(final ActionRequest request, final String action, final User user, final DynamicConfigModel config, - final Resolved requestedResolved, final Map tenants) { + public ReplaceResult replaceDashboardsIndex( + final ActionRequest request, + final String action, + final User user, + final DynamicConfigModel config, + final Resolved requestedResolved, + final Map tenants + ) { throw new RuntimeException("not implemented"); } diff --git a/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java index f00f8be73d..e4fd404daa 100644 --- a/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/ProtectedIndexAccessEvaluator.java @@ -38,11 +38,20 @@ public class ProtectedIndexAccessEvaluator { private final Boolean protectedIndexEnabled; private final WildcardMatcher deniedActionMatcher; - public ProtectedIndexAccessEvaluator(final Settings settings, AuditLog auditLog) { - this.indexMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT)); - this.allowedRolesMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT)); - this.protectedIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT); + this.indexMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_PROTECTED_INDICES_KEY, ConfigConstants.SECURITY_PROTECTED_INDICES_DEFAULT) + ); + this.allowedRolesMatcher = WildcardMatcher.from( + settings.getAsList( + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ROLES_DEFAULT + ) + ); + this.protectedIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_PROTECTED_INDICES_ENABLED_DEFAULT + ); this.auditLog = auditLog; final List indexDeniedActionPatterns = new ArrayList(); @@ -58,15 +67,21 @@ public ProtectedIndexAccessEvaluator(final Settings settings, AuditLog auditLog) this.deniedActionMatcher = WildcardMatcher.from(indexDeniedActionPatterns); } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final IndexResolverReplacer.Resolved requestedResolved, - final PrivilegesEvaluatorResponse presponse, final SecurityRoles securityRoles) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final IndexResolverReplacer.Resolved requestedResolved, + final PrivilegesEvaluatorResponse presponse, + final SecurityRoles securityRoles + ) { if (!protectedIndexEnabled) { return presponse; } if (!requestedResolved.isLocalAll() - && indexMatcher.matchAny(requestedResolved.getAllIndices()) - && deniedActionMatcher.test(action) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + && indexMatcher.matchAny(requestedResolved.getAllIndices()) + && deniedActionMatcher.test(action) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { auditLog.logMissingPrivileges(action, request, task); log.warn("{} for '{}' index/indices is not allowed for a regular user", action, indexMatcher); presponse.allowed = false; @@ -74,26 +89,25 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T } if (requestedResolved.isLocalAll() - && deniedActionMatcher.test(action) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + && deniedActionMatcher.test(action) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { auditLog.logMissingPrivileges(action, request, task); log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; return presponse.markComplete(); } - if((requestedResolved.isLocalAll() - || indexMatcher.matchAny(requestedResolved.getAllIndices())) - && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { + if ((requestedResolved.isLocalAll() || indexMatcher.matchAny(requestedResolved.getAllIndices())) + && !allowedRolesMatcher.matchAny(securityRoles.getRoleNames())) { final boolean isDebugEnabled = log.isDebugEnabled(); - if(request instanceof SearchRequest) { - ((SearchRequest)request).requestCache(Boolean.FALSE); + if (request instanceof SearchRequest) { + ((SearchRequest) request).requestCache(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable search request cache for this request"); } } - if(request instanceof RealtimeRequest) { + if (request instanceof RealtimeRequest) { ((RealtimeRequest) request).realtime(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable realtime for this request"); diff --git a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java index a74ea17ccd..94b0478759 100644 --- a/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SecurityIndexAccessEvaluator.java @@ -61,14 +61,25 @@ public class SecurityIndexAccessEvaluator { private final boolean systemIndexEnabled; public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, IndexResolverReplacer irr) { - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.auditLog = auditLog; this.irr = irr; this.filterSecurityIndex = settings.getAsBoolean(ConfigConstants.SECURITY_FILTER_SECURITYINDEX_FROM_ALL_REQUESTS, false); - this.systemIndexMatcher = WildcardMatcher.from(settings.getAsList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT)); - this.systemIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT); - - final boolean restoreSecurityIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false); + this.systemIndexMatcher = WildcardMatcher.from( + settings.getAsList(ConfigConstants.SECURITY_SYSTEM_INDICES_KEY, ConfigConstants.SECURITY_SYSTEM_INDICES_DEFAULT) + ); + this.systemIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY, + ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_DEFAULT + ); + + final boolean restoreSecurityIndexEnabled = settings.getAsBoolean( + ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, + false + ); final List securityIndexDeniedActionPatternsList = new ArrayList(); securityIndexDeniedActionPatternsList.add("indices:data/write*"); @@ -84,31 +95,44 @@ public SecurityIndexAccessEvaluator(final Settings settings, AuditLog auditLog, securityIndexDeniedActionPatternsListNoSnapshot.add("indices:admin/close*"); securityIndexDeniedActionPatternsListNoSnapshot.add("cluster:admin/snapshot/restore*"); - securityDeniedActionMatcher = WildcardMatcher.from(restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot); + securityDeniedActionMatcher = WildcardMatcher.from( + restoreSecurityIndexEnabled ? securityIndexDeniedActionPatternsList : securityIndexDeniedActionPatternsListNoSnapshot + ); } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final Resolved requestedResolved, - final PrivilegesEvaluatorResponse presponse) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final Resolved requestedResolved, + final PrivilegesEvaluatorResponse presponse + ) { final boolean isDebugEnabled = log.isDebugEnabled(); if (securityDeniedActionMatcher.test(action)) { - if(requestedResolved.isLocalAll()) { - if(filterSecurityIndex) { - irr.replace(request, false, "*","-"+ securityIndex); + if (requestedResolved.isLocalAll()) { + if (filterSecurityIndex) { + irr.replace(request, false, "*", "-" + securityIndex); if (isDebugEnabled) { - log.debug("Filtered '{}'from {}, resulting list with *,-{} is {}", securityIndex, requestedResolved, securityIndex, irr.resolveRequest(request)); + log.debug( + "Filtered '{}'from {}, resulting list with *,-{} is {}", + securityIndex, + requestedResolved, + securityIndex, + irr.resolveRequest(request) + ); } return presponse; } else { auditLog.logSecurityIndexAttempt(request, action, task); - log.warn( "{} for '_all' indices is not allowed for a regular user", action); + log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; return presponse.markComplete(); } } else if (matchAnySystemIndices(requestedResolved)) { - if(filterSecurityIndex) { + if (filterSecurityIndex) { Set allWithoutSecurity = new HashSet<>(requestedResolved.getAllIndices()); allWithoutSecurity.remove(securityIndex); - if(allWithoutSecurity.isEmpty()) { + if (allWithoutSecurity.isEmpty()) { if (isDebugEnabled) { log.debug("Filtered '{}' but resulting list is empty", securityIndex); } @@ -130,17 +154,18 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T } } - if(requestedResolved.isLocalAll() || requestedResolved.getAllIndices().contains(securityIndex) - || matchAnySystemIndices(requestedResolved)) { + if (requestedResolved.isLocalAll() + || requestedResolved.getAllIndices().contains(securityIndex) + || matchAnySystemIndices(requestedResolved)) { - if(request instanceof SearchRequest) { - ((SearchRequest)request).requestCache(Boolean.FALSE); + if (request instanceof SearchRequest) { + ((SearchRequest) request).requestCache(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable search request cache for this request"); } } - if(request instanceof RealtimeRequest) { + if (request instanceof RealtimeRequest) { ((RealtimeRequest) request).realtime(Boolean.FALSE); if (isDebugEnabled) { log.debug("Disable realtime for this request"); @@ -150,12 +175,15 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T return presponse; } - private boolean matchAnySystemIndices(final Resolved requestedResolved){ + private boolean matchAnySystemIndices(final Resolved requestedResolved) { return !getProtectedIndexes(requestedResolved).isEmpty(); } private List getProtectedIndexes(final Resolved requestedResolved) { - final List protectedIndexes = requestedResolved.getAllIndices().stream().filter(securityIndex::equals).collect(Collectors.toList()); + final List protectedIndexes = requestedResolved.getAllIndices() + .stream() + .filter(securityIndex::equals) + .collect(Collectors.toList()); if (systemIndexEnabled) { protectedIndexes.addAll(systemIndexMatcher.getMatchAny(requestedResolved.getAllIndices(), Collectors.toList())); } diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index c536ae2d2e..23612e1a52 100644 --- a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java @@ -49,16 +49,26 @@ public class SnapshotRestoreEvaluator { private final boolean restoreSecurityIndexEnabled; public SnapshotRestoreEvaluator(final Settings settings, AuditLog auditLog) { - this.enableSnapshotRestorePrivilege = settings.getAsBoolean(ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, - ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE); + this.enableSnapshotRestorePrivilege = settings.getAsBoolean( + ConfigConstants.SECURITY_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE, + ConfigConstants.SECURITY_DEFAULT_ENABLE_SNAPSHOT_RESTORE_PRIVILEGE + ); this.restoreSecurityIndexEnabled = settings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_RESTORE_SECURITYINDEX_ENABLED, false); - this.securityIndex = settings.get(ConfigConstants.SECURITY_CONFIG_INDEX_NAME, ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX); + this.securityIndex = settings.get( + ConfigConstants.SECURITY_CONFIG_INDEX_NAME, + ConfigConstants.OPENDISTRO_SECURITY_DEFAULT_CONFIG_INDEX + ); this.auditLog = auditLog; } - public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final Task task, final String action, final ClusterInfoHolder clusterInfoHolder, - final PrivilegesEvaluatorResponse presponse) { + public PrivilegesEvaluatorResponse evaluate( + final ActionRequest request, + final Task task, + final String action, + final ClusterInfoHolder clusterInfoHolder, + final PrivilegesEvaluatorResponse presponse + ) { if (!(request instanceof RestoreSnapshotRequest)) { return presponse; @@ -78,7 +88,6 @@ public PrivilegesEvaluatorResponse evaluate(final ActionRequest request, final T return presponse; } - if (clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { presponse.allowed = true; return presponse.markComplete(); diff --git a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java index 53709458fd..d06a45726a 100644 --- a/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/TermsAggregationEvaluator.java @@ -48,55 +48,65 @@ public class TermsAggregationEvaluator { protected final Logger log = LogManager.getLogger(this.getClass()); - private static final String[] READ_ACTIONS = new String[]{ - "indices:data/read/msearch", - "indices:data/read/mget", - "indices:data/read/get", - "indices:data/read/search", - "indices:data/read/field_caps*" - //"indices:admin/mappings/fields/get*" - }; + private static final String[] READ_ACTIONS = new String[] { + "indices:data/read/msearch", + "indices:data/read/mget", + "indices:data/read/get", + "indices:data/read/search", + "indices:data/read/field_caps*" + // "indices:admin/mappings/fields/get*" + }; private static final QueryBuilder NONE_QUERY = new MatchNoneQueryBuilder(); - public TermsAggregationEvaluator() { - } - - public PrivilegesEvaluatorResponse evaluate(final Resolved resolved, final ActionRequest request, ClusterService clusterService, User user, SecurityRoles securityRoles, IndexNameExpressionResolver resolver, PrivilegesEvaluatorResponse presponse) { + public TermsAggregationEvaluator() {} + + public PrivilegesEvaluatorResponse evaluate( + final Resolved resolved, + final ActionRequest request, + ClusterService clusterService, + User user, + SecurityRoles securityRoles, + IndexNameExpressionResolver resolver, + PrivilegesEvaluatorResponse presponse + ) { try { - if(request instanceof SearchRequest) { + if (request instanceof SearchRequest) { SearchRequest sr = (SearchRequest) request; - if( sr.source() != null - && sr.source().query() == null - && sr.source().aggregations() != null - && sr.source().aggregations().getAggregatorFactories() != null - && sr.source().aggregations().getAggregatorFactories().size() == 1 - && sr.source().size() == 0) { - AggregationBuilder ab = sr.source().aggregations().getAggregatorFactories().iterator().next(); - if( ab instanceof TermsAggregationBuilder - && "terms".equals(ab.getType()) - && "indices".equals(ab.getName())) { - if("_index".equals(((TermsAggregationBuilder) ab).field()) - && ab.getPipelineAggregations().isEmpty() - && ab.getSubAggregations().isEmpty()) { - - - final Set allPermittedIndices = securityRoles.getAllPermittedIndicesForDashboards(resolved, user, READ_ACTIONS, resolver, clusterService); - if(allPermittedIndices == null || allPermittedIndices.isEmpty()) { - sr.source().query(NONE_QUERY); - } else { - sr.source().query(new TermsQueryBuilder("_index", allPermittedIndices)); - } - - presponse.allowed = true; - return presponse.markComplete(); - } - } + if (sr.source() != null + && sr.source().query() == null + && sr.source().aggregations() != null + && sr.source().aggregations().getAggregatorFactories() != null + && sr.source().aggregations().getAggregatorFactories().size() == 1 + && sr.source().size() == 0) { + AggregationBuilder ab = sr.source().aggregations().getAggregatorFactories().iterator().next(); + if (ab instanceof TermsAggregationBuilder && "terms".equals(ab.getType()) && "indices".equals(ab.getName())) { + if ("_index".equals(((TermsAggregationBuilder) ab).field()) + && ab.getPipelineAggregations().isEmpty() + && ab.getSubAggregations().isEmpty()) { + + final Set allPermittedIndices = securityRoles.getAllPermittedIndicesForDashboards( + resolved, + user, + READ_ACTIONS, + resolver, + clusterService + ); + if (allPermittedIndices == null || allPermittedIndices.isEmpty()) { + sr.source().query(NONE_QUERY); + } else { + sr.source().query(new TermsQueryBuilder("_index", allPermittedIndices)); + } + + presponse.allowed = true; + return presponse.markComplete(); + } + } } } } catch (Exception e) { - log.warn("Unable to evaluate terms aggregation",e); + log.warn("Unable to evaluate terms aggregation", e); return presponse; } diff --git a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java index 7bde8523a5..61cc969fdb 100644 --- a/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java +++ b/src/main/java/org/opensearch/security/queries/QueryBuilderTraverser.java @@ -65,7 +65,11 @@ public static Set findAll(QueryBuilder queryBuilder, Predicate patterns = requestedPatterns==null?null:Arrays.asList(requestedPatterns); + final List patterns = requestedPatterns == null ? null : Arrays.asList(requestedPatterns); - if(IndexNameExpressionResolver.isAllIndices(patterns)) { + if (IndexNameExpressionResolver.isAllIndices(patterns)) { return true; } - if(patterns.size() == 1 && patterns.contains("*")) { + if (patterns.size() == 1 && patterns.contains("*")) { return true; } - if(new HashSet(patterns).equals(NULL_SET)) { + if (new HashSet(patterns).equals(NULL_SET)) { return true; } @@ -136,15 +136,15 @@ private static final boolean isLocalAll(String... requestedPatterns) { } private static final boolean isLocalAll(Collection patterns) { - if(IndexNameExpressionResolver.isAllIndices(patterns)) { + if (IndexNameExpressionResolver.isAllIndices(patterns)) { return true; } - if(patterns.contains("_all")) { + if (patterns.contains("_all")) { return true; } - if(new HashSet(patterns).equals(NULL_SET)) { + if (new HashSet(patterns).equals(NULL_SET)) { return true; } @@ -170,10 +170,15 @@ private class ResolvedIndicesProvider implements IndicesProvider { name = request.getClass().getSimpleName(); } - private void resolveIndexPatterns(final String name, final IndicesOptions indicesOptions, final boolean enableCrossClusterResolution, final String[] original) { + private void resolveIndexPatterns( + final String name, + final IndicesOptions indicesOptions, + final boolean enableCrossClusterResolution, + final String[] original + ) { final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("resolve requestedPatterns: "+ Arrays.toString(original)); + log.trace("resolve requestedPatterns: " + Arrays.toString(original)); } if (isAllWithNoRemote(original)) { @@ -189,14 +194,16 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice final RemoteClusterService remoteClusterService = OpenSearchSecurityPlugin.GuiceHolder.getRemoteClusterService(); - if(remoteClusterService.isCrossClusterSearchEnabled() && enableCrossClusterResolution) { + if (remoteClusterService.isCrossClusterSearchEnabled() && enableCrossClusterResolution) { remoteIndices = new HashSet<>(); final Map remoteClusterIndices = OpenSearchSecurityPlugin.GuiceHolder.getRemoteClusterService() - .groupIndices(indicesOptions, original, idx -> resolver.hasIndexAbstraction(idx, clusterService.state())); - final Set remoteClusters = remoteClusterIndices.keySet().stream() - .filter(k->!RemoteClusterService.LOCAL_CLUSTER_GROUP_KEY.equals(k)).collect(Collectors.toSet()); - for(String remoteCluster : remoteClusters) { - for(String remoteIndex : remoteClusterIndices.get(remoteCluster).indices()) { + .groupIndices(indicesOptions, original, idx -> resolver.hasIndexAbstraction(idx, clusterService.state())); + final Set remoteClusters = remoteClusterIndices.keySet() + .stream() + .filter(k -> !RemoteClusterService.LOCAL_CLUSTER_GROUP_KEY.equals(k)) + .collect(Collectors.toSet()); + for (String remoteCluster : remoteClusters) { + for (String remoteIndex : remoteClusterIndices.get(remoteCluster).indices()) { remoteIndices.add(RemoteClusterService.buildRemoteIndexName(remoteCluster, remoteIndex)); } } @@ -211,7 +218,12 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice } if (isTraceEnabled) { - log.trace("CCS is enabled, we found this local patterns " + localRequestedPatterns + " and this remote patterns: " + remoteIndices); + log.trace( + "CCS is enabled, we found this local patterns " + + localRequestedPatterns + + " and this remote patterns: " + + remoteIndices + ); } } else { @@ -239,28 +251,33 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice else { final ClusterState state = clusterService.state(); - final Set dateResolvedLocalRequestedPatterns = localRequestedPatterns - .stream() - .map(resolver::resolveDateMathExpression) - .collect(Collectors.toSet()); + final Set dateResolvedLocalRequestedPatterns = localRequestedPatterns.stream() + .map(resolver::resolveDateMathExpression) + .collect(Collectors.toSet()); final WildcardMatcher dateResolvedMatcher = WildcardMatcher.from(dateResolvedLocalRequestedPatterns); - //fill matchingAliases + // fill matchingAliases final Map lookup = state.metadata().getIndicesLookup(); matchingAliases = lookup.entrySet() - .stream() - .filter(e -> e.getValue().getType() == ALIAS) - .map(Map.Entry::getKey) - .filter(dateResolvedMatcher) - .collect(Collectors.toSet()); + .stream() + .filter(e -> e.getValue().getType() == ALIAS) + .map(Map.Entry::getKey) + .filter(dateResolvedMatcher) + .collect(Collectors.toSet()); final boolean isDebugEnabled = log.isDebugEnabled(); try { - matchingAllIndices = Arrays.asList(resolver.concreteIndexNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0]))); + matchingAllIndices = Arrays.asList( + resolver.concreteIndexNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0])) + ); matchingDataStreams = resolver.dataStreamNames(state, indicesOptions, localRequestedPatterns.toArray(new String[0])); if (isDebugEnabled) { - log.debug("Resolved pattern {} to indices: {} and data-streams: {}", - localRequestedPatterns, matchingAllIndices, matchingDataStreams); + log.debug( + "Resolved pattern {} to indices: {} and data-streams: {}", + localRequestedPatterns, + matchingAllIndices, + matchingDataStreams + ); } } catch (IndexNotFoundException e1) { if (isDebugEnabled) { @@ -276,8 +293,17 @@ private void resolveIndexPatterns(final String name, final IndicesOptions indice } if (isTraceEnabled) { - log.trace("Resolved patterns {} for {} ({}) to [aliases {}, allIndices {}, dataStreams {}, originalRequested{}, remote indices {}]", - original, name, this.name, matchingAliases, matchingAllIndices, matchingDataStreams, Arrays.toString(original), remoteIndices); + log.trace( + "Resolved patterns {} for {} ({}) to [aliases {}, allIndices {}, dataStreams {}, originalRequested{}, remote indices {}]", + original, + name, + this.name, + matchingAliases, + matchingAllIndices, + matchingDataStreams, + Arrays.toString(original), + remoteIndices + ); } resolveTo(matchingAliases, matchingAllIndices, matchingDataStreams, original, remoteIndices); @@ -289,8 +315,13 @@ private void resolveToLocalAll() { originalRequested.add(Resolved.ANY); } - private void resolveTo(Iterable matchingAliases, Iterable matchingAllIndices, - Iterable matchingDataStreams, String[] original, Iterable remoteIndices) { + private void resolveTo( + Iterable matchingAliases, + Iterable matchingAllIndices, + Iterable matchingDataStreams, + String[] original, + Iterable remoteIndices + ) { aliases.addAll(matchingAliases); allIndices.addAll(matchingAllIndices); allIndices.addAll(matchingDataStreams); @@ -302,21 +333,23 @@ private void resolveTo(Iterable matchingAliases, Iterable matchi public String[] provide(String[] original, Object localRequest, boolean supportsReplace) { final IndicesOptions indicesOptions = indicesOptionsFrom(localRequest); final boolean enableCrossClusterResolution = localRequest instanceof FieldCapabilitiesRequest - || localRequest instanceof SearchRequest - || localRequest instanceof ResolveIndexAction.Request; + || localRequest instanceof SearchRequest + || localRequest instanceof ResolveIndexAction.Request; // skip the whole thing if we have seen this exact resolveIndexPatterns request - if (alreadyResolved.add(new MultiKey(indicesOptions, enableCrossClusterResolution, - (original != null) ? new MultiKey(original, false) : null))) { + if (alreadyResolved.add( + new MultiKey(indicesOptions, enableCrossClusterResolution, (original != null) ? new MultiKey(original, false) : null) + )) { resolveIndexPatterns(localRequest.getClass().getSimpleName(), indicesOptions, enableCrossClusterResolution, original); } return IndicesProvider.NOOP; } Resolved resolved(IndicesOptions indicesOptions) { - final Resolved resolved = alreadyResolved.isEmpty() ? Resolved._LOCAL_ALL : - new Resolved(aliases.build(), allIndices.build(), originalRequested.build(), remoteIndices.build(), indicesOptions); + final Resolved resolved = alreadyResolved.isEmpty() + ? Resolved._LOCAL_ALL + : new Resolved(aliases.build(), allIndices.build(), originalRequested.build(), remoteIndices.build(), indicesOptions); - if(log.isTraceEnabled()) { + if (log.isTraceEnabled()) { log.trace("Finally resolved for {}: {}", name, resolved); } @@ -324,16 +357,17 @@ Resolved resolved(IndicesOptions indicesOptions) { } } - //dnfof + // dnfof public boolean replace(final TransportRequest request, boolean retainMode, String... replacements) { return getOrReplaceAllIndices(request, new IndicesProvider() { @Override public String[] provide(String[] original, Object request, boolean supportsReplace) { - if(supportsReplace) { - if(retainMode && !isAllWithNoRemote(original)) { + if (supportsReplace) { + if (retainMode && !isAllWithNoRemote(original)) { final Resolved resolved = resolveRequest(request); - final List retained = WildcardMatcher.from(resolved.getAllIndices()).getMatchAny(replacements, Collectors.toList()); + final List retained = WildcardMatcher.from(resolved.getAllIndices()) + .getMatchAny(replacements, Collectors.toList()); retained.addAll(resolved.getRemoteIndices()); return retained.toArray(new String[0]); } @@ -361,7 +395,13 @@ public final static class Resolved { private static final String ANY = "*"; private static final ImmutableSet All_SET = ImmutableSet.of(ANY); private static final Set types = All_SET; - public static final Resolved _LOCAL_ALL = new Resolved(All_SET, All_SET, All_SET, ImmutableSet.of(), SearchRequest.DEFAULT_INDICES_OPTIONS); + public static final Resolved _LOCAL_ALL = new Resolved( + All_SET, + All_SET, + All_SET, + ImmutableSet.of(), + SearchRequest.DEFAULT_INDICES_OPTIONS + ); private final Set aliases; private final Set allIndices; @@ -370,16 +410,19 @@ public final static class Resolved { private final boolean isLocalAll; private final IndicesOptions indicesOptions; - public Resolved(final ImmutableSet aliases, - final ImmutableSet allIndices, - final ImmutableSet originalRequested, - final ImmutableSet remoteIndices, - IndicesOptions indicesOptions) { + public Resolved( + final ImmutableSet aliases, + final ImmutableSet allIndices, + final ImmutableSet originalRequested, + final ImmutableSet remoteIndices, + IndicesOptions indicesOptions + ) { this.aliases = aliases; this.allIndices = allIndices; this.originalRequested = originalRequested; this.remoteIndices = remoteIndices; - this.isLocalAll = IndexResolverReplacer.isLocalAll(originalRequested.toArray(new String[0])) || (aliases.contains("*") && allIndices.contains("*")); + this.isLocalAll = IndexResolverReplacer.isLocalAll(originalRequested.toArray(new String[0])) + || (aliases.contains("*") && allIndices.contains("*")); this.indicesOptions = indicesOptions; } @@ -417,8 +460,17 @@ public Set getRemoteIndices() { @Override public String toString() { - return "Resolved [aliases=" + aliases + ", allIndices=" + allIndices + ", types=" + types - + ", originalRequested=" + originalRequested + ", remoteIndices=" + remoteIndices + "]"; + return "Resolved [aliases=" + + aliases + + ", allIndices=" + + allIndices + + ", types=" + + types + + ", originalRequested=" + + originalRequested + + ", remoteIndices=" + + remoteIndices + + "]"; } @Override @@ -434,33 +486,22 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Resolved other = (Resolved) obj; if (aliases == null) { - if (other.aliases != null) - return false; - } else if (!aliases.equals(other.aliases)) - return false; + if (other.aliases != null) return false; + } else if (!aliases.equals(other.aliases)) return false; if (allIndices == null) { - if (other.allIndices != null) - return false; - } else if (!allIndices.equals(other.allIndices)) - return false; + if (other.allIndices != null) return false; + } else if (!allIndices.equals(other.allIndices)) return false; if (originalRequested == null) { - if (other.originalRequested != null) - return false; - } else if (!originalRequested.equals(other.originalRequested)) - return false; + if (other.originalRequested != null) return false; + } else if (!originalRequested.equals(other.originalRequested)) return false; if (remoteIndices == null) { - if (other.remoteIndices != null) - return false; - } else if (!remoteIndices.equals(other.remoteIndices)) - return false; + if (other.remoteIndices != null) return false; + } else if (!remoteIndices.equals(other.remoteIndices)) return false; return true; } } @@ -482,42 +523,42 @@ private List renamedIndices(final RestoreSnapshotRequest request, final } } - - //-- + // -- @FunctionalInterface public interface IndicesProvider { public static final String[] NOOP = new String[0]; + String[] provide(String[] original, Object request, boolean supportsReplace); } private boolean checkIndices(Object request, String[] indices, boolean needsToBeSizeOne, boolean allowEmpty) { - if(indices == IndicesProvider.NOOP) { + if (indices == IndicesProvider.NOOP) { return false; } final boolean isTraceEnabled = log.isTraceEnabled(); - if(!allowEmpty && (indices == null || indices.length == 0)) { - if(isTraceEnabled && request != null) { - log.trace("Null or empty indices for "+request.getClass().getName()); + if (!allowEmpty && (indices == null || indices.length == 0)) { + if (isTraceEnabled && request != null) { + log.trace("Null or empty indices for " + request.getClass().getName()); } return false; } - if(!allowEmpty && needsToBeSizeOne && indices.length != 1) { - if(isTraceEnabled && request != null) { - log.trace("To much indices for "+request.getClass().getName()); + if (!allowEmpty && needsToBeSizeOne && indices.length != 1) { + if (isTraceEnabled && request != null) { + log.trace("To much indices for " + request.getClass().getName()); } return false; } for (int i = 0; i < indices.length; i++) { final String index = indices[i]; - if(index == null || index.isEmpty()) { - //not allowed - if(isTraceEnabled && request != null) { - log.trace("At least one null or empty index for "+request.getClass().getName()); + if (index == null || index.isEmpty()) { + // not allowed + if (isTraceEnabled && request != null) { + log.trace("At least one null or empty index for " + request.getClass().getName()); } return false; } @@ -537,7 +578,7 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid final boolean isDebugEnabled = log.isDebugEnabled(); final boolean isTraceEnabled = log.isTraceEnabled(); if (isTraceEnabled) { - log.trace("getOrReplaceAllIndices() for "+request.getClass()); + log.trace("getOrReplaceAllIndices() for " + request.getClass()); } boolean result = true; @@ -550,7 +591,7 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid } else if (request instanceof MultiGetRequest) { - for (ListIterator it = ((MultiGetRequest) request).getItems().listIterator(); it.hasNext();){ + for (ListIterator it = ((MultiGetRequest) request).getItems().listIterator(); it.hasNext();) { Item item = it.next(); result = getOrReplaceAllIndices(item, provider, false) && result; /*if(item.index() == null || item.indices() == null || item.indices().length == 0) { @@ -574,12 +615,12 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid result = getOrReplaceAllIndices(ar, provider, false) && result; } - } else if(request instanceof PutMappingRequest) { + } else if (request instanceof PutMappingRequest) { PutMappingRequest pmr = (PutMappingRequest) request; Index concreteIndex = pmr.getConcreteIndex(); - if(concreteIndex != null && (pmr.indices() == null || pmr.indices().length == 0)) { - String[] newIndices = provider.provide(new String[]{concreteIndex.getName()}, request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (concreteIndex != null && (pmr.indices() == null || pmr.indices().length == 0)) { + String[] newIndices = provider.provide(new String[] { concreteIndex.getName() }, request, true); + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } @@ -587,58 +628,64 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid ((PutMappingRequest) request).setConcreteIndex(null); } else { String[] newIndices = provider.provide(((PutMappingRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, false, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, false, allowEmptyIndices) == false) { return false; } ((PutMappingRequest) request).indices(newIndices); } - } else if(request instanceof RestoreSnapshotRequest) { + } else if (request instanceof RestoreSnapshotRequest) { - if(clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { - return true; - } + if (clusterInfoHolder.isLocalNodeElectedClusterManager() == Boolean.FALSE) { + return true; + } - final RestoreSnapshotRequest restoreRequest = (RestoreSnapshotRequest) request; - final SnapshotInfo snapshotInfo = SnapshotRestoreHelper.getSnapshotInfo(restoreRequest); + final RestoreSnapshotRequest restoreRequest = (RestoreSnapshotRequest) request; + final SnapshotInfo snapshotInfo = SnapshotRestoreHelper.getSnapshotInfo(restoreRequest); - if (snapshotInfo == null) { - log.warn("snapshot repository '" + restoreRequest.repository() + "', snapshot '" + restoreRequest.snapshot() + "' not found"); - provider.provide(new String[]{"*"}, request, false); - } else { - final List requestedResolvedIndices = SnapshotUtils.filterIndices(snapshotInfo.indices(), restoreRequest.indices(), restoreRequest.indicesOptions()); - final List renamedTargetIndices = renamedIndices(restoreRequest, requestedResolvedIndices); - //final Set indices = new HashSet<>(requestedResolvedIndices); - //indices.addAll(renamedTargetIndices); - if (isDebugEnabled) { - log.debug("snapshot: {} contains this indices: {}", snapshotInfo.snapshotId().getName(), renamedTargetIndices); - } - provider.provide(renamedTargetIndices.toArray(new String[0]), request, false); + if (snapshotInfo == null) { + log.warn( + "snapshot repository '" + restoreRequest.repository() + "', snapshot '" + restoreRequest.snapshot() + "' not found" + ); + provider.provide(new String[] { "*" }, request, false); + } else { + final List requestedResolvedIndices = SnapshotUtils.filterIndices( + snapshotInfo.indices(), + restoreRequest.indices(), + restoreRequest.indicesOptions() + ); + final List renamedTargetIndices = renamedIndices(restoreRequest, requestedResolvedIndices); + // final Set indices = new HashSet<>(requestedResolvedIndices); + // indices.addAll(renamedTargetIndices); + if (isDebugEnabled) { + log.debug("snapshot: {} contains this indices: {}", snapshotInfo.snapshotId().getName(), renamedTargetIndices); + } + provider.provide(renamedTargetIndices.toArray(new String[0]), request, false); } } else if (request instanceof IndicesAliasesRequest) { - for(AliasActions ar: ((IndicesAliasesRequest) request).getAliasActions()) { + for (AliasActions ar : ((IndicesAliasesRequest) request).getAliasActions()) { result = getOrReplaceAllIndices(ar, provider, false) && result; } } else if (request instanceof DeleteRequest) { String[] newIndices = provider.provide(((DeleteRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((DeleteRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((DeleteRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof UpdateRequest) { String[] newIndices = provider.provide(((UpdateRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((UpdateRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((UpdateRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof SingleShardRequest) { final SingleShardRequest singleShardRequest = (SingleShardRequest) request; final String index = singleShardRequest.index(); - String[] indices = provider.provide(index == null ? null : new String[]{index}, request, true); + String[] indices = provider.provide(index == null ? null : new String[] { index }, request, true); if (!checkIndices(request, indices, true, allowEmptyIndices)) { return false; } - singleShardRequest.index(indices.length != 1? null : indices[0]); + singleShardRequest.index(indices.length != 1 ? null : indices[0]); } else if (request instanceof FieldCapabilitiesIndexRequest) { // FieldCapabilitiesIndexRequest does not support replacing the indexes. // However, the indexes are always determined by FieldCapabilitiesRequest which will be reduced below @@ -648,56 +695,56 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid String index = fieldCapabilitiesRequest.index(); - String[] newIndices = provider.provide(new String[]{index}, request, true); + String[] newIndices = provider.provide(new String[] { index }, request, true); if (!checkIndices(request, newIndices, true, allowEmptyIndices)) { return false; } } else if (request instanceof IndexRequest) { String[] newIndices = provider.provide(((IndexRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((IndexRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((IndexRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof Replaceable) { String[] newIndices = provider.provide(((Replaceable) request).indices(), request, true); - if(checkIndices(request, newIndices, false, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, false, allowEmptyIndices) == false) { return false; } ((Replaceable) request).indices(newIndices); } else if (request instanceof BulkShardRequest) { provider.provide(((ReplicationRequest) request).indices(), request, false); - //replace not supported? + // replace not supported? } else if (request instanceof ReplicationRequest) { String[] newIndices = provider.provide(((ReplicationRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((ReplicationRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((ReplicationRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof MultiGetRequest.Item) { String[] newIndices = provider.provide(((MultiGetRequest.Item) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((MultiGetRequest.Item) request).index(newIndices.length!=1?null:newIndices[0]); + ((MultiGetRequest.Item) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof CreateIndexRequest) { String[] newIndices = provider.provide(((CreateIndexRequest) request).indices(), request, true); - if(checkIndices(request, newIndices, true, allowEmptyIndices) == false) { + if (checkIndices(request, newIndices, true, allowEmptyIndices) == false) { return false; } - ((CreateIndexRequest) request).index(newIndices.length!=1?null:newIndices[0]); + ((CreateIndexRequest) request).index(newIndices.length != 1 ? null : newIndices[0]); } else if (request instanceof CreateDataStreamAction.Request) { provider.provide(((CreateDataStreamAction.Request) request).indices(), request, false); } else if (request instanceof ReindexRequest) { result = getOrReplaceAllIndices(((ReindexRequest) request).getDestination(), provider, false) && result; result = getOrReplaceAllIndices(((ReindexRequest) request).getSearchRequest(), provider, false) && result; } else if (request instanceof BaseNodesRequest) { - //do nothing + // do nothing } else if (request instanceof MainRequest) { - //do nothing + // do nothing } else if (request instanceof ClearScrollRequest) { - //do nothing + // do nothing } else if (request instanceof SearchScrollRequest) { - //do nothing + // do nothing } else if (request instanceof PutComponentTemplateAction.Request) { // do nothing } else { @@ -712,17 +759,15 @@ private boolean getOrReplaceAllIndices(final Object request, final IndicesProvid private IndicesOptions indicesOptionsFrom(Object localRequest) { - if(!respectRequestIndicesOptions) { + if (!respectRequestIndicesOptions) { return IndicesOptions.fromOptions(false, true, true, false, true); } if (IndicesRequest.class.isInstance(localRequest)) { return ((IndicesRequest) localRequest).indicesOptions(); - } - else if (RestoreSnapshotRequest.class.isInstance(localRequest)) { + } else if (RestoreSnapshotRequest.class.isInstance(localRequest)) { return ((RestoreSnapshotRequest) localRequest).indicesOptions(); - } - else { + } else { return IndicesOptions.fromOptions(false, true, true, false, true); } } diff --git a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java index a7620f6bdc..352d99b57e 100644 --- a/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/DashboardsInfoAction.java @@ -55,25 +55,22 @@ public class DashboardsInfoAction extends BaseRestHandler { private static final List routes = ImmutableList.builder() - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/dashboardsinfo"), - new Route(POST, "/dashboardsinfo") - ), - "/_plugins/_security")) - .addAll(addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/kibanainfo"), - new Route(POST, "/kibanainfo") - ), - "/_opendistro/_security")) + .addAll( + addRoutesPrefix(ImmutableList.of(new Route(GET, "/dashboardsinfo"), new Route(POST, "/dashboardsinfo")), "/_plugins/_security") + ) + .addAll(addRoutesPrefix(ImmutableList.of(new Route(GET, "/kibanainfo"), new Route(POST, "/kibanainfo")), "/_opendistro/_security")) .build(); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; private final ThreadContext threadContext; - public DashboardsInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool) { + public DashboardsInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -90,15 +87,15 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); builder.startObject(); - builder.field("user_name", user==null?null:user.getName()); + builder.field("user_name", user == null ? null : user.getName()); builder.field("not_fail_on_forbidden_enabled", evaluator.notFailOnForbiddenEnabled()); builder.field("opensearch_dashboards_mt_enabled", evaluator.multitenancyEnabled()); builder.field("opensearch_dashboards_index", evaluator.dashboardsIndex()); @@ -111,13 +108,13 @@ public void accept(RestChannel channel) throws Exception { response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { log.error(e1.toString()); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -132,5 +129,4 @@ public String getName() { return "Kibana Info Action"; } - } diff --git a/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java index b328a049c1..379a3b6b13 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityConfigUpdateAction.java @@ -40,9 +40,7 @@ public class SecurityConfigUpdateAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(PUT, "/configupdate")), - "/_plugins/_security"); + private static final List routes = addRoutesPrefix(ImmutableList.of(new Route(PUT, "/configupdate")), "/_plugins/_security"); private final ThreadContext threadContext; private final AdminDNs adminDns; @@ -50,8 +48,14 @@ public class SecurityConfigUpdateAction extends BaseRestHandler { 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) { + 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; @@ -60,11 +64,13 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController this.principalExtractor = principalExtractor; } - @Override public List routes() { + @Override + public List routes() { return routes; } - @Override protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + @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); @@ -75,7 +81,7 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - //only allowed for admins + // only allowed for admins if (user == null || !adminDns.isAdmin(user)) { return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, "")); } else { @@ -86,7 +92,8 @@ public SecurityConfigUpdateAction(final Settings settings, final RestController } } - @Override public String getName() { + @Override + public String getName() { return "Security config update"; } diff --git a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java index 17d5ee122f..0631e3044a 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityHealthAction.java @@ -47,10 +47,11 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class SecurityHealthAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(GET, "/health"), - new Route(POST, "/health") - ), "/_opendistro/_security", "/_plugins/_security"); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(GET, "/health"), new Route(POST, "/health")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final BackendRegistry registry; @@ -68,7 +69,7 @@ public List routes() { protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { return new RestChannelConsumer() { - final String mode = request.param("mode","strict"); + final String mode = request.param("mode", "strict"); @Override public void accept(RestChannel channel) throws Exception { @@ -77,7 +78,6 @@ public void accept(RestChannel channel) throws Exception { BytesRestResponse response = null; try { - String status = "UP"; String message = null; @@ -99,11 +99,9 @@ public void accept(RestChannel channel) throws Exception { builder.close(); } - channel.sendResponse(response); } - }; } diff --git a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java index 7867e8790d..6159f555c7 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityInfoAction.java @@ -60,16 +60,22 @@ import static org.opensearch.security.dlic.rest.support.Utils.addRoutesPrefix; public class SecurityInfoAction extends BaseRestHandler { - private static final List routes = addRoutesPrefix(ImmutableList.of( - new Route(GET, "/authinfo"), - new Route(POST, "/authinfo") - ),"/_opendistro/_security", "/_plugins/_security"); + private static final List routes = addRoutesPrefix( + ImmutableList.of(new Route(GET, "/authinfo"), new Route(POST, "/authinfo")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; private final ThreadContext threadContext; - public SecurityInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool) { + public SecurityInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -86,12 +92,11 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final boolean verbose = request.paramAsBoolean("verbose", false); final X509Certificate[] certs = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PEER_CERTIFICATES); @@ -101,41 +106,54 @@ public void accept(RestChannel channel) throws Exception { final Set securityRoles = evaluator.mapRoles(user, remoteAddress); builder.startObject(); - builder.field("user", user==null?null:user.toString()); - builder.field("user_name", user==null?null:user.getName()); - builder.field("user_requested_tenant", user==null?null:user.getRequestedTenant()); + builder.field("user", user == null ? null : user.toString()); + builder.field("user_name", user == null ? null : user.getName()); + builder.field("user_requested_tenant", user == null ? null : user.getRequestedTenant()); builder.field("remote_address", remoteAddress); - builder.field("backend_roles", user==null?null:user.getRoles()); - builder.field("custom_attribute_names", user==null?null:user.getCustomAttributesMap().keySet()); + builder.field("backend_roles", user == null ? null : user.getRoles()); + builder.field("custom_attribute_names", user == null ? null : user.getCustomAttributesMap().keySet()); builder.field("roles", securityRoles); builder.field("tenants", evaluator.mapTenants(user, securityRoles)); - builder.field("principal", (String)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); + builder.field("principal", (String) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_PRINCIPAL)); builder.field("peer_certificates", certs != null && certs.length > 0 ? certs.length + "" : "0"); - builder.field("sso_logout_url", (String)threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); + builder.field("sso_logout_url", (String) threadContext.getTransient(ConfigConstants.SSO_LOGOUT_URL)); - if(user != null && verbose) { + if (user != null && verbose) { try { - builder.field("size_of_user", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject(user).length())); - builder.field("size_of_custom_attributes", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject((Serializable) user.getCustomAttributesMap()).getBytes(StandardCharsets.UTF_8).length)); - builder.field("size_of_backendroles", RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject((Serializable)user.getRoles()).getBytes(StandardCharsets.UTF_8).length)); + builder.field( + "size_of_user", + RamUsageEstimator.humanReadableUnits(Base64Helper.serializeObject(user).length()) + ); + builder.field( + "size_of_custom_attributes", + RamUsageEstimator.humanReadableUnits( + Base64Helper.serializeObject((Serializable) user.getCustomAttributesMap()) + .getBytes(StandardCharsets.UTF_8).length + ) + ); + builder.field( + "size_of_backendroles", + RamUsageEstimator.humanReadableUnits( + Base64Helper.serializeObject((Serializable) user.getRoles()).getBytes(StandardCharsets.UTF_8).length + ) + ); } catch (Throwable e) { - //ignore + // ignore } } - builder.endObject(); response = new BytesRestResponse(RestStatus.OK, builder); } catch (final Exception e1) { - log.error(e1.toString(),e1); - builder = channel.newBuilder(); //NOSONAR + log.error(e1.toString(), e1); + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } diff --git a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java index 982448a53f..8f20a0b9a2 100644 --- a/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java +++ b/src/main/java/org/opensearch/security/rest/SecurityWhoAmIAction.java @@ -41,88 +41,93 @@ 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"; - } + 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/rest/TenantInfoAction.java b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java index f7b2a606c6..c6f09efd98 100644 --- a/src/main/java/org/opensearch/security/rest/TenantInfoAction.java +++ b/src/main/java/org/opensearch/security/rest/TenantInfoAction.java @@ -65,11 +65,10 @@ public class TenantInfoAction extends BaseRestHandler { private static final List routes = addRoutesPrefix( - ImmutableList.of( - new Route(GET, "/tenantinfo"), - new Route(POST, "/tenantinfo") - ), - "/_opendistro/_security", "/_plugins/_security"); + ImmutableList.of(new Route(GET, "/tenantinfo"), new Route(POST, "/tenantinfo")), + "/_opendistro/_security", + "/_plugins/_security" + ); private final Logger log = LogManager.getLogger(this.getClass()); private final PrivilegesEvaluator evaluator; @@ -78,9 +77,15 @@ public class TenantInfoAction extends BaseRestHandler { private final AdminDNs adminDns; private final ConfigurationRepository configurationRepository; - public TenantInfoAction(final Settings settings, final RestController controller, - final PrivilegesEvaluator evaluator, final ThreadPool threadPool, final ClusterService clusterService, final AdminDNs adminDns, - final ConfigurationRepository configurationRepository) { + public TenantInfoAction( + final Settings settings, + final RestController controller, + final PrivilegesEvaluator evaluator, + final ThreadPool threadPool, + final ClusterService clusterService, + final AdminDNs adminDns, + final ConfigurationRepository configurationRepository + ) { super(); this.threadContext = threadPool.getThreadContext(); this.evaluator = evaluator; @@ -100,41 +105,41 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli @Override public void accept(RestChannel channel) throws Exception { - XContentBuilder builder = channel.newBuilder(); //NOSONAR + XContentBuilder builder = channel.newBuilder(); // NOSONAR BytesRestResponse response = null; try { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - //only allowed for admins or the kibanaserveruser - if(!isAuthorized()) { - response = new BytesRestResponse(RestStatus.FORBIDDEN,""); + // only allowed for admins or the kibanaserveruser + if (!isAuthorized()) { + response = new BytesRestResponse(RestStatus.FORBIDDEN, ""); } else { - builder.startObject(); + builder.startObject(); - final SortedMap lookup = clusterService.state().metadata().getIndicesLookup(); - for(final String indexOrAlias: lookup.keySet()) { - final String tenant = tenantNameForIndex(indexOrAlias); - if(tenant != null) { - builder.field(indexOrAlias, tenant); - } - } + final SortedMap lookup = clusterService.state().metadata().getIndicesLookup(); + for (final String indexOrAlias : lookup.keySet()) { + final String tenant = tenantNameForIndex(indexOrAlias); + if (tenant != null) { + builder.field(indexOrAlias, tenant); + } + } - builder.endObject(); + builder.endObject(); - response = new BytesRestResponse(RestStatus.OK, builder); + response = new BytesRestResponse(RestStatus.OK, builder); } } catch (final Exception e1) { log.error(e1.toString()); - builder = channel.newBuilder(); //NOSONAR + builder = channel.newBuilder(); // NOSONAR builder.startObject(); builder.field("error", e1.toString()); builder.endObject(); response = new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder); } finally { - if(builder != null) { + if (builder != null) { builder.close(); } } @@ -145,7 +150,7 @@ public void accept(RestChannel channel) throws Exception { } private boolean isAuthorized() { - final User user = (User)threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user = (User) threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); if (user == null) { return false; @@ -173,38 +178,38 @@ private boolean isAuthorized() { } private final SecurityDynamicConfiguration load(final CType config, boolean logComplianceEvent) { - SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex(Collections.singleton(config), logComplianceEvent).get(config).deepClone(); + SecurityDynamicConfiguration loaded = configurationRepository.getConfigurationsFromIndex( + Collections.singleton(config), + logComplianceEvent + ).get(config).deepClone(); return DynamicConfigFactory.addStatics(loaded); } private String tenantNameForIndex(String index) { - String[] indexParts; - if(index == null - || (indexParts = index.split("_")).length != 3 - ) { - return null; - } - - - if(!indexParts[0].equals(evaluator.dashboardsIndex())) { - return null; - } - - try { - final int expectedHash = Integer.parseInt(indexParts[1]); - final String sanitizedName = indexParts[2]; - - for(String tenant: evaluator.getAllConfiguredTenantNames()) { - if(tenant.hashCode() == expectedHash && sanitizedName.equals(tenant.toLowerCase().replaceAll("[^a-z0-9]+",""))) { - return tenant; - } - } - - return "__private__"; - } catch (NumberFormatException e) { - log.warn("Index {} looks like a Security tenant index but we cannot parse the hashcode so we ignore it.", index); - return null; - } + String[] indexParts; + if (index == null || (indexParts = index.split("_")).length != 3) { + return null; + } + + if (!indexParts[0].equals(evaluator.dashboardsIndex())) { + return null; + } + + try { + final int expectedHash = Integer.parseInt(indexParts[1]); + final String sanitizedName = indexParts[2]; + + for (String tenant : evaluator.getAllConfiguredTenantNames()) { + if (tenant.hashCode() == expectedHash && sanitizedName.equals(tenant.toLowerCase().replaceAll("[^a-z0-9]+", ""))) { + return tenant; + } + } + + return "__private__"; + } catch (NumberFormatException e) { + log.warn("Index {} looks like a Security tenant index but we cannot parse the hashcode so we ignore it.", index); + return null; + } } @Override @@ -212,5 +217,4 @@ public String getName() { return "Tenant Info Action"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModel.java b/src/main/java/org/opensearch/security/securityconf/ConfigModel.java index b4b2a9dd20..653ff23896 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModel.java @@ -32,11 +32,12 @@ import org.opensearch.common.transport.TransportAddress; import org.opensearch.security.user.User; - public abstract class ConfigModel { public abstract Map mapTenants(User user, Set roles); + public abstract Set mapSecurityRoles(User user, TransportAddress caller); + public abstract SecurityRoles getSecurityRoles(); public abstract Set getAllConfiguredTenantNames(); diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index 7a978034f1..837dc0cff0 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -66,7 +66,6 @@ import static org.opensearch.cluster.metadata.IndexAbstraction.Type.ALIAS; - public class ConfigModelV6 extends ConfigModel { protected final Logger log = LogManager.getLogger(this.getClass()); @@ -78,18 +77,22 @@ public class ConfigModelV6 extends ConfigModel { private SecurityDynamicConfiguration roles; public ConfigModelV6( - SecurityDynamicConfiguration roles, - SecurityDynamicConfiguration actiongroups, - SecurityDynamicConfiguration rolesmapping, - DynamicConfigModel dcm, - Settings opensearchSettings) { + SecurityDynamicConfiguration roles, + SecurityDynamicConfiguration actiongroups, + SecurityDynamicConfiguration rolesmapping, + DynamicConfigModel dcm, + Settings opensearchSettings + ) { this.roles = roles; try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( - opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) - .toUpperCase()); + opensearchSettings.get( + ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, + ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString() + ).toUpperCase() + ); } catch (Exception e) { log.error("Cannot apply roles mapping resolution", e); rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY; @@ -137,15 +140,14 @@ private Set getGroupMembers(final String groupname) { private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - // SG5 format, plain array - //List en = actionGroups.getAsList(DotPath.of(entry)); - //if (en.isEmpty()) { - // try SG6 format including readonly and permissions key - // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); - //} + // List en = actionGroups.getAsList(DotPath.of(entry)); + // if (en.isEmpty()) { + // try SG6 format including readonly and permissions key + // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); + // } - if(!actionGroups.getCEntries().containsKey(entry)) { + if (!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } @@ -153,27 +155,26 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { + if (actionGroupAsObject != null && actionGroupAsObject instanceof List) { - for (final String perm: ((List) actionGroupAsObject)) { + for (final String perm : ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } - - } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV6) { - for (final String perm: ((ActionGroupsV6) actionGroupAsObject).getPermissions()) { + } else if (actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV6) { + for (final String perm : ((ActionGroupsV6) actionGroupAsObject).getPermissions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } } else { - throw new RuntimeException("Unable to handle "+actionGroupAsObject); + throw new RuntimeException("Unable to handle " + actionGroupAsObject); } return Collections.unmodifiableSet(ret); @@ -182,7 +183,7 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); - for (String string: actions) { + for (String string : actions) { final Set groups = getGroupMembers(string); if (groups.isEmpty()) { resolvedActions.add(string); @@ -201,7 +202,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { final Set> futures = new HashSet<>(5000); final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: settings.getCEntries().entrySet()) { + for (Entry securityRole : settings.getCEntries().entrySet()) { Future future = execs.submit(new Callable() { @@ -209,61 +210,60 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { public SecurityRole call() throws Exception { SecurityRole _securityRole = new SecurityRole(securityRole.getKey()); - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { return null; } final Set permittedClusterActions = agr.resolvedActions(securityRole.getValue().getCluster()); _securityRole.addClusterPerms(permittedClusterActions); - //if(tenants != null) { - for(Entry tenant: securityRole.getValue().getTenants().entrySet()) { + // if(tenants != null) { + for (Entry tenant : securityRole.getValue().getTenants().entrySet()) { - //if(tenant.equals(user.getName())) { - // continue; - //} + // if(tenant.equals(user.getName())) { + // continue; + // } - if("RW".equalsIgnoreCase(tenant.getValue())) { - _securityRole.addTenant(new Tenant(tenant.getKey(), true)); - } else { - _securityRole.addTenant(new Tenant(tenant.getKey(), false)); - //if(_securityRole.tenants.stream().filter(t->t.tenant.equals(tenant)).count() > 0) { //RW outperforms RO - // _securityRole.addTenant(new Tenant(tenant, false)); - //} - } + if ("RW".equalsIgnoreCase(tenant.getValue())) { + _securityRole.addTenant(new Tenant(tenant.getKey(), true)); + } else { + _securityRole.addTenant(new Tenant(tenant.getKey(), false)); + // if(_securityRole.tenants.stream().filter(t->t.tenant.equals(tenant)).count() > 0) { //RW outperforms RO + // _securityRole.addTenant(new Tenant(tenant, false)); + // } } - //} - - - //final Map permittedAliasesIndices = securityRoleSettings.getGroups(DotPath.of("indices")); - - for (final Entry permittedAliasesIndex : securityRole.getValue().getIndices().entrySet()) { + } + // } - //final String resolvedRole = securityRole; - //final String indexPattern = permittedAliasesIndex; + // final Map permittedAliasesIndices = + // securityRoleSettings.getGroups(DotPath.of("indices")); - final String dls = permittedAliasesIndex.getValue().get_dls_(); - final List fls = permittedAliasesIndex.getValue().get_fls_(); - final List maskedFields = permittedAliasesIndex.getValue().get_masked_fields_(); + for (final Entry permittedAliasesIndex : securityRole.getValue().getIndices().entrySet()) { - IndexPattern _indexPattern = new IndexPattern(permittedAliasesIndex.getKey()); - _indexPattern.setDlsQuery(dls); - _indexPattern.addFlsFields(fls); - _indexPattern.addMaskedFields(maskedFields); + // final String resolvedRole = securityRole; + // final String indexPattern = permittedAliasesIndex; - for(Entry> type: permittedAliasesIndex.getValue().getTypes().entrySet()) { - TypePerm typePerm = new TypePerm(type.getKey()); - final List perms = type.getValue(); - typePerm.addPerms(agr.resolvedActions(perms)); - _indexPattern.addTypePerms(typePerm); - } + final String dls = permittedAliasesIndex.getValue().get_dls_(); + final List fls = permittedAliasesIndex.getValue().get_fls_(); + final List maskedFields = permittedAliasesIndex.getValue().get_masked_fields_(); - _securityRole.addIndexPattern(_indexPattern); + IndexPattern _indexPattern = new IndexPattern(permittedAliasesIndex.getKey()); + _indexPattern.setDlsQuery(dls); + _indexPattern.addFlsFields(fls); + _indexPattern.addMaskedFields(maskedFields); + for (Entry> type : permittedAliasesIndex.getValue().getTypes().entrySet()) { + TypePerm typePerm = new TypePerm(type.getKey()); + final List perms = type.getValue(); + typePerm.addPerms(agr.resolvedActions(perms)); + _indexPattern.addTypePerms(typePerm); } + _securityRole.addIndexPattern(_indexPattern); - return _securityRole; + } + + return _securityRole; } }); @@ -296,8 +296,7 @@ public SecurityRole call() throws Exception { } } - - //beans + // beans public static class SecurityRoles implements org.opensearch.security.securityconf.SecurityRoles { @@ -326,18 +325,13 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRoles other = (SecurityRoles) obj; if (roles == null) { - if (other.roles != null) - return false; - } else if (!roles.equals(other.roles)) - return false; + if (other.roles != null) return false; + } else if (!roles.equals(other.roles)) return false; return true; } @@ -349,6 +343,7 @@ public String toString() { public Set getRoles() { return Collections.unmodifiableSet(roles); } + public Set getRoleNames() { return getRoles().stream().map(r -> r.getName()).collect(Collectors.toSet()); } @@ -363,10 +358,14 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService cs, - NamedXContentRegistry namedXContentRegistry) { + public EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService cs, + NamedXContentRegistry namedXContentRegistry + ) { final Map> dlsQueries = new HashMap>(); final Map> flsFields = new HashMap>(); @@ -380,8 +379,9 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, final Set maskedFields = ip.getMaskedFields(); Set concreteIndices = new HashSet<>(); - - if ((dls != null && dls.length() > 0) || (fls != null && fls.size() > 0) || (maskedFields != null && maskedFields.size() > 0)) { + if ((dls != null && dls.length() > 0) + || (fls != null && fls.size() > 0) + || (maskedFields != null && maskedFields.size() > 0)) { concreteIndices = ip.getResolvedIndexPattern(user, resolver, cs); } @@ -448,10 +448,14 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, return new EvaluatedDlsFlsConfig(dlsQueries, flsFields, maskedFieldsMap); } - - - //opensearchDashboards special only, terms eval - public Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { + // opensearchDashboards special only, terms eval + public Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); @@ -460,7 +464,7 @@ public Set getAllPermittedIndicesForDashboards(Resolved resolved, User u return Collections.unmodifiableSet(retVal); } - //dnfof only + // dnfof only public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { @@ -472,7 +476,7 @@ public Set reduce(Resolved resolved, User user, String[] actions, IndexN return Collections.unmodifiableSet(retVal); } - //return true on success + // return true on success public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { for (SecurityRole sr : roles) { if (ConfigModelV6.impliesTypePerm(sr.getIpatterns(), resolved, user, actions, resolver, cs)) { @@ -489,16 +493,20 @@ public boolean impliesClusterPermissionPermission(String action) { @Override public boolean hasExplicitClusterPermissionPermission(String action) { - return roles.stream() - .map(r -> { - final WildcardMatcher m = WildcardMatcher.from(r.clusterPerms); - return m == WildcardMatcher.ANY ? WildcardMatcher.NONE : m; - }).filter(m -> m.test(action)).count() > 0; - } - - //rolespan - public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + return roles.stream().map(r -> { + final WildcardMatcher m = WildcardMatcher.from(r.clusterPerms); + return m == WildcardMatcher.ANY ? WildcardMatcher.NONE : m; + }).filter(m -> m.test(action)).count() > 0; + } + + // rolespan + public boolean impliesTypePermGlobal( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set ipatterns = new HashSet(); roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns())); return ConfigModelV6.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs); @@ -521,14 +529,19 @@ private boolean impliesClusterPermission(String action) { return WildcardMatcher.from(clusterPerms).test(action); } - //get indices which are permitted for the given types and actions - //dnfof + opensearchDashboards special only - private Set getAllResolvedPermittedIndices(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + // get indices which are permitted for the given types and actions + // dnfof + opensearchDashboards special only + private Set getAllResolvedPermittedIndices( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { final Set retVal = new HashSet<>(); for (IndexPattern p : ipatterns) { - //what if we cannot resolve one (for create purposes) + // what if we cannot resolve one (for create purposes) boolean patternMatch = false; final Set tperms = p.getTypePerms(); for (TypePerm tp : tperms) { @@ -537,24 +550,25 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, } } if (patternMatch) { - //resolved but can contain patterns for nonexistent indices - final WildcardMatcher permitted = WildcardMatcher.from(p.getResolvedIndexPattern(user, resolver, cs)); //maybe they do not exist + // resolved but can contain patterns for nonexistent indices + final WildcardMatcher permitted = WildcardMatcher.from(p.getResolvedIndexPattern(user, resolver, cs)); // maybe they do + // not exist final Set res = new HashSet<>(); if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) { - //resolved but can contain patterns for nonexistent indices + // resolved but can contain patterns for nonexistent indices resolved.getAllIndices().stream().filter(permitted).forEach(res::add); } else { - //we want all indices so just return what's permitted + // we want all indices so just return what's permitted - //#557 - //final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); + // #557 + // final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); Arrays.stream(cs.state().metadata().getConcreteAllOpenIndices()).filter(permitted).forEach(res::add); } retVal.addAll(res); } } - //all that we want and all thats permitted of them + // all that we want and all thats permitted of them return Collections.unmodifiableSet(retVal); } @@ -592,44 +606,43 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRole other = (SecurityRole) obj; if (clusterPerms == null) { - if (other.clusterPerms != null) - return false; - } else if (!clusterPerms.equals(other.clusterPerms)) - return false; + if (other.clusterPerms != null) return false; + } else if (!clusterPerms.equals(other.clusterPerms)) return false; if (ipatterns == null) { - if (other.ipatterns != null) - return false; - } else if (!ipatterns.equals(other.ipatterns)) - return false; + if (other.ipatterns != null) return false; + } else if (!ipatterns.equals(other.ipatterns)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; if (tenants == null) { - if (other.tenants != null) - return false; - } else if (!tenants.equals(other.tenants)) - return false; + if (other.tenants != null) return false; + } else if (!tenants.equals(other.tenants)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " " + name + System.lineSeparator() + " tenants=" + tenants + System.lineSeparator() - + " ipatterns=" + ipatterns + System.lineSeparator() + " clusterPerms=" + clusterPerms; + return System.lineSeparator() + + " " + + name + + System.lineSeparator() + + " tenants=" + + tenants + + System.lineSeparator() + + " ipatterns=" + + ipatterns + + System.lineSeparator() + + " clusterPerms=" + + clusterPerms; } public Set getTenants(User user) { - //TODO filter out user tenants + // TODO filter out user tenants return Collections.unmodifiableSet(tenants); } @@ -647,7 +660,7 @@ public String getName() { } - //sg roles + // sg roles public static class IndexPattern { private final String indexPattern; private String dlsQuery; @@ -702,45 +715,42 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; IndexPattern other = (IndexPattern) obj; if (dlsQuery == null) { - if (other.dlsQuery != null) - return false; - } else if (!dlsQuery.equals(other.dlsQuery)) - return false; + if (other.dlsQuery != null) return false; + } else if (!dlsQuery.equals(other.dlsQuery)) return false; if (fls == null) { - if (other.fls != null) - return false; - } else if (!fls.equals(other.fls)) - return false; + if (other.fls != null) return false; + } else if (!fls.equals(other.fls)) return false; if (maskedFields == null) { - if (other.maskedFields != null) - return false; - } else if (!maskedFields.equals(other.maskedFields)) - return false; + if (other.maskedFields != null) return false; + } else if (!maskedFields.equals(other.maskedFields)) return false; if (indexPattern == null) { - if (other.indexPattern != null) - return false; - } else if (!indexPattern.equals(other.indexPattern)) - return false; + if (other.indexPattern != null) return false; + } else if (!indexPattern.equals(other.indexPattern)) return false; if (typePerms == null) { - if (other.typePerms != null) - return false; - } else if (!typePerms.equals(other.typePerms)) - return false; + if (other.typePerms != null) return false; + } else if (!typePerms.equals(other.typePerms)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " indexPattern=" + indexPattern + System.lineSeparator() + " dlsQuery=" + dlsQuery - + System.lineSeparator() + " fls=" + fls + System.lineSeparator() + " typePerms=" + typePerms; + return System.lineSeparator() + + " indexPattern=" + + indexPattern + + System.lineSeparator() + + " dlsQuery=" + + dlsQuery + + System.lineSeparator() + + " fls=" + + fls + + System.lineSeparator() + + " typePerms=" + + typePerms; } public String getUnresolvedIndexPattern(User user) { @@ -752,11 +762,15 @@ private Set getResolvedIndexPattern(User user, IndexNameExpressionResolv WildcardMatcher matcher = WildcardMatcher.from(unresolved); String[] resolved = null; if (!(matcher instanceof WildcardMatcher.Exact)) { - final String[] aliasesForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() - .filter(e -> e.getValue().getType() == ALIAS) - .filter(e -> matcher.test(e.getKey())) - .map(e -> e.getKey()) - .toArray(String[]::new); + final String[] aliasesForPermittedPattern = cs.state() + .getMetadata() + .getIndicesLookup() + .entrySet() + .stream() + .filter(e -> e.getValue().getType() == ALIAS) + .filter(e -> matcher.test(e.getKey())) + .map(e -> e.getKey()) + .toArray(String[]::new); if (aliasesForPermittedPattern.length > 0) { resolved = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), aliasesForPermittedPattern); @@ -769,10 +783,7 @@ private Set getResolvedIndexPattern(User user, IndexNameExpressionResolv if (resolved == null || resolved.length == 0) { return ImmutableSet.of(unresolved); } else { - return ImmutableSet.builder() - .addAll(Arrays.asList(resolved)) - .add(unresolved) - .build(); + return ImmutableSet.builder().addAll(Arrays.asList(resolved)).add(unresolved).build(); } } @@ -820,29 +831,27 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; TypePerm other = (TypePerm) obj; if (perms == null) { - if (other.perms != null) - return false; - } else if (!perms.equals(other.perms)) - return false; + if (other.perms != null) return false; + } else if (!perms.equals(other.perms)) return false; if (typeMatcher == null) { - if (other.typeMatcher != null) - return false; - } else if (!typeMatcher.equals(other.typeMatcher)) - return false; + if (other.typeMatcher != null) return false; + } else if (!typeMatcher.equals(other.typeMatcher)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " typePattern=" + typeMatcher + System.lineSeparator() + " perms=" + perms; + return System.lineSeparator() + + " typePattern=" + + typeMatcher + + System.lineSeparator() + + " perms=" + + perms; } public WildcardMatcher getTypeMatcher() { @@ -884,26 +893,25 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Tenant other = (Tenant) obj; - if (readWrite != other.readWrite) - return false; + if (readWrite != other.readWrite) return false; if (tenant == null) { - if (other.tenant != null) - return false; - } else if (!tenant.equals(other.tenant)) - return false; + if (other.tenant != null) return false; + } else if (!tenant.equals(other.tenant)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " tenant=" + tenant + System.lineSeparator() + " readWrite=" + readWrite; + return System.lineSeparator() + + " tenant=" + + tenant + + System.lineSeparator() + + " readWrite=" + + readWrite; } } @@ -981,38 +989,43 @@ public boolean matches(String index, String type, String action) { } } - private static boolean impliesTypePerm(Set ipatterns, Resolved resolved, User user, String[] requestedActions, - IndexNameExpressionResolver resolver, ClusterService cs) { + private static boolean impliesTypePerm( + Set ipatterns, + Resolved resolved, + User user, + String[] requestedActions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { IndexMatcherAndTypePermissions[] indexMatcherAndTypePermissions; if (resolved.isLocalAll()) { // Only let localAll pass if there is an explicit privilege for a * index pattern - indexMatcherAndTypePermissions = ipatterns - .stream() - .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) - .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) - .toArray(IndexMatcherAndTypePermissions[]::new); + indexMatcherAndTypePermissions = ipatterns.stream() + .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) + .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) + .toArray(IndexMatcherAndTypePermissions[]::new); } else { - indexMatcherAndTypePermissions = ipatterns - .stream() - .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) - .toArray(IndexMatcherAndTypePermissions[]::new); + indexMatcherAndTypePermissions = ipatterns.stream() + .map(p -> new IndexMatcherAndTypePermissions(p.getResolvedIndexPattern(user, resolver, cs), p.getTypePerms())) + .toArray(IndexMatcherAndTypePermissions[]::new); } return resolved.getAllIndices() - .stream().allMatch(index -> - resolved.getTypes().stream().allMatch(type -> - Arrays.stream(requestedActions).allMatch(action -> - Arrays.stream(indexMatcherAndTypePermissions).anyMatch(ipatp -> - ipatp.matches(index, type, action) - ) - ) - ) - ); + .stream() + .allMatch( + index -> resolved.getTypes() + .stream() + .allMatch( + type -> Arrays.stream(requestedActions) + .allMatch( + action -> Arrays.stream(indexMatcherAndTypePermissions) + .anyMatch(ipatp -> ipatp.matches(index, type, action)) + ) + ) + ); } - - - //####### + // ####### private class TenantHolder { @@ -1023,37 +1036,39 @@ public TenantHolder(SecurityDynamicConfiguration roles) { final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: roles.getCEntries().entrySet()) { + for (Entry securityRole : roles.getCEntries().entrySet()) { - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { continue; } - Future>>> future = execs.submit(new Callable>>>() { - @Override - public Tuple>> call() throws Exception { - final Set> tuples = new HashSet<>(); - final Map tenants = securityRole.getValue().getTenants(); - - if (tenants != null) { - - for (String tenant : tenants.keySet()) { - - if ("RW".equalsIgnoreCase(tenants.get(tenant))) { - //RW - tuples.add(new Tuple(tenant, true)); - } else { - //RO - //if(!tenantsMM.containsValue(value)) { //RW outperforms RO - tuples.add(new Tuple(tenant, false)); - //} + Future>>> future = execs.submit( + new Callable>>>() { + @Override + public Tuple>> call() throws Exception { + final Set> tuples = new HashSet<>(); + final Map tenants = securityRole.getValue().getTenants(); + + if (tenants != null) { + + for (String tenant : tenants.keySet()) { + + if ("RW".equalsIgnoreCase(tenants.get(tenant))) { + // RW + tuples.add(new Tuple(tenant, true)); + } else { + // RO + // if(!tenantsMM.containsValue(value)) { //RW outperforms RO + tuples.add(new Tuple(tenant, false)); + // } + } } } - } - return new Tuple>>(securityRole.getKey(), tuples); + return new Tuple>>(securityRole.getKey(), tuples); + } } - }); + ); futures.add(future); @@ -1069,7 +1084,9 @@ public Tuple>> call() throws Exception { } try { - final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()).hashSetValues(16).build(); + final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()) + .hashSetValues(16) + .build(); for (Future>>> future : futures) { Tuple>> result = future.get(); @@ -1097,14 +1114,18 @@ public Map mapTenants(final User user, Set roles) { final Map result = new HashMap<>(roles.size()); result.put(user.getName(), true); - tenantsMM.entries().stream().filter(e -> roles.contains(e.getKey())).filter(e -> !user.getName().equals(e.getValue().v1())).forEach(e -> { - final String tenant = e.getValue().v1(); - final boolean rw = e.getValue().v2(); + tenantsMM.entries() + .stream() + .filter(e -> roles.contains(e.getKey())) + .filter(e -> !user.getName().equals(e.getValue().v1())) + .forEach(e -> { + final String tenant = e.getValue().v1(); + final boolean rw = e.getValue().v2(); - if (rw || !result.containsKey(tenant)) { //RW outperforms RO - result.put(tenant, rw); - } - }); + if (rw || !result.containsKey(tenant)) { // RW outperforms RO + result.put(tenant, rw); + } + }); return Collections.unmodifiableMap(result); } @@ -1171,7 +1192,7 @@ private Set map(final User user, final TransportAddress caller) { final Set securityRoles = new HashSet<>(); if (rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { if (log.isDebugEnabled()) { log.debug("Pass backendroles from {}", user); } @@ -1179,7 +1200,7 @@ private Set map(final User user, final TransportAddress caller) { } if (((rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { for (String p : WildcardMatcher.getAllMatchingPatterns(userMatchers, user.getName())) { securityRoles.addAll(users.get(p)); @@ -1196,7 +1217,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller != null) { - //IPV4 or IPv6 (compressed and without scope identifiers) + // IPV4 or IPv6 (compressed and without scope identifiers) final String ipAddress = caller.getAddress(); final List hostMatchers = WildcardMatcher.matchers(hosts.keySet()); @@ -1205,7 +1226,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller.address() != null - && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { + && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { final String hostName = caller.address().getHostString(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) { @@ -1229,10 +1250,6 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 560cfb8a6d..1fb6e4da0e 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -83,20 +83,24 @@ public class ConfigModelV7 extends ConfigModel { private SecurityDynamicConfiguration tenants; public ConfigModelV7( - SecurityDynamicConfiguration roles, - SecurityDynamicConfiguration rolemappings, - SecurityDynamicConfiguration actiongroups, - SecurityDynamicConfiguration tenants, - DynamicConfigModel dcm, - Settings opensearchSettings) { + SecurityDynamicConfiguration roles, + SecurityDynamicConfiguration rolemappings, + SecurityDynamicConfiguration actiongroups, + SecurityDynamicConfiguration tenants, + DynamicConfigModel dcm, + Settings opensearchSettings + ) { this.roles = roles; this.tenants = tenants; try { rolesMappingResolution = ConfigConstants.RolesMappingResolution.valueOf( - opensearchSettings.get(ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString()) - .toUpperCase()); + opensearchSettings.get( + ConfigConstants.SECURITY_ROLES_MAPPING_RESOLUTION, + ConfigConstants.RolesMappingResolution.MAPPING_ONLY.toString() + ).toUpperCase() + ); } catch (Exception e) { log.error("Cannot apply roles mapping resolution", e); rolesMappingResolution = ConfigConstants.RolesMappingResolution.MAPPING_ONLY; @@ -134,15 +138,14 @@ private Set getGroupMembers(final String groupname) { private Set resolve(final SecurityDynamicConfiguration actionGroups, final String entry) { - // SG5 format, plain array - //List en = actionGroups.getAsList(DotPath.of(entry)); - //if (en.isEmpty()) { - // try SG6 format including readonly and permissions key - // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); - //} + // List en = actionGroups.getAsList(DotPath.of(entry)); + // if (en.isEmpty()) { + // try SG6 format including readonly and permissions key + // en = actionGroups.getAsList(DotPath.of(entry + "." + ConfigConstants.CONFIGKEY_ACTION_GROUPS_PERMISSIONS)); + // } - if(!actionGroups.getCEntries().containsKey(entry)) { + if (!actionGroups.getCEntries().containsKey(entry)) { return Collections.emptySet(); } @@ -150,27 +153,26 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, final Object actionGroupAsObject = actionGroups.getCEntries().get(entry); - if(actionGroupAsObject != null && actionGroupAsObject instanceof List) { + if (actionGroupAsObject != null && actionGroupAsObject instanceof List) { - for (final String perm: ((List) actionGroupAsObject)) { + for (final String perm : ((List) actionGroupAsObject)) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } - - } else if(actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV7) { - for (final String perm: ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) { + } else if (actionGroupAsObject != null && actionGroupAsObject instanceof ActionGroupsV7) { + for (final String perm : ((ActionGroupsV7) actionGroupAsObject).getAllowed_actions()) { if (actionGroups.getCEntries().keySet().contains(perm)) { - ret.addAll(resolve(actionGroups,perm)); + ret.addAll(resolve(actionGroups, perm)); } else { ret.add(perm); } } } else { - throw new RuntimeException("Unable to handle "+actionGroupAsObject); + throw new RuntimeException("Unable to handle " + actionGroupAsObject); } return Collections.unmodifiableSet(ret); @@ -179,7 +181,7 @@ private Set resolve(final SecurityDynamicConfiguration actionGroups, @Override public Set resolvedActions(final List actions) { final Set resolvedActions = new HashSet(); - for (String string: actions) { + for (String string : actions) { final Set groups = getGroupMembers(string); if (groups.isEmpty()) { resolvedActions.add(string); @@ -198,7 +200,7 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { final Set> futures = new HashSet<>(5000); final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: settings.getCEntries().entrySet()) { + for (Entry securityRole : settings.getCEntries().entrySet()) { Future future = execs.submit(new Callable() { @@ -206,54 +208,53 @@ private SecurityRoles reload(SecurityDynamicConfiguration settings) { public SecurityRole call() throws Exception { SecurityRole.Builder _securityRole = new SecurityRole.Builder(securityRole.getKey()); - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { return null; } final Set permittedClusterActions = agr.resolvedActions(securityRole.getValue().getCluster_permissions()); _securityRole.addClusterPerms(permittedClusterActions); - /*for(RoleV7.Tenant tenant: securityRole.getValue().getTenant_permissions()) { + /*for(RoleV7.Tenant tenant: securityRole.getValue().getTenant_permissions()) { - //if(tenant.equals(user.getName())) { - // continue; - //} + //if(tenant.equals(user.getName())) { + // continue; + //} - if(isTenantsRw(tenant)) { - _securityRole.addTenant(new Tenant(tenant.getKey(), true)); - } else { - _securityRole.addTenant(new Tenant(tenant.getKey(), false)); - } - }*/ + if(isTenantsRw(tenant)) { + _securityRole.addTenant(new Tenant(tenant.getKey(), true)); + } else { + _securityRole.addTenant(new Tenant(tenant.getKey(), false)); + } + }*/ - for (final Index permittedAliasesIndex : securityRole.getValue().getIndex_permissions()) { + for (final Index permittedAliasesIndex : securityRole.getValue().getIndex_permissions()) { - final String dls = permittedAliasesIndex.getDls(); - final List fls = permittedAliasesIndex.getFls(); - final List maskedFields = permittedAliasesIndex.getMasked_fields(); + final String dls = permittedAliasesIndex.getDls(); + final List fls = permittedAliasesIndex.getFls(); + final List maskedFields = permittedAliasesIndex.getMasked_fields(); - for(String pat: permittedAliasesIndex.getIndex_patterns()) { - IndexPattern _indexPattern = new IndexPattern(pat); - _indexPattern.setDlsQuery(dls); - _indexPattern.addFlsFields(fls); - _indexPattern.addMaskedFields(maskedFields); - _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions())); + for (String pat : permittedAliasesIndex.getIndex_patterns()) { + IndexPattern _indexPattern = new IndexPattern(pat); + _indexPattern.setDlsQuery(dls); + _indexPattern.addFlsFields(fls); + _indexPattern.addMaskedFields(maskedFields); + _indexPattern.addPerm(agr.resolvedActions(permittedAliasesIndex.getAllowed_actions())); - /*for(Entry> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) { - TypePerm typePerm = new TypePerm(type.getKey()); - final List perms = type.getValue(); - typePerm.addPerms(agr.resolvedActions(perms)); - _indexPattern.addTypePerms(typePerm); - }*/ + /*for(Entry> type: permittedAliasesIndex.getValue().getTypes(-).entrySet()) { + TypePerm typePerm = new TypePerm(type.getKey()); + final List perms = type.getValue(); + typePerm.addPerms(agr.resolvedActions(perms)); + _indexPattern.addTypePerms(typePerm); + }*/ - _securityRole.addIndexPattern(_indexPattern); - - } + _securityRole.addIndexPattern(_indexPattern); } + } - return _securityRole.build(); + return _securityRole.build(); } }); @@ -286,8 +287,7 @@ public SecurityRole call() throws Exception { } } - - //beans + // beans public static class SecurityRoles implements org.opensearch.security.securityconf.SecurityRoles { @@ -316,18 +316,13 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRoles other = (SecurityRoles) obj; if (roles == null) { - if (other.roles != null) - return false; - } else if (!roles.equals(other.roles)) - return false; + if (other.roles != null) return false; + } else if (!roles.equals(other.roles)) return false; return true; } @@ -354,14 +349,17 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService cs, - NamedXContentRegistry namedXContentRegistry) { - + public EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService cs, + NamedXContentRegistry namedXContentRegistry + ) { if (!containsDlsFlsConfig()) { - if(log.isDebugEnabled()) { + if (log.isDebugEnabled()) { log.debug("No fls or dls found for {} in {} security roles", user, roles.size()); } @@ -382,17 +380,17 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, for (SecurityRole role : roles) { for (IndexPattern ip : role.getIpatterns()) { - final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); - String dls = ip.getDlsQuery(user); + final Set concreteIndices = ip.concreteIndexNames(user, resolver, cs); + String dls = ip.getDlsQuery(user); - if (dls != null && dls.length() > 0) { + if (dls != null && dls.length() > 0) { - for (String concreteIndex : concreteIndices) { - dlsQueriesByIndex.computeIfAbsent(concreteIndex, (key) -> new HashSet()).add(dls); - } - } else if (dfmEmptyOverwritesAll) { - noDlsConcreteIndices.addAll(concreteIndices); - } + for (String concreteIndex : concreteIndices) { + dlsQueriesByIndex.computeIfAbsent(concreteIndex, (key) -> new HashSet()).add(dls); + } + } else if (dfmEmptyOverwritesAll) { + noDlsConcreteIndices.addAll(concreteIndices); + } Set fls = ip.getFls(); @@ -429,12 +427,21 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, } if (dfmEmptyOverwritesAll) { if (log.isDebugEnabled()) { - log.debug("Index patterns with no dls queries attached: {} - They will be removed from {}", noDlsConcreteIndices, - dlsQueriesByIndex.keySet()); - log.debug("Index patterns with no fls fields attached: {} - They will be removed from {}", noFlsConcreteIndices, - flsFields.keySet()); - log.debug("Index patterns with no masked fields attached: {} - They will be removed from {}", noMaskedFieldConcreteIndices, - maskedFieldsMap.keySet()); + log.debug( + "Index patterns with no dls queries attached: {} - They will be removed from {}", + noDlsConcreteIndices, + dlsQueriesByIndex.keySet() + ); + log.debug( + "Index patterns with no fls fields attached: {} - They will be removed from {}", + noFlsConcreteIndices, + flsFields.keySet() + ); + log.debug( + "Index patterns with no masked fields attached: {} - They will be removed from {}", + noMaskedFieldConcreteIndices, + maskedFieldsMap.keySet() + ); } // removing the indices that do not have D/M/F restrictions // from the keySet will also modify the underlying map @@ -446,9 +453,14 @@ public EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, return new EvaluatedDlsFlsConfig(dlsQueriesByIndex, flsFields, maskedFieldsMap); } - - //opensearchDashboards special only, terms eval - public Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { + // opensearchDashboards special only, terms eval + public Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { retVal.addAll(sr.getAllResolvedPermittedIndices(Resolved._LOCAL_ALL, user, actions, resolver, cs)); @@ -457,7 +469,7 @@ public Set getAllPermittedIndicesForDashboards(Resolved resolved, User u return Collections.unmodifiableSet(retVal); } - //dnfof only + // dnfof only public Set reduce(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { Set retVal = new HashSet<>(); for (SecurityRole sr : roles) { @@ -469,7 +481,7 @@ public Set reduce(Resolved resolved, User user, String[] actions, IndexN return Collections.unmodifiableSet(retVal); } - //return true on success + // return true on success public boolean get(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs) { for (SecurityRole sr : roles) { if (ConfigModelV7.impliesTypePerm(sr.getIpatterns(), resolved, user, actions, resolver, cs)) { @@ -487,13 +499,19 @@ public boolean impliesClusterPermissionPermission(String action) { @Override public boolean hasExplicitClusterPermissionPermission(String action) { return roles.stream() - .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) - .filter(m -> m.test(action)).count() > 0; - } - - //rolespan - public boolean impliesTypePermGlobal(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + .map(r -> r.clusterPerms == WildcardMatcher.ANY ? WildcardMatcher.NONE : r.clusterPerms) + .filter(m -> m.test(action)) + .count() > 0; + } + + // rolespan + public boolean impliesTypePermGlobal( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set ipatterns = new HashSet(); roles.stream().forEach(p -> ipatterns.addAll(p.getIpatterns())); return ConfigModelV7.impliesTypePerm(ipatterns, resolved, user, actions, resolver, cs); @@ -521,6 +539,7 @@ public static final class Builder { private final String name; private final Set clusterPerms = new HashSet<>(); private final Set ipatterns = new HashSet<>(); + public Builder(String name) { this.name = Objects.requireNonNull(name); } @@ -537,7 +556,6 @@ public Builder addClusterPerms(Collection clusterPerms) { return this; } - public SecurityRole build() { return new SecurityRole(name, ipatterns, WildcardMatcher.from(clusterPerms)); } @@ -553,34 +571,40 @@ private boolean impliesClusterPermission(String action) { return clusterPerms.test(action); } - //get indices which are permitted for the given types and actions - //dnfof + opensearchDashboards special only - private Set getAllResolvedPermittedIndices(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, - ClusterService cs) { + // get indices which are permitted for the given types and actions + // dnfof + opensearchDashboards special only + private Set getAllResolvedPermittedIndices( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { final Set retVal = new HashSet<>(); for (IndexPattern p : ipatterns) { - //what if we cannot resolve one (for create purposes) + // what if we cannot resolve one (for create purposes) final boolean patternMatch = p.getPerms().matchAll(actions); -// final Set tperms = p.getTypePerms(); -// for (TypePerm tp : tperms) { -// if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) { -// patternMatch = WildcardMatcher.matchAll(tp.perms.toArray(new String[0]), actions); -// } -// } + // final Set tperms = p.getTypePerms(); + // for (TypePerm tp : tperms) { + // if (WildcardMatcher.matchAny(tp.typePattern, resolved.getTypes(-).toArray(new String[0]))) { + // patternMatch = WildcardMatcher.matchAll(tp.perms.toArray(new String[0]), actions); + // } + // } if (patternMatch) { - //resolved but can contain patterns for nonexistent indices - final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); //maybe they do not exist + // resolved but can contain patterns for nonexistent indices + final WildcardMatcher permitted = WildcardMatcher.from(p.attemptResolveIndexNames(user, resolver, cs)); // maybe they do + // not exist final Set res = new HashSet<>(); if (!resolved.isLocalAll() && !resolved.getAllIndices().contains("*") && !resolved.getAllIndices().contains("_all")) { - //resolved but can contain patterns for nonexistent indices + // resolved but can contain patterns for nonexistent indices resolved.getAllIndices().stream().filter(permitted).forEach(res::add); } else { - //we want all indices so just return what's permitted + // we want all indices so just return what's permitted - //#557 - //final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); + // #557 + // final String[] allIndices = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), "*"); final String[] allIndices = cs.state().metadata().getConcreteAllOpenIndices(); Arrays.stream(allIndices).filter(permitted).forEach(res::add); } @@ -588,7 +612,7 @@ private Set getAllResolvedPermittedIndices(Resolved resolved, User user, } } - //all that we want and all thats permitted of them + // all that we want and all thats permitted of them return Collections.unmodifiableSet(retVal); } @@ -606,52 +630,50 @@ public int hashCode() { result = prime * result + ((clusterPerms == null) ? 0 : clusterPerms.hashCode()); result = prime * result + ((ipatterns == null) ? 0 : ipatterns.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); - //result = prime * result + ((tenants == null) ? 0 : tenants.hashCode()); + // result = prime * result + ((tenants == null) ? 0 : tenants.hashCode()); return result; } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; SecurityRole other = (SecurityRole) obj; if (clusterPerms == null) { - if (other.clusterPerms != null) - return false; - } else if (!clusterPerms.equals(other.clusterPerms)) - return false; + if (other.clusterPerms != null) return false; + } else if (!clusterPerms.equals(other.clusterPerms)) return false; if (ipatterns == null) { - if (other.ipatterns != null) - return false; - } else if (!ipatterns.equals(other.ipatterns)) - return false; + if (other.ipatterns != null) return false; + } else if (!ipatterns.equals(other.ipatterns)) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; -// if (tenants == null) { -// if (other.tenants != null) -// return false; -// } else if (!tenants.equals(other.tenants)) -// return false; + if (other.name != null) return false; + } else if (!name.equals(other.name)) return false; + // if (tenants == null) { + // if (other.tenants != null) + // return false; + // } else if (!tenants.equals(other.tenants)) + // return false; return true; } @Override public String toString() { - return System.lineSeparator() + " " + name + System.lineSeparator() - + " ipatterns=" + ipatterns + System.lineSeparator() + " clusterPerms=" + clusterPerms; - } - - //public Set getTenants(User user) { - // //TODO filter out user tenants - // return Collections.unmodifiableSet(tenants); - //} + return System.lineSeparator() + + " " + + name + + System.lineSeparator() + + " ipatterns=" + + ipatterns + + System.lineSeparator() + + " clusterPerms=" + + clusterPerms; + } + + // public Set getTenants(User user) { + // //TODO filter out user tenants + // return Collections.unmodifiableSet(tenants); + // } public Set getIpatterns() { return Collections.unmodifiableSet(ipatterns); @@ -663,7 +685,7 @@ public String getName() { } - //sg roles + // sg roles public static class IndexPattern { private final String indexPattern; private String dlsQuery; @@ -718,45 +740,42 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; IndexPattern other = (IndexPattern) obj; if (dlsQuery == null) { - if (other.dlsQuery != null) - return false; - } else if (!dlsQuery.equals(other.dlsQuery)) - return false; + if (other.dlsQuery != null) return false; + } else if (!dlsQuery.equals(other.dlsQuery)) return false; if (fls == null) { - if (other.fls != null) - return false; - } else if (!fls.equals(other.fls)) - return false; + if (other.fls != null) return false; + } else if (!fls.equals(other.fls)) return false; if (maskedFields == null) { - if (other.maskedFields != null) - return false; - } else if (!maskedFields.equals(other.maskedFields)) - return false; + if (other.maskedFields != null) return false; + } else if (!maskedFields.equals(other.maskedFields)) return false; if (indexPattern == null) { - if (other.indexPattern != null) - return false; - } else if (!indexPattern.equals(other.indexPattern)) - return false; + if (other.indexPattern != null) return false; + } else if (!indexPattern.equals(other.indexPattern)) return false; if (perms == null) { - if (other.perms != null) - return false; - } else if (!perms.equals(other.perms)) - return false; + if (other.perms != null) return false; + } else if (!perms.equals(other.perms)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " indexPattern=" + indexPattern + System.lineSeparator() + " dlsQuery=" + dlsQuery - + System.lineSeparator() + " fls=" + fls + System.lineSeparator() + " perms=" + perms; + return System.lineSeparator() + + " indexPattern=" + + indexPattern + + System.lineSeparator() + + " dlsQuery=" + + dlsQuery + + System.lineSeparator() + + " fls=" + + fls + + System.lineSeparator() + + " perms=" + + perms; } public String getUnresolvedIndexPattern(User user) { @@ -773,27 +792,45 @@ public Set attemptResolveIndexNames(final User user, final IndexNameExpr return getResolvedIndexPattern(user, resolver, cs, true); } - public Set getResolvedIndexPattern(final User user, final IndexNameExpressionResolver resolver, final ClusterService cs, final boolean appendUnresolved) { + public Set getResolvedIndexPattern( + final User user, + final IndexNameExpressionResolver resolver, + final ClusterService cs, + final boolean appendUnresolved + ) { final String unresolved = getUnresolvedIndexPattern(user); final ImmutableSet.Builder resolvedIndices = new ImmutableSet.Builder<>(); final WildcardMatcher matcher = WildcardMatcher.from(unresolved); boolean includeDataStreams = true; if (!(matcher instanceof WildcardMatcher.Exact)) { - final String[] aliasesAndDataStreamsForPermittedPattern = cs.state().getMetadata().getIndicesLookup().entrySet().stream() - .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM)) - .filter(e -> matcher.test(e.getKey())) - .map(e -> e.getKey()) - .toArray(String[]::new); + final String[] aliasesAndDataStreamsForPermittedPattern = cs.state() + .getMetadata() + .getIndicesLookup() + .entrySet() + .stream() + .filter(e -> (e.getValue().getType() == ALIAS) || (e.getValue().getType() == DATA_STREAM)) + .filter(e -> matcher.test(e.getKey())) + .map(e -> e.getKey()) + .toArray(String[]::new); if (aliasesAndDataStreamsForPermittedPattern.length > 0) { - final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames(cs.state(), - IndicesOptions.lenientExpandOpen(), includeDataStreams, aliasesAndDataStreamsForPermittedPattern); + final String[] resolvedAliasesAndDataStreamIndices = resolver.concreteIndexNames( + cs.state(), + IndicesOptions.lenientExpandOpen(), + includeDataStreams, + aliasesAndDataStreamsForPermittedPattern + ); resolvedIndices.addAll(Arrays.asList(resolvedAliasesAndDataStreamIndices)); } } if (Strings.isNotBlank(unresolved)) { - final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames(cs.state(), IndicesOptions.lenientExpandOpen(), includeDataStreams, unresolved); + final String[] resolvedIndicesFromPattern = resolver.concreteIndexNames( + cs.state(), + IndicesOptions.lenientExpandOpen(), + includeDataStreams, + unresolved + ); resolvedIndices.addAll(Arrays.asList(resolvedIndicesFromPattern)); } @@ -831,7 +868,6 @@ public WildcardMatcher getPerms() { return WildcardMatcher.from(perms); } - } /*public static class TypePerm { @@ -928,26 +964,25 @@ public int hashCode() { @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; Tenant other = (Tenant) obj; - if (readWrite != other.readWrite) - return false; + if (readWrite != other.readWrite) return false; if (tenant == null) { - if (other.tenant != null) - return false; - } else if (!tenant.equals(other.tenant)) - return false; + if (other.tenant != null) return false; + } else if (!tenant.equals(other.tenant)) return false; return true; } @Override public String toString() { - return System.lineSeparator() + " tenant=" + tenant + System.lineSeparator() + " readWrite=" + readWrite; + return System.lineSeparator() + + " tenant=" + + tenant + + System.lineSeparator() + + " readWrite=" + + readWrite; } } @@ -997,6 +1032,7 @@ private static String toQuotedCommaSeparatedString(final Set roles) { private static final class IndexMatcherAndPermissions { private WildcardMatcher matcher; private WildcardMatcher perms; + public IndexMatcherAndPermissions(Set patterns, Set perms) { this.matcher = WildcardMatcher.from(patterns); this.perms = WildcardMatcher.from(perms); @@ -1007,31 +1043,31 @@ public boolean matches(String index, String action) { } } - private static boolean impliesTypePerm(Set ipatterns, Resolved resolved, User user, String[] requestedActions, - IndexNameExpressionResolver resolver, ClusterService cs) { + private static boolean impliesTypePerm( + Set ipatterns, + Resolved resolved, + User user, + String[] requestedActions, + IndexNameExpressionResolver resolver, + ClusterService cs + ) { Set resolvedRequestedIndices = resolved.getAllIndices(); IndexMatcherAndPermissions[] indexMatcherAndPermissions; if (resolved.isLocalAll()) { - indexMatcherAndPermissions = ipatterns - .stream() - .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) - .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) - .toArray(IndexMatcherAndPermissions[]::new); + indexMatcherAndPermissions = ipatterns.stream() + .filter(indexPattern -> "*".equals(indexPattern.getUnresolvedIndexPattern(user))) + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) + .toArray(IndexMatcherAndPermissions[]::new); } else { - indexMatcherAndPermissions = ipatterns - .stream() - .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) - .toArray(IndexMatcherAndPermissions[]::new); - } - return resolvedRequestedIndices - .stream() - .allMatch(index -> - Arrays.stream(requestedActions).allMatch(action -> - Arrays.stream(indexMatcherAndPermissions).anyMatch(ipap -> - ipap.matches(index, action) - ) - ) - ); + indexMatcherAndPermissions = ipatterns.stream() + .map(p -> new IndexMatcherAndPermissions(p.attemptResolveIndexNames(user, resolver, cs), p.perms)) + .toArray(IndexMatcherAndPermissions[]::new); + } + return resolvedRequestedIndices.stream() + .allMatch( + index -> Arrays.stream(requestedActions) + .allMatch(action -> Arrays.stream(indexMatcherAndPermissions).anyMatch(ipap -> ipap.matches(index, action))) + ); } private class TenantHolder { @@ -1043,38 +1079,54 @@ public TenantHolder(SecurityDynamicConfiguration roles, SecurityDynamicC final ExecutorService execs = Executors.newFixedThreadPool(10); - for(Entry securityRole: roles.getCEntries().entrySet()) { + for (Entry securityRole : roles.getCEntries().entrySet()) { - if(securityRole.getValue() == null) { + if (securityRole.getValue() == null) { continue; } - Future>>> future = execs.submit(new Callable>>>() { - @Override - public Tuple>> call() throws Exception { - final Set> tuples = new HashSet<>(); - final List tenants = securityRole.getValue().getTenant_permissions(); - if (tenants != null) { - - for (RoleV7.Tenant tenant : tenants) { - - // find Wildcarded tenant patterns - List matchingTenants = WildcardMatcher.from(tenant.getTenant_patterns()).getMatchAny(definedTenants.getCEntries().keySet(), Collectors.toList()) ; - for(String matchingTenant: matchingTenants ) { - tuples.add(new Tuple(matchingTenant, agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write"))); - } - // find parameter substitution specified tenant - Pattern parameterPattern = Pattern.compile("^\\$\\{attr"); - List matchingParameterTenantList = tenant.getTenant_patterns().stream().filter(parameterPattern.asPredicate()).collect(Collectors.toList()); - for(String matchingParameterTenant : matchingParameterTenantList ) { - tuples.add(new Tuple(matchingParameterTenant,agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write"))) ; + Future>>> future = execs.submit( + new Callable>>>() { + @Override + public Tuple>> call() throws Exception { + final Set> tuples = new HashSet<>(); + final List tenants = securityRole.getValue().getTenant_permissions(); + if (tenants != null) { + + for (RoleV7.Tenant tenant : tenants) { + + // find Wildcarded tenant patterns + List matchingTenants = WildcardMatcher.from(tenant.getTenant_patterns()) + .getMatchAny(definedTenants.getCEntries().keySet(), Collectors.toList()); + for (String matchingTenant : matchingTenants) { + tuples.add( + new Tuple( + matchingTenant, + agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write") + ) + ); + } + // find parameter substitution specified tenant + Pattern parameterPattern = Pattern.compile("^\\$\\{attr"); + List matchingParameterTenantList = tenant.getTenant_patterns() + .stream() + .filter(parameterPattern.asPredicate()) + .collect(Collectors.toList()); + for (String matchingParameterTenant : matchingParameterTenantList) { + tuples.add( + new Tuple( + matchingParameterTenant, + agr.resolvedActions(tenant.getAllowed_actions()).contains("kibana:saved_objects/*/write") + ) + ); + } } } - } - return new Tuple>>(securityRole.getKey(), tuples); + return new Tuple>>(securityRole.getKey(), tuples); + } } - }); + ); futures.add(future); @@ -1090,7 +1142,9 @@ public Tuple>> call() throws Exception { } try { - final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()).hashSetValues(16).build(); + final SetMultimap> tenantsMM_ = SetMultimapBuilder.hashKeys(futures.size()) + .hashSetValues(16) + .build(); for (Future>>> future : futures) { Tuple>> result = future.get(); @@ -1118,32 +1172,33 @@ public Map mapTenants(final User user, Set roles) { final Map result = new HashMap<>(roles.size()); result.put(user.getName(), true); - tenantsMM.entries().stream().filter(e -> roles.contains(e.getKey())).filter(e -> !user.getName().equals(e.getValue().v1())).forEach(e -> { - - // replaceProperties for tenant name because - // at this point e.getValue().v1() can be in this form : "${attr.[internal|jwt|proxy|ldap].*}" - // let's substitute it with the eventual value of the user's attribute - final String tenant = replaceProperties(e.getValue().v1(),user); - final boolean rw = e.getValue().v2(); - - if (rw || !result.containsKey(tenant)) { //RW outperforms RO - - // We want to make sure that we add a tenant that exists - // Indeed, because we don't have control over what will be - // passed on as values of users' attributes, we have to make - // sure that we don't allow them to select tenants that do not exist. - if(ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) { - result.put(tenant, rw); + tenantsMM.entries() + .stream() + .filter(e -> roles.contains(e.getKey())) + .filter(e -> !user.getName().equals(e.getValue().v1())) + .forEach(e -> { + + // replaceProperties for tenant name because + // at this point e.getValue().v1() can be in this form : "${attr.[internal|jwt|proxy|ldap].*}" + // let's substitute it with the eventual value of the user's attribute + final String tenant = replaceProperties(e.getValue().v1(), user); + final boolean rw = e.getValue().v2(); + + if (rw || !result.containsKey(tenant)) { // RW outperforms RO + + // We want to make sure that we add a tenant that exists + // Indeed, because we don't have control over what will be + // passed on as values of users' attributes, we have to make + // sure that we don't allow them to select tenants that do not exist. + if (ConfigModelV7.this.tenants.getCEntries().containsKey(tenant)) { + result.put(tenant, rw); + } } - } - }); + }); Set _roles = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); _roles.addAll(roles); - if(!result.containsKey("global_tenant") && ( - _roles.contains("kibana_user") - || _roles.contains("all_access") - )) { + if (!result.containsKey("global_tenant") && (_roles.contains("kibana_user") || _roles.contains("all_access"))) { result.put("global_tenant", true); } @@ -1212,7 +1267,7 @@ private Set map(final User user, final TransportAddress caller) { final Set securityRoles = new HashSet<>(user.getSecurityRoles()); if (rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.BACKENDROLES_ONLY) { if (log.isDebugEnabled()) { log.debug("Pass backendroles from {}", user); } @@ -1220,7 +1275,7 @@ private Set map(final User user, final TransportAddress caller) { } if (((rolesMappingResolution == ConfigConstants.RolesMappingResolution.BOTH - || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { + || rolesMappingResolution == ConfigConstants.RolesMappingResolution.MAPPING_ONLY))) { for (String p : WildcardMatcher.getAllMatchingPatterns(userMatchers, user.getName())) { securityRoles.addAll(users.get(p)); @@ -1236,7 +1291,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller != null) { - //IPV4 or IPv6 (compressed and without scope identifiers) + // IPV4 or IPv6 (compressed and without scope identifiers) final String ipAddress = caller.getAddress(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, ipAddress)) { @@ -1244,7 +1299,7 @@ private Set map(final User user, final TransportAddress caller) { } if (caller.address() != null - && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { + && (hostResolverMode.equalsIgnoreCase("ip-hostname") || hostResolverMode.equalsIgnoreCase("ip-hostname-lookup"))) { final String hostName = caller.address().getHostString(); for (String p : WildcardMatcher.getAllMatchingPatterns(hostMatchers, hostName)) { @@ -1268,10 +1323,6 @@ private Set map(final User user, final TransportAddress caller) { } } - - - - public Map mapTenants(User user, Set roles) { return tenantHolder.mapTenants(user, roles); } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java index 9d8c36576c..65be27d64f 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigFactory.java @@ -89,29 +89,32 @@ static void resetStatics() { } private void loadStaticConfig() throws IOException { - JsonNode staticRolesJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_roles.yml")); + JsonNode staticRolesJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_roles.yml") + ); staticRoles = SecurityDynamicConfiguration.fromNode(staticRolesJsonNode, CType.ROLES, 2, 0, 0); - JsonNode staticActionGroupsJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_action_groups.yml")); + JsonNode staticActionGroupsJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_action_groups.yml") + ); staticActionGroups = SecurityDynamicConfiguration.fromNode(staticActionGroupsJsonNode, CType.ACTIONGROUPS, 2, 0, 0); - JsonNode staticTenantsJsonNode = DefaultObjectMapper.YAML_MAPPER - .readTree(DynamicConfigFactory.class.getResourceAsStream("/static_config/static_tenants.yml")); + JsonNode staticTenantsJsonNode = DefaultObjectMapper.YAML_MAPPER.readTree( + DynamicConfigFactory.class.getResourceAsStream("/static_config/static_tenants.yml") + ); staticTenants = SecurityDynamicConfiguration.fromNode(staticTenantsJsonNode, CType.TENANTS, 2, 0, 0); } public final static SecurityDynamicConfiguration addStatics(SecurityDynamicConfiguration original) { - if(original.getCType() == CType.ACTIONGROUPS && !staticActionGroups.getCEntries().isEmpty()) { + if (original.getCType() == CType.ACTIONGROUPS && !staticActionGroups.getCEntries().isEmpty()) { original.add(staticActionGroups.deepClone()); } - if(original.getCType() == CType.ROLES && !staticRoles.getCEntries().isEmpty()) { + if (original.getCType() == CType.ROLES && !staticRoles.getCEntries().isEmpty()) { original.add(staticRoles.deepClone()); } - if(original.getCType() == CType.TENANTS && !staticTenants.getCEntries().isEmpty()) { + if (original.getCType() == CType.TENANTS && !staticTenants.getCEntries().isEmpty()) { original.add(staticTenants.deepClone()); } @@ -128,18 +131,24 @@ public final static SecurityDynamicConfiguration addStatics(SecurityDynamicCo SecurityDynamicConfiguration config; - public DynamicConfigFactory(ConfigurationRepository cr, final Settings opensearchSettings, - final Path configPath, Client client, ThreadPool threadPool, ClusterInfoHolder cih) { + public DynamicConfigFactory( + ConfigurationRepository cr, + final Settings opensearchSettings, + final Path configPath, + Client client, + ThreadPool threadPool, + ClusterInfoHolder cih + ) { super(); this.cr = cr; this.opensearchSettings = opensearchSettings; this.configPath = configPath; - if(opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true)) { + if (opensearchSettings.getAsBoolean(ConfigConstants.SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true)) { try { loadStaticConfig(); } catch (IOException e) { - throw new StaticResourceException("Unable to load static resources due to "+e, e); + throw new StaticResourceException("Unable to load static resources due to " + e, e); } } else { log.info("Static resources will not be loaded."); @@ -162,18 +171,55 @@ public void onChange(Map> typeToConfig) { SecurityDynamicConfiguration whitelistingSetting = cr.getConfiguration(CType.WHITELIST); SecurityDynamicConfiguration allowlistingSetting = cr.getConfiguration(CType.ALLOWLIST); - if (log.isDebugEnabled()) { - String logmsg = "current config (because of " + typeToConfig.keySet() + ")\n" + - " actionGroups: " + actionGroups.getImplementingClass() + " with " + actionGroups.getCEntries().size() + " entries\n" + - " config: " + config.getImplementingClass() + " with " + config.getCEntries().size() + " entries\n" + - " internalusers: " + internalusers.getImplementingClass() + " with " + internalusers.getCEntries().size() + " entries\n" + - " roles: " + roles.getImplementingClass() + " with " + roles.getCEntries().size() + " entries\n" + - " rolesmapping: " + rolesmapping.getImplementingClass() + " with " + rolesmapping.getCEntries().size() + " entries\n" + - " tenants: " + tenants.getImplementingClass() + " with " + tenants.getCEntries().size() + " entries\n" + - " nodesdn: " + nodesDn.getImplementingClass() + " with " + nodesDn.getCEntries().size() + " entries\n" + - " whitelist " + whitelistingSetting.getImplementingClass() + " with " + whitelistingSetting.getCEntries().size() + " entries\n" + - " allowlist " + allowlistingSetting.getImplementingClass() + " with " + allowlistingSetting.getCEntries().size() + " entries\n"; + String logmsg = "current config (because of " + + typeToConfig.keySet() + + ")\n" + + " actionGroups: " + + actionGroups.getImplementingClass() + + " with " + + actionGroups.getCEntries().size() + + " entries\n" + + " config: " + + config.getImplementingClass() + + " with " + + config.getCEntries().size() + + " entries\n" + + " internalusers: " + + internalusers.getImplementingClass() + + " with " + + internalusers.getCEntries().size() + + " entries\n" + + " roles: " + + roles.getImplementingClass() + + " with " + + roles.getCEntries().size() + + " entries\n" + + " rolesmapping: " + + rolesmapping.getImplementingClass() + + " with " + + rolesmapping.getCEntries().size() + + " entries\n" + + " tenants: " + + tenants.getImplementingClass() + + " with " + + tenants.getCEntries().size() + + " entries\n" + + " nodesdn: " + + nodesDn.getImplementingClass() + + " with " + + nodesDn.getCEntries().size() + + " entries\n" + + " whitelist " + + whitelistingSetting.getImplementingClass() + + " with " + + whitelistingSetting.getCEntries().size() + + " entries\n" + + " allowlist " + + allowlistingSetting.getImplementingClass() + + " with " + + allowlistingSetting.getCEntries().size() + + " entries\n"; log.debug(logmsg); } @@ -183,70 +229,85 @@ public void onChange(Map> typeToConfig) { final NodesDnModel nm = new NodesDnModelImpl(nodesDn); final WhitelistingSettings whitelist = (WhitelistingSettings) cr.getConfiguration(CType.WHITELIST).getCEntry("config"); final AllowlistingSettings allowlist = (AllowlistingSettings) cr.getConfiguration(CType.ALLOWLIST).getCEntry("config"); - final AuditConfig audit = (AuditConfig)cr.getConfiguration(CType.AUDIT).getCEntry("config"); - - if(config.getImplementingClass() == ConfigV7.class) { - //statics - - if(roles.containsAny(staticRoles)) { - throw new StaticResourceException("Cannot override static roles"); - } - if(!roles.add(staticRoles) && !staticRoles.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static roles"); - } - - log.debug("Static roles loaded ({})", staticRoles.getCEntries().size()); + final AuditConfig audit = (AuditConfig) cr.getConfiguration(CType.AUDIT).getCEntry("config"); - if(actionGroups.containsAny(staticActionGroups)) { - System.out.println("static: " + actionGroups.getCEntries()); - System.out.println("Static Action Groups:" + staticActionGroups.getCEntries()); - throw new StaticResourceException("Cannot override static action groups"); - } - if(!actionGroups.add(staticActionGroups) && !staticActionGroups.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static action groups"); - } + if (config.getImplementingClass() == ConfigV7.class) { + // statics + if (roles.containsAny(staticRoles)) { + throw new StaticResourceException("Cannot override static roles"); + } + if (!roles.add(staticRoles) && !staticRoles.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static roles"); + } - log.debug("Static action groups loaded ({})", staticActionGroups.getCEntries().size()); - - if(tenants.containsAny(staticTenants)) { - throw new StaticResourceException("Cannot override static tenants"); - } - if(!tenants.add(staticTenants) && !staticTenants.getCEntries().isEmpty()) { - throw new StaticResourceException("Unable to load static tenants"); - } + log.debug("Static roles loaded ({})", staticRoles.getCEntries().size()); + if (actionGroups.containsAny(staticActionGroups)) { + System.out.println("static: " + actionGroups.getCEntries()); + System.out.println("Static Action Groups:" + staticActionGroups.getCEntries()); + throw new StaticResourceException("Cannot override static action groups"); + } + if (!actionGroups.add(staticActionGroups) && !staticActionGroups.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static action groups"); + } - log.debug("Static tenants loaded ({})", staticTenants.getCEntries().size()); + log.debug("Static action groups loaded ({})", staticActionGroups.getCEntries().size()); - log.debug("Static configuration loaded (total roles: {}/total action groups: {}/total tenants: {})", - roles.getCEntries().size(), actionGroups.getCEntries().size(), tenants.getCEntries().size()); + if (tenants.containsAny(staticTenants)) { + throw new StaticResourceException("Cannot override static tenants"); + } + if (!tenants.add(staticTenants) && !staticTenants.getCEntries().isEmpty()) { + throw new StaticResourceException("Unable to load static tenants"); + } + log.debug("Static tenants loaded ({})", staticTenants.getCEntries().size()); + log.debug( + "Static configuration loaded (total roles: {}/total action groups: {}/total tenants: {})", + roles.getCEntries().size(), + actionGroups.getCEntries().size(), + tenants.getCEntries().size() + ); - //rebuild v7 Models + // rebuild v7 Models dcm = new DynamicConfigModelV7(getConfigV7(config), opensearchSettings, configPath, iab); - ium = new InternalUsersModelV7((SecurityDynamicConfiguration) internalusers, + ium = new InternalUsersModelV7( + (SecurityDynamicConfiguration) internalusers, + (SecurityDynamicConfiguration) roles, + (SecurityDynamicConfiguration) rolesmapping + ); + cm = new ConfigModelV7( (SecurityDynamicConfiguration) roles, - (SecurityDynamicConfiguration) rolesmapping); - cm = new ConfigModelV7((SecurityDynamicConfiguration) roles,(SecurityDynamicConfiguration)rolesmapping, (SecurityDynamicConfiguration)actionGroups, (SecurityDynamicConfiguration) tenants,dcm, opensearchSettings); + (SecurityDynamicConfiguration) rolesmapping, + (SecurityDynamicConfiguration) actionGroups, + (SecurityDynamicConfiguration) tenants, + dcm, + opensearchSettings + ); } else { - //rebuild v6 Models + // rebuild v6 Models dcm = new DynamicConfigModelV6(getConfigV6(config), opensearchSettings, configPath, iab); ium = new InternalUsersModelV6((SecurityDynamicConfiguration) internalusers); - cm = new ConfigModelV6((SecurityDynamicConfiguration) roles, (SecurityDynamicConfiguration)actionGroups, (SecurityDynamicConfiguration)rolesmapping, dcm, opensearchSettings); + cm = new ConfigModelV6( + (SecurityDynamicConfiguration) roles, + (SecurityDynamicConfiguration) actionGroups, + (SecurityDynamicConfiguration) rolesmapping, + dcm, + opensearchSettings + ); } - //notify subscribers + // notify subscribers eventBus.post(cm); eventBus.post(dcm); eventBus.post(ium); eventBus.post(nm); - eventBus.post(whitelist==null? defaultWhitelistingSettings: whitelist); - eventBus.post(allowlist==null? defaultAllowlistingSettings: allowlist); + eventBus.post(whitelist == null ? defaultWhitelistingSettings : whitelist); + eventBus.post(allowlist == null ? defaultAllowlistingSettings : allowlist); if (cr.isAuditHotReloadingEnabled()) { eventBus.post(audit); } @@ -288,9 +349,11 @@ private static class InternalUsersModelV7 extends InternalUsersModel { private final SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration; - public InternalUsersModelV7(SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration, - SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration, - SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration) { + public InternalUsersModelV7( + SecurityDynamicConfiguration internalUserV7SecurityDynamicConfiguration, + SecurityDynamicConfiguration rolesV7SecurityDynamicConfiguration, + SecurityDynamicConfiguration rolesMappingsV7SecurityDynamicConfiguration + ) { super(); this.internalUserV7SecurityDynamicConfiguration = internalUserV7SecurityDynamicConfiguration; this.rolesV7SecurityDynamicConfiguration = rolesV7SecurityDynamicConfiguration; @@ -305,25 +368,25 @@ public boolean exists(String user) { @Override public List getBackenRoles(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getBackend_roles(); + return tmp == null ? null : tmp.getBackend_roles(); } @Override public Map getAttributes(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getAttributes(); + return tmp == null ? null : tmp.getAttributes(); } @Override public String getDescription(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getDescription(); + return tmp == null ? null : tmp.getDescription(); } @Override public String getHash(String user) { InternalUserV7 tmp = internalUserV7SecurityDynamicConfiguration.getCEntry(user); - return tmp==null?null:tmp.getHash(); + return tmp == null ? null : tmp.getHash(); } public List getSecurityRoles(String user) { @@ -331,14 +394,18 @@ public List getSecurityRoles(String user) { // Security roles should only contain roles that exist in the roles dynamic config. // We should filter out any roles that have hidden rolesmapping. - return tmp == null ? ImmutableList.of() : - tmp.getOpendistro_security_roles().stream().filter(role -> !isRolesMappingHidden(role) && rolesV7SecurityDynamicConfiguration.exists(role)).collect(ImmutableList.toImmutableList()); + return tmp == null + ? ImmutableList.of() + : tmp.getOpendistro_security_roles() + .stream() + .filter(role -> !isRolesMappingHidden(role) && rolesV7SecurityDynamicConfiguration.exists(role)) + .collect(ImmutableList.toImmutableList()); } // Remove any hidden rolesmapping from the security roles private boolean isRolesMappingHidden(String rolename) { final RoleMappingsV7 roleMapping = rolesMappingsV7SecurityDynamicConfiguration.getCEntry(rolename); - return roleMapping!=null && roleMapping.isHidden(); + return roleMapping != null && roleMapping.isHidden(); } } @@ -346,7 +413,6 @@ private static class InternalUsersModelV6 extends InternalUsersModel { SecurityDynamicConfiguration configuration; - public InternalUsersModelV6(SecurityDynamicConfiguration configuration) { super(); this.configuration = configuration; @@ -360,13 +426,13 @@ public boolean exists(String user) { @Override public List getBackenRoles(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getRoles(); + return tmp == null ? null : tmp.getRoles(); } @Override public Map getAttributes(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getAttributes(); + return tmp == null ? null : tmp.getAttributes(); } @Override @@ -377,7 +443,7 @@ public String getDescription(String user) { @Override public String getHash(String user) { InternalUserV6 tmp = configuration.getCEntry(user); - return tmp==null?null:tmp.getHash(); + return tmp == null ? null : tmp.getHash(); } public List getSecurityRoles(String user) { @@ -391,14 +457,17 @@ private static class NodesDnModelImpl extends NodesDnModel { public NodesDnModelImpl(SecurityDynamicConfiguration configuration) { super(); - this.configuration = null == configuration.getCType() ? SecurityDynamicConfiguration.empty() : - (SecurityDynamicConfiguration)configuration; + this.configuration = null == configuration.getCType() + ? SecurityDynamicConfiguration.empty() + : (SecurityDynamicConfiguration) configuration; } @Override public Map getNodesDn() { - return this.configuration.getCEntries().entrySet().stream().collect( - ImmutableMap.toImmutableMap(Entry::getKey, entry -> WildcardMatcher.from(entry.getValue().getNodesDn(), false))); + return this.configuration.getCEntries() + .entrySet() + .stream() + .collect(ImmutableMap.toImmutableMap(Entry::getKey, entry -> WildcardMatcher.from(entry.getValue().getNodesDn(), false))); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index 22121bca7f..08976f2013 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -55,30 +55,53 @@ public abstract class DynamicConfigModel { protected final Logger log = LogManager.getLogger(this.getClass()); + public abstract SortedSet getRestAuthDomains(); + public abstract Set getRestAuthorizers(); + public abstract boolean isAnonymousAuthenticationEnabled(); + public abstract boolean isXffEnabled(); + public abstract String getInternalProxies(); + public abstract String getRemoteIpHeader(); + public abstract boolean isRestAuthDisabled(); + public abstract boolean isInterTransportAuthDisabled(); + public abstract boolean isRespectRequestIndicesEnabled(); + public abstract String getDashboardsServerUsername(); + public abstract String getDashboardsOpenSearchRole(); + public abstract String getDashboardsIndexname(); + public abstract boolean isDashboardsMultitenancyEnabled(); + public abstract boolean isDashboardsPrivateTenantEnabled(); + public abstract String getDashboardsDefaultTenant(); + public abstract boolean isDnfofEnabled(); + public abstract boolean isMultiRolespanEnabled(); + public abstract String getFilteredAliasMode(); + public abstract String getHostsResolverMode(); + public abstract boolean isDnfofForEmptyResultsEnabled(); public abstract List getIpAuthFailureListeners(); + public abstract Multimap getAuthBackendFailureListeners(); + public abstract List> getIpClientBlockRegistries(); + public abstract Multimap> getAuthBackendClientBlockRegistries(); protected final Map authImplMap = new HashMap<>(); @@ -114,6 +137,4 @@ public DynamicConfigModel() { authImplMap.put("username_authFailureListener", UserNameBasedRateLimiter.class.getName()); } - - } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java index 2dce89ba7c..994989416b 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV6.java @@ -81,27 +81,32 @@ public class DynamicConfigModelV6 extends DynamicConfigModel { public DynamicConfigModelV6(ConfigV6 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; - this.opensearchSettings = opensearchSettings; + this.opensearchSettings = opensearchSettings; this.configPath = configPath; this.iab = iab; buildAAA(); } + @Override public SortedSet getRestAuthDomains() { return Collections.unmodifiableSortedSet(restAuthDomains); } + @Override public Set getRestAuthorizers() { return Collections.unmodifiableSet(restAuthorizers); } + @Override public boolean isAnonymousAuthenticationEnabled() { return config.dynamic.http.anonymous_auth_enabled; } + @Override public boolean isXffEnabled() { return config.dynamic.http.xff.enabled; } + @Override public String getInternalProxies() { return config.dynamic.http.xff.internalProxies; @@ -116,44 +121,57 @@ public String getRemoteIpHeader() { public boolean isRestAuthDisabled() { return config.dynamic.disable_rest_auth; } + @Override public boolean isInterTransportAuthDisabled() { return config.dynamic.disable_intertransport_auth; } + @Override public boolean isRespectRequestIndicesEnabled() { return config.dynamic.respect_request_indices_options; } + @Override public String getDashboardsServerUsername() { return config.dynamic.kibana.server_username; } + @Override public String getDashboardsOpenSearchRole() { return config.dynamic.kibana.opendistro_role; } + @Override public String getDashboardsIndexname() { return config.dynamic.kibana.index; } + @Override public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } + @Override public boolean isDashboardsPrivateTenantEnabled() { return config.dynamic.kibana.private_tenant_enabled; } + @Override - public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + public String getDashboardsDefaultTenant() { + return config.dynamic.kibana.default_tenant; + } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden || config.dynamic.kibana.do_not_fail_on_forbidden; } + @Override public boolean isMultiRolespanEnabled() { return config.dynamic.multi_rolespan_enabled; } + @Override public String getFilteredAliasMode() { return config.dynamic.filtered_alias_mode; @@ -208,26 +226,33 @@ private void buildAAA() { final boolean httpEnabled = enabled && ad.getValue().http_enabled; final boolean transportEnabled = enabled && ad.getValue().transport_enabled; - if (httpEnabled || transportEnabled) { try { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authzBackendClazz.equals("internal") - || authzBackendClazz.equals("intern")) { + if (authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authzBackendClazz.equals("internal") + || authzBackendClazz.equals("intern")) { authorizationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authorizationBackend = newInstance( - authzBackendClazz,"z", - Settings.builder() + authzBackendClazz, + "z", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), DynamicConfiguration.checkKeyFunction()).build(), configPath); - .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build(), configPath); + .put( + Settings.builder() + .loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } if (httpEnabled) { @@ -242,7 +267,7 @@ private void buildAAA() { destroyableComponents0.add((Destroyable) authorizationBackend); } } catch (final Exception e) { - log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(),e); + log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(), e); } } } @@ -258,31 +283,56 @@ private void buildAAA() { try { AuthenticationBackend authenticationBackend; final String authBackendClazz = ad.getValue().authentication_backend.type; - if(authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authBackendClazz.equals("internal") - || authBackendClazz.equals("intern")) { + if (authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authBackendClazz.equals("internal") + || authBackendClazz.equals("intern")) { authenticationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authenticationBackend = newInstance( - authBackendClazz,"c", - Settings.builder() + authBackendClazz, + "c", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), DynamicConfiguration.checkKeyFunction()).build() - .put(Settings.builder().loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build() + .put( + Settings.builder() + .loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } - String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default - HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", - Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), - .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() - - , configPath); - - final AuthDomain _ad = new AuthDomain(authenticationBackend, httpAuthenticator, - ad.getValue().http_authenticator.challenge, ad.getValue().order); + String httpAuthenticatorType = ad.getValue().http_authenticator.type; // no default + HTTPAuthenticator httpAuthenticator = httpAuthenticatorType == null + ? null + : (HTTPAuthenticator) newInstance( + httpAuthenticatorType, + "h", + Settings.builder() + .put(opensearchSettings) + // .putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), + // DynamicConfiguration.checkKeyFunction()).build(), + .put( + Settings.builder() + .loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON) + .build() + ) + .build() + + , + configPath + ); + + final AuthDomain _ad = new AuthDomain( + authenticationBackend, + httpAuthenticator, + ad.getValue().http_authenticator.challenge, + ad.getValue().order + ); if (httpEnabled && _ad.getHttpAuthenticator() != null) { restAuthDomains0.add(_ad); @@ -316,14 +366,19 @@ private void buildAAA() { destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - if(originalDestroyableComponents != null) { + if (originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } originalDestroyableComponents = null; - createAuthFailureListeners(ipAuthFailureListeners0, - authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); + createAuthFailureListeners( + ipAuthFailureListeners0, + authBackendFailureListeners0, + ipClientBlockRegistries0, + authBackendClientBlockRegistries0, + destroyableComponents0 + ); ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); @@ -346,8 +401,8 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti String clazz = clazzOrShortcut; - if(authImplMap.containsKey(clazz+"_"+type)) { - clazz = authImplMap.get(clazz+"_"+type); + if (authImplMap.containsKey(clazz + "_" + type)) { + clazz = authImplMap.get(clazz + "_" + type); } return ReflectionHelper.instantiateAAA(clazz, settings, configPath); @@ -362,15 +417,20 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final } } - private void createAuthFailureListeners(List ipAuthFailureListeners, - Multimap authBackendFailureListeners, List> ipClientBlockRegistries, - Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { + private void createAuthFailureListeners( + List ipAuthFailureListeners, + Multimap authBackendFailureListeners, + List> ipClientBlockRegistries, + Multimap> authBackendUserClientBlockRegistries, + List destroyableComponents0 + ) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { Settings entrySettings = Settings.builder() - .put(opensearchSettings) - .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); + .put(opensearchSettings) + .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()) + .build(); String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; @@ -387,8 +447,13 @@ private void createAuthFailureListeners(List ipAuthFailureL ipClientBlockRegistries.add(clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } @@ -405,8 +470,13 @@ private void createAuthFailureListeners(List ipAuthFailureL authBackendUserClientBlockRegistries.put(authenticationBackend, clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java index 8e92675dcc..f6bbcc2161 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModelV7.java @@ -81,27 +81,32 @@ public class DynamicConfigModelV7 extends DynamicConfigModel { public DynamicConfigModelV7(ConfigV7 config, Settings opensearchSettings, Path configPath, InternalAuthenticationBackend iab) { super(); this.config = config; - this.opensearchSettings = opensearchSettings; + this.opensearchSettings = opensearchSettings; this.configPath = configPath; this.iab = iab; buildAAA(); } + @Override public SortedSet getRestAuthDomains() { return Collections.unmodifiableSortedSet(restAuthDomains); } + @Override public Set getRestAuthorizers() { return Collections.unmodifiableSet(restAuthorizers); } + @Override public boolean isAnonymousAuthenticationEnabled() { return config.dynamic.http.anonymous_auth_enabled; } + @Override public boolean isXffEnabled() { return config.dynamic.http.xff.enabled; } + @Override public String getInternalProxies() { return config.dynamic.http.xff.internalProxies; @@ -116,44 +121,57 @@ public String getRemoteIpHeader() { public boolean isRestAuthDisabled() { return config.dynamic.disable_rest_auth; } + @Override public boolean isInterTransportAuthDisabled() { return config.dynamic.disable_intertransport_auth; } + @Override public boolean isRespectRequestIndicesEnabled() { return config.dynamic.respect_request_indices_options; } + @Override public String getDashboardsServerUsername() { return config.dynamic.kibana.server_username; } + @Override public String getDashboardsOpenSearchRole() { return config.dynamic.kibana.opendistro_role; } + @Override public String getDashboardsIndexname() { return config.dynamic.kibana.index; } + @Override public boolean isDashboardsMultitenancyEnabled() { return config.dynamic.kibana.multitenancy_enabled; } + @Override public boolean isDashboardsPrivateTenantEnabled() { return config.dynamic.kibana.private_tenant_enabled; } + @Override - public String getDashboardsDefaultTenant() { return config.dynamic.kibana.default_tenant; } + public String getDashboardsDefaultTenant() { + return config.dynamic.kibana.default_tenant; + } + @Override public boolean isDnfofEnabled() { return config.dynamic.do_not_fail_on_forbidden; } + @Override public boolean isMultiRolespanEnabled() { return config.dynamic.multi_rolespan_enabled; } + @Override public String getFilteredAliasMode() { return config.dynamic.filtered_alias_mode; @@ -189,7 +207,6 @@ public Multimap> getAuthBackendClientBlockRe return Multimaps.unmodifiableMultimap(authBackendClientBlockRegistries); } - private void buildAAA() { final SortedSet restAuthDomains0 = new TreeSet<>(); @@ -208,26 +225,33 @@ private void buildAAA() { final boolean httpEnabled = ad.getValue().http_enabled; final boolean transportEnabled = ad.getValue().transport_enabled; - if (httpEnabled || transportEnabled) { try { final String authzBackendClazz = ad.getValue().authorization_backend.type; final AuthorizationBackend authorizationBackend; - if(authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authzBackendClazz.equals("internal") - || authzBackendClazz.equals("intern")) { + if (authzBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authzBackendClazz.equals("internal") + || authzBackendClazz.equals("intern")) { authorizationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authorizationBackend = newInstance( - authzBackendClazz,"z", - Settings.builder() + authzBackendClazz, + "z", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), DynamicConfiguration.checkKeyFunction()).build(), configPath); - .put(Settings.builder().loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authorization_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build(), configPath); + .put( + Settings.builder() + .loadFromSource(ad.getValue().authorization_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } if (httpEnabled) { @@ -242,7 +266,7 @@ private void buildAAA() { destroyableComponents0.add((Destroyable) authorizationBackend); } } catch (final Exception e) { - log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(),e); + log.error("Unable to initialize AuthorizationBackend {} due to {}", ad, e.toString(), e); } } } @@ -257,31 +281,56 @@ private void buildAAA() { try { AuthenticationBackend authenticationBackend; final String authBackendClazz = ad.getValue().authentication_backend.type; - if(authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) //NOSONAR - || authBackendClazz.equals("internal") - || authBackendClazz.equals("intern")) { + if (authBackendClazz.equals(InternalAuthenticationBackend.class.getName()) // NOSONAR + || authBackendClazz.equals("internal") + || authBackendClazz.equals("intern")) { authenticationBackend = iab; ReflectionHelper.addLoadedModule(InternalAuthenticationBackend.class); } else { authenticationBackend = newInstance( - authBackendClazz,"c", - Settings.builder() + authBackendClazz, + "c", + Settings.builder() .put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), DynamicConfiguration.checkKeyFunction()).build() - .put(Settings.builder().loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON).build()).build() - , configPath); + // .putProperties(ads.getAsStringMap(DotPath.of("authentication_backend.config")), + // DynamicConfiguration.checkKeyFunction()).build() + .put( + Settings.builder() + .loadFromSource(ad.getValue().authentication_backend.configAsJson(), XContentType.JSON) + .build() + ) + .build(), + configPath + ); } - String httpAuthenticatorType = ad.getValue().http_authenticator.type; //no default - HTTPAuthenticator httpAuthenticator = httpAuthenticatorType==null?null: (HTTPAuthenticator) newInstance(httpAuthenticatorType,"h", - Settings.builder().put(opensearchSettings) - //.putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), DynamicConfiguration.checkKeyFunction()).build(), - .put(Settings.builder().loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON).build()).build() - - , configPath); - - final AuthDomain _ad = new AuthDomain(authenticationBackend, httpAuthenticator, - ad.getValue().http_authenticator.challenge, ad.getValue().order); + String httpAuthenticatorType = ad.getValue().http_authenticator.type; // no default + HTTPAuthenticator httpAuthenticator = httpAuthenticatorType == null + ? null + : (HTTPAuthenticator) newInstance( + httpAuthenticatorType, + "h", + Settings.builder() + .put(opensearchSettings) + // .putProperties(ads.getAsStringMap(DotPath.of("http_authenticator.config")), + // DynamicConfiguration.checkKeyFunction()).build(), + .put( + Settings.builder() + .loadFromSource(ad.getValue().http_authenticator.configAsJson(), XContentType.JSON) + .build() + ) + .build() + + , + configPath + ); + + final AuthDomain _ad = new AuthDomain( + authenticationBackend, + httpAuthenticator, + ad.getValue().http_authenticator.challenge, + ad.getValue().order + ); if (httpEnabled && _ad.getHttpAuthenticator() != null) { restAuthDomains0.add(_ad); @@ -315,14 +364,19 @@ private void buildAAA() { destroyableComponents = Collections.unmodifiableList(destroyableComponents0); - if(originalDestroyableComponents != null) { + if (originalDestroyableComponents != null) { destroyDestroyables(originalDestroyableComponents); } originalDestroyableComponents = null; - createAuthFailureListeners(ipAuthFailureListeners0, - authBackendFailureListeners0, ipClientBlockRegistries0, authBackendClientBlockRegistries0, destroyableComponents0); + createAuthFailureListeners( + ipAuthFailureListeners0, + authBackendFailureListeners0, + ipClientBlockRegistries0, + authBackendClientBlockRegistries0, + destroyableComponents0 + ); ipAuthFailureListeners = Collections.unmodifiableList(ipAuthFailureListeners0); ipClientBlockRegistries = Collections.unmodifiableList(ipClientBlockRegistries0); @@ -345,8 +399,8 @@ private T newInstance(final String clazzOrShortcut, String type, final Setti String clazz = clazzOrShortcut; - if(authImplMap.containsKey(clazz+"_"+type)) { - clazz = authImplMap.get(clazz+"_"+type); + if (authImplMap.containsKey(clazz + "_" + type)) { + clazz = authImplMap.get(clazz + "_" + type); } return ReflectionHelper.instantiateAAA(clazz, settings, configPath); @@ -361,15 +415,20 @@ private String translateShortcutToClassName(final String clazzOrShortcut, final } } - private void createAuthFailureListeners(List ipAuthFailureListeners, - Multimap authBackendFailureListeners, List> ipClientBlockRegistries, - Multimap> authBackendUserClientBlockRegistries, List destroyableComponents0) { + private void createAuthFailureListeners( + List ipAuthFailureListeners, + Multimap authBackendFailureListeners, + List> ipClientBlockRegistries, + Multimap> authBackendUserClientBlockRegistries, + List destroyableComponents0 + ) { for (Entry entry : config.dynamic.auth_failure_listeners.getListeners().entrySet()) { Settings entrySettings = Settings.builder() - .put(opensearchSettings) - .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()).build(); + .put(opensearchSettings) + .put(Settings.builder().loadFromSource(entry.getValue().asJson(), XContentType.JSON).build()) + .build(); String type = entry.getValue().type; String authenticationBackend = entry.getValue().authentication_backend; @@ -386,8 +445,13 @@ private void createAuthFailureListeners(List ipAuthFailureL ipClientBlockRegistries.add(clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } @@ -404,8 +468,13 @@ private void createAuthFailureListeners(List ipAuthFailureL authBackendUserClientBlockRegistries.put(authenticationBackend, clientBlockRegistry); } else { - log.error("Illegal ClientIdType for AuthFailureListener" + entry.getKey() + ": " - + ((ClientBlockRegistry) authFailureListener).getClientIdType() + "; must be InetAddress."); + log.error( + "Illegal ClientIdType for AuthFailureListener" + + entry.getKey() + + ": " + + ((ClientBlockRegistry) authFailureListener).getClientIdType() + + "; must be InetAddress." + ); } } } diff --git a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java index 8870cb3aad..aa22e8729f 100644 --- a/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java +++ b/src/main/java/org/opensearch/security/securityconf/EvaluatedDlsFlsConfig.java @@ -21,14 +21,21 @@ import org.opensearch.security.support.WildcardMatcher; public class EvaluatedDlsFlsConfig { - public static EvaluatedDlsFlsConfig EMPTY = new EvaluatedDlsFlsConfig(Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); + public static EvaluatedDlsFlsConfig EMPTY = new EvaluatedDlsFlsConfig( + Collections.emptyMap(), + Collections.emptyMap(), + Collections.emptyMap() + ); private final Map> dlsQueriesByIndex; private final Map> flsByIndex; private final Map> fieldMaskingByIndex; - public EvaluatedDlsFlsConfig(Map> dlsQueriesByIndex, Map> flsByIndex, - Map> fieldMaskingByIndex) { + public EvaluatedDlsFlsConfig( + Map> dlsQueriesByIndex, + Map> flsByIndex, + Map> fieldMaskingByIndex + ) { this.dlsQueriesByIndex = Collections.unmodifiableMap(dlsQueriesByIndex); this.flsByIndex = Collections.unmodifiableMap(flsByIndex); this.fieldMaskingByIndex = Collections.unmodifiableMap(fieldMaskingByIndex); @@ -88,8 +95,11 @@ public EvaluatedDlsFlsConfig filter(Resolved indices) { } else { Set allIndices = indices.getAllIndices(); - return new EvaluatedDlsFlsConfig(filter(dlsQueriesByIndex, allIndices), filter(flsByIndex, allIndices), - filter(fieldMaskingByIndex, allIndices)); + return new EvaluatedDlsFlsConfig( + filter(dlsQueriesByIndex, allIndices), + filter(flsByIndex, allIndices), + filter(fieldMaskingByIndex, allIndices) + ); } } @@ -119,8 +129,13 @@ private Map> filter(Map> map, Set getBackenRoles(String user); + public abstract Map getAttributes(String user); + public abstract String getDescription(String user); + public abstract String getHash(String user); + public abstract List getSecurityRoles(String user); } diff --git a/src/main/java/org/opensearch/security/securityconf/Migration.java b/src/main/java/org/opensearch/security/securityconf/Migration.java index ec6a5525b9..4eeaba5c68 100644 --- a/src/main/java/org/opensearch/security/securityconf/Migration.java +++ b/src/main/java/org/opensearch/security/securityconf/Migration.java @@ -53,10 +53,12 @@ import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.securityconf.impl.v7.TenantV7; - public class Migration { - public static Tuple,SecurityDynamicConfiguration> migrateRoles(SecurityDynamicConfiguration r6cs, SecurityDynamicConfiguration rms6) throws MigrationException { + public static Tuple, SecurityDynamicConfiguration> migrateRoles( + SecurityDynamicConfiguration r6cs, + SecurityDynamicConfiguration rms6 + ) throws MigrationException { final SecurityDynamicConfiguration r7 = SecurityDynamicConfiguration.empty(); r7.setCType(r6cs.getCType()); @@ -72,11 +74,11 @@ public static Tuple,SecurityDynamicConfigur Set dedupTenants = new HashSet<>(); - for(final Entry r6e: r6cs.getCEntries().entrySet()) { - final String roleName = r6e.getKey(); + for (final Entry r6e : r6cs.getCEntries().entrySet()) { + final String roleName = r6e.getKey(); final RoleV6 r6 = r6e.getValue(); - if(r6 == null) { + if (r6 == null) { RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was empty"); r7.putCEntry(roleName, noPermRole); @@ -85,18 +87,18 @@ public static Tuple,SecurityDynamicConfigur r7.putCEntry(roleName, new RoleV7(r6)); - for(Entry tenant: r6.getTenants().entrySet()) { + for (Entry tenant : r6.getTenants().entrySet()) { dedupTenants.add(tenant.getKey()); } } - if(rms6 != null) { - for(final Entry r6m: rms6.getCEntries().entrySet()) { - final String roleName = r6m.getKey(); - //final RoleMappingsV6 r6 = r6m.getValue(); + if (rms6 != null) { + for (final Entry r6m : rms6.getCEntries().entrySet()) { + final String roleName = r6m.getKey(); + // final RoleMappingsV6 r6 = r6m.getValue(); - if(!r7.exists(roleName)) { - //rolemapping but role does not exists + if (!r7.exists(roleName)) { + // rolemapping but role does not exists RoleV7 noPermRole = new RoleV7(); noPermRole.setDescription("Migrated from v6, was in rolemappings but no role existed"); r7.putCEntry(roleName, noPermRole); @@ -105,7 +107,7 @@ public static Tuple,SecurityDynamicConfigur } } - for(String tenantName: dedupTenants) { + for (String tenantName : dedupTenants) { TenantV7 entry = new TenantV7(); entry.setDescription("Migrated from v6"); t7.putCEntry(tenantName, entry); @@ -115,22 +117,25 @@ public static Tuple,SecurityDynamicConfigur } - public static SecurityDynamicConfiguration migrateConfig(SecurityDynamicConfiguration r6cs) throws MigrationException { + public static SecurityDynamicConfiguration migrateConfig(SecurityDynamicConfiguration r6cs) + throws MigrationException { final SecurityDynamicConfiguration c7 = SecurityDynamicConfiguration.empty(); c7.setCType(r6cs.getCType()); c7.set_meta(new Meta()); c7.get_meta().setConfig_version(2); c7.get_meta().setType("config"); - if(r6cs.getCEntries().size() != 1) { - throw new MigrationException("Unable to migrate config because expected size was 1 but actual size is "+r6cs.getCEntries().size()); + if (r6cs.getCEntries().size() != 1) { + throw new MigrationException( + "Unable to migrate config because expected size was 1 but actual size is " + r6cs.getCEntries().size() + ); } - if(r6cs.getCEntries().get("opendistro_security") == null) { + if (r6cs.getCEntries().get("opendistro_security") == null) { throw new MigrationException("Unable to migrate config because 'opendistro_security' key not found"); } - for(final Entry r6c: r6cs.getCEntries().entrySet()) { + for (final Entry r6c : r6cs.getCEntries().entrySet()) { c7.putCEntry("config", new ConfigV7(r6c.getValue())); } return c7; @@ -143,54 +148,60 @@ public static SecurityDynamicConfiguration migrateNodesDn(SecurityDynam migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("nodesdn"); - for(final Entry entry: nodesDn.getCEntries().entrySet()) { + for (final Entry entry : nodesDn.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new NodesDn(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateWhitelistingSetting(SecurityDynamicConfiguration whitelistingSetting) { + public static SecurityDynamicConfiguration migrateWhitelistingSetting( + SecurityDynamicConfiguration whitelistingSetting + ) { final SecurityDynamicConfiguration migrated = SecurityDynamicConfiguration.empty(); migrated.setCType(whitelistingSetting.getCType()); migrated.set_meta(new Meta()); migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("whitelist"); - for(final Entry entry: whitelistingSetting.getCEntries().entrySet()) { + for (final Entry entry : whitelistingSetting.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new WhitelistingSettings(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateAllowlistingSetting(SecurityDynamicConfiguration allowlistingSetting) { + public static SecurityDynamicConfiguration migrateAllowlistingSetting( + SecurityDynamicConfiguration allowlistingSetting + ) { final SecurityDynamicConfiguration migrated = SecurityDynamicConfiguration.empty(); migrated.setCType(allowlistingSetting.getCType()); migrated.set_meta(new Meta()); migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("whitelist"); - for(final Entry entry: allowlistingSetting.getCEntries().entrySet()) { + for (final Entry entry : allowlistingSetting.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), new AllowlistingSettings(entry.getValue())); } return migrated; } - public static SecurityDynamicConfiguration migrateInternalUsers(SecurityDynamicConfiguration r6is) throws MigrationException { + public static SecurityDynamicConfiguration migrateInternalUsers(SecurityDynamicConfiguration r6is) + throws MigrationException { final SecurityDynamicConfiguration i7 = SecurityDynamicConfiguration.empty(); i7.setCType(r6is.getCType()); i7.set_meta(new Meta()); i7.get_meta().setConfig_version(2); i7.get_meta().setType("internalusers"); - for(final Entry r6i: r6is.getCEntries().entrySet()) { - final String username = !Strings.isNullOrEmpty(r6i.getValue().getUsername())?r6i.getValue().getUsername():r6i.getKey(); + for (final Entry r6i : r6is.getCEntries().entrySet()) { + final String username = !Strings.isNullOrEmpty(r6i.getValue().getUsername()) ? r6i.getValue().getUsername() : r6i.getKey(); i7.putCEntry(username, new InternalUserV7(r6i.getValue())); } return i7; } - public static SecurityDynamicConfiguration migrateActionGroups(SecurityDynamicConfiguration r6as) throws MigrationException { + public static SecurityDynamicConfiguration migrateActionGroups(SecurityDynamicConfiguration r6as) + throws MigrationException { final SecurityDynamicConfiguration a7 = SecurityDynamicConfiguration.empty(); a7.setCType(r6as.getCType()); @@ -198,27 +209,28 @@ public static SecurityDynamicConfiguration migrateActionGroups( a7.get_meta().setConfig_version(2); a7.get_meta().setType("actiongroups"); - if(r6as.getImplementingClass().isAssignableFrom(List.class)) { - for(final Entry r6a: r6as.getCEntries().entrySet()) { + if (r6as.getImplementingClass().isAssignableFrom(List.class)) { + for (final Entry r6a : r6as.getCEntries().entrySet()) { a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (List) r6a.getValue())); } } else { - for(final Entry r6a: r6as.getCEntries().entrySet()) { - a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (ActionGroupsV6)r6a.getValue())); + for (final Entry r6a : r6as.getCEntries().entrySet()) { + a7.putCEntry(r6a.getKey(), new ActionGroupsV7(r6a.getKey(), (ActionGroupsV6) r6a.getValue())); } } return a7; } - public static SecurityDynamicConfiguration migrateRoleMappings(SecurityDynamicConfiguration r6rms) throws MigrationException { + public static SecurityDynamicConfiguration migrateRoleMappings(SecurityDynamicConfiguration r6rms) + throws MigrationException { final SecurityDynamicConfiguration rms7 = SecurityDynamicConfiguration.empty(); rms7.setCType(r6rms.getCType()); rms7.set_meta(new Meta()); rms7.get_meta().setConfig_version(2); rms7.get_meta().setType("rolesmapping"); - for(final Entry r6m: r6rms.getCEntries().entrySet()) { + for (final Entry r6m : r6rms.getCEntries().entrySet()) { rms7.putCEntry(r6m.getKey(), new RoleMappingsV7(r6m.getValue())); } @@ -232,7 +244,7 @@ public static SecurityDynamicConfiguration migrateAudit(SecurityDyn migrated.get_meta().setConfig_version(2); migrated.get_meta().setType("audit"); - for(final Entry entry: audit.getCEntries().entrySet()) { + for (final Entry entry : audit.getCEntries().entrySet()) { migrated.putCEntry(entry.getKey(), entry.getValue()); } return migrated; diff --git a/src/main/java/org/opensearch/security/securityconf/RoleMappings.java b/src/main/java/org/opensearch/security/securityconf/RoleMappings.java index 01d686733e..ff4153898b 100644 --- a/src/main/java/org/opensearch/security/securityconf/RoleMappings.java +++ b/src/main/java/org/opensearch/security/securityconf/RoleMappings.java @@ -16,8 +16,8 @@ public class RoleMappings { - private List hosts= Collections.emptyList(); - private List users= Collections.emptyList(); + private List hosts = Collections.emptyList(); + private List users = Collections.emptyList(); public void setHosts(List hosts) { this.hosts = hosts; diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index de7afbc27b..c52a3c1bad 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -43,15 +43,45 @@ public interface SecurityRoles { Set getRoleNames(); - Set reduce(Resolved requestedResolved, User user, String[] strings, IndexNameExpressionResolver resolver, ClusterService clusterService); + Set reduce( + Resolved requestedResolved, + User user, + String[] strings, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - boolean impliesTypePermGlobal(Resolved requestedResolved, User user, String[] allIndexPermsRequiredA, IndexNameExpressionResolver resolver, ClusterService clusterService); + boolean impliesTypePermGlobal( + Resolved requestedResolved, + User user, + String[] allIndexPermsRequiredA, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - boolean get(Resolved requestedResolved, User user, String[] allIndexPermsRequiredA, IndexNameExpressionResolver resolver, ClusterService clusterService); + boolean get( + Resolved requestedResolved, + User user, + String[] allIndexPermsRequiredA, + IndexNameExpressionResolver resolver, + ClusterService clusterService + ); - EvaluatedDlsFlsConfig getDlsFls(User user, boolean dfmEmptyOverwritesAll, IndexNameExpressionResolver resolver, ClusterService clusterService, NamedXContentRegistry namedXContentRegistry); + EvaluatedDlsFlsConfig getDlsFls( + User user, + boolean dfmEmptyOverwritesAll, + IndexNameExpressionResolver resolver, + ClusterService clusterService, + NamedXContentRegistry namedXContentRegistry + ); - Set getAllPermittedIndicesForDashboards(Resolved resolved, User user, String[] actions, IndexNameExpressionResolver resolver, ClusterService cs); + Set getAllPermittedIndicesForDashboards( + Resolved resolved, + User user, + String[] actions, + IndexNameExpressionResolver resolver, + ClusterService cs + ); SecurityRoles filter(Set roles); diff --git a/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java b/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java index 131c43b50b..e2c86009d1 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/AllowlistingSettings.java @@ -48,7 +48,7 @@ public void setEnabled(boolean enabled) { } public Map> getRequests() { - return this.requests == null ? Collections.emptyMap(): this.requests; + return this.requests == null ? Collections.emptyMap() : this.requests; } public void setRequests(Map> requests) { @@ -60,7 +60,6 @@ public String toString() { return "AllowlistingSetting [enabled=" + enabled + ", requests=" + requests + ']'; } - /** * Helper function to check if a rest request is allowlisted, by checking if the path is allowlisted, * and then if the Http method is allowlisted. @@ -76,26 +75,26 @@ public String toString() { * GET /_cluster/settings - OK * GET /_cluster/settings/ - OK */ - private boolean requestIsAllowlisted(RestRequest request){ + private boolean requestIsAllowlisted(RestRequest request) { - //ALSO ALLOWS REQUEST TO HAVE TRAILING '/' - //pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes - //pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ + // ALSO ALLOWS REQUEST TO HAVE TRAILING '/' + // pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes + // pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ String path = request.path(); String pathWithoutTrailingSlash; String pathWithTrailingSlash; - //first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash + // first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash pathWithoutTrailingSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; pathWithTrailingSlash = pathWithoutTrailingSlash + '/'; - //check if pathWithoutTrailingSlash is allowlisted - if(requests.containsKey(pathWithoutTrailingSlash) && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithoutTrailingSlash is allowlisted + if (requests.containsKey(pathWithoutTrailingSlash) + && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; - //check if pathWithTrailingSlash is allowlisted - if(requests.containsKey(pathWithTrailingSlash) && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithTrailingSlash is allowlisted + if (requests.containsKey(pathWithTrailingSlash) + && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; return false; } @@ -108,15 +107,19 @@ private boolean requestIsAllowlisted(RestRequest request){ * then all PUT /_opendistro/_security/api/rolesmapping/{resource_name} work. * Currently, each resource_name has to be allowlisted separately */ - public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, - NodeClient client) throws IOException { + public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, NodeClient client) throws IOException { // if allowlisting is enabled but the request is not allowlisted, then return false, otherwise true. - if (this.enabled && !requestIsAllowlisted(request)){ - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, channel.newErrorBuilder().startObject() - .field("error", request.method() + " " + request.path() + " API not allowlisted") - .field("status", RestStatus.FORBIDDEN) - .endObject() - )); + if (this.enabled && !requestIsAllowlisted(request)) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + channel.newErrorBuilder() + .startObject() + .field("error", request.method() + " " + request.path() + " API not allowlisted") + .field("status", RestStatus.FORBIDDEN) + .endObject() + ) + ); return false; } return true; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/CType.java b/src/main/java/org/opensearch/security/securityconf/impl/CType.java index 5d9f1f307b..4e5e2de496 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/CType.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/CType.java @@ -50,10 +50,8 @@ public enum CType { - INTERNALUSERS(toMap(1, InternalUserV6.class, 2, - InternalUserV7.class)), - ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, - ActionGroupsV7.class)), + INTERNALUSERS(toMap(1, InternalUserV6.class, 2, InternalUserV7.class)), + ACTIONGROUPS(toMap(0, List.class, 1, ActionGroupsV6.class, 2, ActionGroupsV7.class)), CONFIG(toMap(1, ConfigV6.class, 2, ConfigV7.class)), ROLES(toMap(1, RoleV6.class, 2, RoleV7.class)), ROLESMAPPING(toMap(1, RoleMappingsV6.class, 2, RoleMappingsV7.class)), diff --git a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java index 1e9060efa1..2fa4ced06a 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/Meta.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/Meta.java @@ -31,7 +31,6 @@ public class Meta { - private String type; private int config_version; @@ -45,9 +44,11 @@ public void setType(String type) { this.type = type; cType = CType.fromString(type); } + public int getConfig_version() { return config_version; } + public void setConfig_version(int config_version) { this.config_version = config_version; } @@ -62,5 +63,4 @@ public String toString() { return "Meta [type=" + type + ", config_version=" + config_version + ", cType=" + cType + "]"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java index c282f439e8..285dec5d10 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/SecurityDynamicConfiguration.java @@ -53,12 +53,13 @@ public class SecurityDynamicConfiguration implements ToXContent { - private static final TypeReference> typeRefMSO = new TypeReference>() {}; + private static final TypeReference> typeRefMSO = new TypeReference>() { + }; @JsonIgnore private final Map centries = new HashMap<>(); - private long seqNo= -1; - private long primaryTerm= -1; + private long seqNo = -1; + private long primaryTerm = -1; private CType ctype; private int version = -1; @@ -66,21 +67,36 @@ public static SecurityDynamicConfiguration empty() { return new SecurityDynamicConfiguration(); } - public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { + public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm) + throws IOException { return fromJson(json, ctype, version, seqNo, primaryTerm, false); } - public static SecurityDynamicConfiguration fromJson(String json, CType ctype, int version, long seqNo, long primaryTerm, boolean acceptInvalid) throws IOException { + public static SecurityDynamicConfiguration fromJson( + String json, + CType ctype, + int version, + long seqNo, + long primaryTerm, + boolean acceptInvalid + ) throws IOException { SecurityDynamicConfiguration sdc = null; - if(ctype != null) { + if (ctype != null) { final Class implementationClass = ctype.getImplementationClass().get(version); - if(implementationClass == null) { - throw new IllegalArgumentException("No implementation class found for "+ctype+" and config version "+version); + if (implementationClass == null) { + throw new IllegalArgumentException("No implementation class found for " + ctype + " and config version " + version); } - if(acceptInvalid && version < 2) { - sdc = NonValidatingObjectMapper.readValue(json, NonValidatingObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass)); + if (acceptInvalid && version < 2) { + sdc = NonValidatingObjectMapper.readValue( + json, + NonValidatingObjectMapper.getTypeFactory() + .constructParametricType(SecurityDynamicConfiguration.class, implementationClass) + ); } else { - sdc = DefaultObjectMapper.readValue(json, DefaultObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass)); + sdc = DefaultObjectMapper.readValue( + json, + DefaultObjectMapper.getTypeFactory().constructParametricType(SecurityDynamicConfiguration.class, implementationClass) + ); } validate(sdc, version, ctype); @@ -97,29 +113,32 @@ public static SecurityDynamicConfiguration fromJson(String json, CType ct } public static void validate(SecurityDynamicConfiguration sdc, int version, CType ctype) throws IOException { - if(version < 2 && sdc.get_meta() != null) { - throw new IOException("A version of "+version+" can not have a _meta key for "+ctype); + if (version < 2 && sdc.get_meta() != null) { + throw new IOException("A version of " + version + " can not have a _meta key for " + ctype); } - if(version >= 2 && sdc.get_meta() == null) { - throw new IOException("A version of "+version+" must have a _meta key for "+ctype); + if (version >= 2 && sdc.get_meta() == null) { + throw new IOException("A version of " + version + " must have a _meta key for " + ctype); } - if(version < 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("opendistro_security"))) { - throw new IOException("A version of "+version+" must have a single toplevel key named 'opendistro_security' for "+ctype); + if (version < 2 + && ctype == CType.CONFIG + && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("opendistro_security"))) { + throw new IOException("A version of " + version + " must have a single toplevel key named 'opendistro_security' for " + ctype); } - if(version >= 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("config"))) { - throw new IOException("A version of "+version+" must have a single toplevel key named 'config' for "+ctype); + if (version >= 2 && ctype == CType.CONFIG && (sdc.getCEntries().size() != 1 || !sdc.getCEntries().keySet().contains("config"))) { + throw new IOException("A version of " + version + " must have a single toplevel key named 'config' for " + ctype); } } - public static SecurityDynamicConfiguration fromNode(JsonNode json, CType ctype, int version, long seqNo, long primaryTerm) throws IOException { + public static SecurityDynamicConfiguration fromNode(JsonNode json, CType ctype, int version, long seqNo, long primaryTerm) + throws IOException { return fromJson(DefaultObjectMapper.writeValueAsString(json, false), ctype, version, seqNo, primaryTerm); } - //for Jackson + // for Jackson private SecurityDynamicConfiguration() { super(); } @@ -134,7 +153,6 @@ public void set_meta(Meta _meta) { this._meta = _meta; } - @JsonAnySetter void setCEntries(String key, T value) { putCEntry(key, value); @@ -147,8 +165,8 @@ public Map getCEntries() { @JsonIgnore public void removeHidden() { - for(Entry entry: new HashMap(centries).entrySet()) { - if(entry.getValue() instanceof Hideable && ((Hideable) entry.getValue()).isHidden()) { + for (Entry entry : new HashMap(centries).entrySet()) { + if (entry.getValue() instanceof Hideable && ((Hideable) entry.getValue()).isHidden()) { centries.remove(entry.getKey()); } } @@ -156,8 +174,8 @@ public void removeHidden() { @JsonIgnore public void removeStatic() { - for(Entry entry: new HashMap(centries).entrySet()) { - if(entry.getValue() instanceof StaticDefinable && ((StaticDefinable) entry.getValue()).isStatic()) { + for (Entry entry : new HashMap(centries).entrySet()) { + if (entry.getValue() instanceof StaticDefinable && ((StaticDefinable) entry.getValue()).isStatic()) { centries.remove(entry.getKey()); } } @@ -165,14 +183,13 @@ public void removeStatic() { @JsonIgnore public void clearHashes() { - for(Entry entry: centries.entrySet()) { - if(entry.getValue() instanceof Hashed) { - ((Hashed) entry.getValue()).clearHash(); + for (Entry entry : centries.entrySet()) { + if (entry.getValue() instanceof Hashed) { + ((Hashed) entry.getValue()).clearHash(); } } } - public void removeOthers(String key) { T tmp = this.centries.get(key); this.centries.clear(); @@ -206,8 +223,19 @@ public BytesReference toBytesReference() throws IOException { @Override public String toString() { - return "SecurityDynamicConfiguration [seqNo=" + seqNo + ", primaryTerm=" + primaryTerm + ", ctype=" + ctype + ", version=" + version + ", centries=" - + centries + ", getImplementingClass()=" + getImplementingClass() + "]"; + return "SecurityDynamicConfiguration [seqNo=" + + seqNo + + ", primaryTerm=" + + primaryTerm + + ", ctype=" + + ctype + + ", version=" + + version + + ", centries=" + + centries + + ", getImplementingClass()=" + + getImplementingClass() + + "]"; } @Override @@ -250,7 +278,7 @@ public int getVersion() { @JsonIgnore public Class getImplementingClass() { - return ctype==null?null:ctype.getImplementationClass().get(getVersion()); + return ctype == null ? null : ctype.getImplementationClass().get(getVersion()); } @JsonIgnore @@ -264,21 +292,21 @@ public SecurityDynamicConfiguration deepClone() { @JsonIgnore public void remove(String key) { - centries.remove(key); + centries.remove(key); } @SuppressWarnings({ "rawtypes", "unchecked" }) public boolean add(SecurityDynamicConfiguration other) { - if(other.ctype == null || !other.ctype.equals(this.ctype)) { + if (other.ctype == null || !other.ctype.equals(this.ctype)) { return false; } - if(other.getImplementingClass() == null || !other.getImplementingClass().equals(this.getImplementingClass())) { + if (other.getImplementingClass() == null || !other.getImplementingClass().equals(this.getImplementingClass())) { return false; } - if(other.version != this.version) { + if (other.version != this.version) { return false; } @@ -292,7 +320,7 @@ public boolean containsAny(SecurityDynamicConfiguration other) { return !Collections.disjoint(this.centries.keySet(), other.centries.keySet()); } - public boolean isHidden(String resourceName){ + public boolean isHidden(String resourceName) { final Object o = centries.get(resourceName); return o != null && o instanceof Hideable && ((Hideable) o).isHidden(); } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java b/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java index b6dfbaae4e..57405b24fe 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/WhitelistingSettings.java @@ -48,7 +48,7 @@ public void setEnabled(boolean enabled) { } public Map> getRequests() { - return this.requests == null ? Collections.emptyMap(): this.requests; + return this.requests == null ? Collections.emptyMap() : this.requests; } public void setRequests(Map> requests) { @@ -60,7 +60,6 @@ public String toString() { return "WhitelistingSetting [enabled=" + enabled + ", requests=" + requests + ']'; } - /** * Helper function to check if a rest request is whitelisted, by checking if the path is whitelisted, * and then if the Http method is whitelisted. @@ -76,26 +75,26 @@ public String toString() { * GET /_cluster/settings - OK * GET /_cluster/settings/ - OK */ - private boolean requestIsWhitelisted(RestRequest request){ + private boolean requestIsWhitelisted(RestRequest request) { - //ALSO ALLOWS REQUEST TO HAVE TRAILING '/' - //pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes - //pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ + // ALSO ALLOWS REQUEST TO HAVE TRAILING '/' + // pathWithoutTrailingSlash stores the endpoint path without extra '/'. eg: /_cat/nodes + // pathWithTrailingSlash stores the endpoint path with extra '/'. eg: /_cat/nodes/ String path = request.path(); String pathWithoutTrailingSlash; String pathWithTrailingSlash; - //first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash + // first obtain pathWithoutTrailingSlash, then add a '/' to it to get pathWithTrailingSlash pathWithoutTrailingSlash = path.endsWith("/") ? path.substring(0, path.length() - 1) : path; pathWithTrailingSlash = pathWithoutTrailingSlash + '/'; - //check if pathWithoutTrailingSlash is whitelisted - if(requests.containsKey(pathWithoutTrailingSlash) && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithoutTrailingSlash is whitelisted + if (requests.containsKey(pathWithoutTrailingSlash) + && requests.get(pathWithoutTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; - //check if pathWithTrailingSlash is whitelisted - if(requests.containsKey(pathWithTrailingSlash) && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) - return true; + // check if pathWithTrailingSlash is whitelisted + if (requests.containsKey(pathWithTrailingSlash) + && requests.get(pathWithTrailingSlash).contains(HttpRequestMethods.valueOf(request.method().toString()))) return true; return false; } @@ -109,15 +108,19 @@ private boolean requestIsWhitelisted(RestRequest request){ * Currently, each resource_name has to be whitelisted separately */ @Override - public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, - NodeClient client) throws IOException { + public boolean checkRequestIsAllowed(RestRequest request, RestChannel channel, NodeClient client) throws IOException { // if whitelisting is enabled but the request is not whitelisted, then return false, otherwise true. - if (this.enabled && !requestIsWhitelisted(request)){ - channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, channel.newErrorBuilder().startObject() - .field("error", request.method() + " " + request.path() + " API not whitelisted") - .field("status", RestStatus.FORBIDDEN) - .endObject() - )); + if (this.enabled && !requestIsWhitelisted(request)) { + channel.sendResponse( + new BytesRestResponse( + RestStatus.FORBIDDEN, + channel.newErrorBuilder() + .startObject() + .field("error", request.method() + " " + request.path() + " API not whitelisted") + .field("status", RestStatus.FORBIDDEN) + .endObject() + ) + ); return false; } return true; diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java index 99bef31505..45fced168c 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ActionGroupsV6.java @@ -35,7 +35,6 @@ public class ActionGroupsV6 implements Hideable { - private boolean readonly; private boolean hidden; private List permissions = Collections.emptyList(); @@ -48,28 +47,34 @@ public ActionGroupsV6() { public boolean isReserved() { return readonly; } + public boolean isReadonly() { return readonly; } + public void setReadonly(boolean readonly) { this.readonly = readonly; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getPermissions() { return permissions; } + public void setPermissions(List permissions) { this.permissions = permissions; } + @Override public String toString() { return "ActionGroups [readonly=" + readonly + ", hidden=" + hidden + ", permissions=" + permissions + "]"; } - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java index 7f90e386a8..c85e69fb0d 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/ConfigV6.java @@ -25,7 +25,6 @@ * GitHub history for details. */ - package org.opensearch.security.securityconf.impl.v6; import java.util.Collections; @@ -46,8 +45,6 @@ public class ConfigV6 { public Dynamic dynamic; - - @Override public String toString() { return "Config [dynamic=" + dynamic + "]"; @@ -55,7 +52,6 @@ public String toString() { public static class Dynamic { - public String filtered_alias_mode = "warn"; public boolean disable_rest_auth; public boolean disable_intertransport_auth; @@ -74,8 +70,17 @@ public static class Dynamic { @Override public String toString() { - return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" - + authz + "]"; + return "Dynamic [filtered_alias_mode=" + + filtered_alias_mode + + ", kibana=" + + kibana + + ", http=" + + http + + ", authc=" + + authc + + ", authz=" + + authz + + "]"; } } @@ -91,25 +96,33 @@ public static class Kibana { public String opendistro_role = null; public String index = ".kibana"; public boolean do_not_fail_on_forbidden; + @Override public String toString() { - return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", server_username=" + server_username + ", opendistro_role=" + opendistro_role - + ", index=" + index + ", do_not_fail_on_forbidden=" + do_not_fail_on_forbidden + "]"; + return "Kibana [multitenancy_enabled=" + + multitenancy_enabled + + ", server_username=" + + server_username + + ", opendistro_role=" + + opendistro_role + + ", index=" + + index + + ", do_not_fail_on_forbidden=" + + do_not_fail_on_forbidden + + "]"; } - - } public static class Http { public boolean anonymous_auth_enabled = false; public Xff xff = new Xff(); + @Override public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - } public static class AuthFailureListeners { @@ -126,7 +139,6 @@ public Map getListeners() { return listeners; } - } public static class AuthFailureListener { @@ -156,23 +168,33 @@ public static class Xff { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean enabled = true; public String internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}").toString(); - public String remoteIpHeader="X-Forwarded-For"; - public String proxiesHeader="X-Forwarded-By"; + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ).toString(); + public String remoteIpHeader = "X-Forwarded-For"; + public String proxiesHeader = "X-Forwarded-By"; public String trustedProxies; + @Override public String toString() { - return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader + ", proxiesHeader=" - + proxiesHeader + ", trustedProxies=" + trustedProxies + "]"; + return "Xff [enabled=" + + enabled + + ", internalProxies=" + + internalProxies + + ", remoteIpHeader=" + + remoteIpHeader + + ", proxiesHeader=" + + proxiesHeader + + ", trustedProxies=" + + trustedProxies + + "]"; } - } public static class Authc { @@ -195,26 +217,36 @@ public String toString() { return "Authc [domains=" + domains + "]"; } - } public static class AuthcDomain { @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean http_enabled= true; + public boolean http_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean transport_enabled= true; + public boolean transport_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean enabled= true; + public boolean enabled = true; public int order = 0; public HttpAuthenticator http_authenticator = new HttpAuthenticator(); public AuthcBackend authentication_backend = new AuthcBackend(); + @Override public String toString() { - return "AuthcDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", order=" - + order + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + "]"; + return "AuthcDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", enabled=" + + enabled + + ", order=" + + order + + ", http_authenticator=" + + http_authenticator + + ", authentication_backend=" + + authentication_backend + + "]"; } - } public static class HttpAuthenticator { @@ -237,7 +269,6 @@ public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - } public static class AuthzBackend { @@ -258,7 +289,6 @@ public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - } public static class AuthcBackend { @@ -279,7 +309,6 @@ public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - } public static class Authz { @@ -301,7 +330,6 @@ public String toString() { return "Authz [domains=" + domains + "]"; } - } public static class AuthzDomain { @@ -312,12 +340,20 @@ public static class AuthzDomain { @JsonInclude(JsonInclude.Include.NON_NULL) public boolean enabled = true; public AuthzBackend authorization_backend = new AuthzBackend(); + @Override public String toString() { - return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", enabled=" + enabled + ", authorization_backend=" + authorization_backend + "]"; + return "AuthzDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", enabled=" + + enabled + + ", authorization_backend=" + + authorization_backend + + "]"; } - } } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java index f650101b64..0db727ad99 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/InternalUserV6.java @@ -38,91 +38,111 @@ public class InternalUserV6 implements Hideable, Hashed { - private String hash; - private boolean readonly; - private boolean hidden; - private List roles = Collections.emptyList(); - private Map attributes = Collections.emptyMap(); - private String username; - - - - public InternalUserV6(String hash, boolean readonly, boolean hidden, List roles, Map attributes, String username) { - super(); - this.hash = hash; - this.readonly = readonly; - this.hidden = hidden; - this.roles = roles; - this.attributes = attributes; - this.username = username; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public InternalUserV6() { - super(); - //default constructor - } - - public String getHash() { - return hash; - } - public void setHash(String hash) { - this.hash = hash; - } - - public void setPassword(String password){ - // no-op setter. Due to a bug in 6.x, empty "password" may be saved to the internalusers doc. Ignore it. - } - - public boolean isReadonly() { - return readonly; - } - public void setReadonly(boolean readonly) { - this.readonly = readonly; - } - public boolean isHidden() { - return hidden; - } - public void setHidden(boolean hidden) { - this.hidden = hidden; - } - public List getRoles() { - return roles; - } - public void setRoles(List roles) { - this.roles = roles; - } - public Map getAttributes() { - return attributes; - } - public void setAttributes(Map attributes) { - this.attributes = attributes; - } - - @Override - public String toString() { - return "SgInternalUser [hash=" + hash + ", readonly=" + readonly + ", hidden=" + hidden + ", roles=" + roles + ", attributes=" - + attributes + "]"; - } - - @JsonIgnore - public boolean isReserved() { - return readonly; - } - - @Override - @JsonIgnore - public void clearHash() { - hash = ""; - } + private String hash; + private boolean readonly; + private boolean hidden; + private List roles = Collections.emptyList(); + private Map attributes = Collections.emptyMap(); + private String username; + + public InternalUserV6( + String hash, + boolean readonly, + boolean hidden, + List roles, + Map attributes, + String username + ) { + super(); + this.hash = hash; + this.readonly = readonly; + this.hidden = hidden; + this.roles = roles; + this.attributes = attributes; + this.username = username; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public InternalUserV6() { + super(); + // default constructor + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setPassword(String password) { + // no-op setter. Due to a bug in 6.x, empty "password" may be saved to the internalusers doc. Ignore it. + } + + public boolean isReadonly() { + return readonly; + } + + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + public boolean isHidden() { + return hidden; + } + + public void setHidden(boolean hidden) { + this.hidden = hidden; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + @Override + public String toString() { + return "SgInternalUser [hash=" + + hash + + ", readonly=" + + readonly + + ", hidden=" + + hidden + + ", roles=" + + roles + + ", attributes=" + + attributes + + "]"; + } + @JsonIgnore + public boolean isReserved() { + return readonly; + } + @Override + @JsonIgnore + public void clearHash() { + hash = ""; } + +} diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java index bb10fd8812..f78c2f4305 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleMappingsV6.java @@ -41,10 +41,7 @@ public class RoleMappingsV6 extends RoleMappings implements Hideable { private boolean readonly; private boolean hidden; private List backendroles = Collections.emptyList(); - private List andBackendroles= Collections.emptyList(); - - - + private List andBackendroles = Collections.emptyList(); public RoleMappingsV6() { super(); @@ -53,34 +50,51 @@ public RoleMappingsV6() { public boolean isReadonly() { return readonly; } + public void setReadonly(boolean readonly) { this.readonly = readonly; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getBackendroles() { return backendroles; } + public void setBackendroles(List backendroles) { this.backendroles = backendroles; } - @JsonProperty(value="and_backendroles") + @JsonProperty(value = "and_backendroles") public List getAndBackendroles() { return andBackendroles; } + public void setAndBackendroles(List andBackendroles) { this.andBackendroles = andBackendroles; } @Override public String toString() { - return "RoleMappings [readonly=" + readonly + ", hidden=" + hidden + ", backendroles=" + backendroles + ", hosts=" + getHosts() + ", users=" - + getUsers() + ", andBackendroles=" + andBackendroles + "]"; + return "RoleMappings [readonly=" + + readonly + + ", hidden=" + + hidden + + ", backendroles=" + + backendroles + + ", hosts=" + + getHosts() + + ", users=" + + getUsers() + + ", andBackendroles=" + + andBackendroles + + "]"; } @JsonIgnore diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java index e8254d4530..8bc8b46249 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v6/RoleV6.java @@ -65,8 +65,6 @@ public Map> getTypes() { private List _fls_; private List _masked_fields_; - - public String get_dls_() { return _dls_; } @@ -84,8 +82,6 @@ public String toString() { return "Index [types=" + types + ", _dls_=" + _dls_ + ", _fls_=" + _fls_ + ", _masked_fields_=" + _masked_fields_ + "]"; } - - } public boolean isReadonly() { @@ -130,7 +126,17 @@ public void setIndices(Map indices) { @Override public String toString() { - return "Role [readonly=" + readonly + ", hidden=" + hidden + ", cluster=" + cluster + ", tenants=" + tenants + ", indices=" + indices + "]"; + return "Role [readonly=" + + readonly + + ", hidden=" + + hidden + + ", cluster=" + + cluster + + ", tenants=" + + tenants + + ", indices=" + + indices + + "]"; } @JsonIgnore diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java index f38978eec2..9ec9c25e5d 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ActionGroupsV7.java @@ -38,8 +38,6 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { - - private boolean reserved; private boolean hidden; @JsonProperty(value = "static") @@ -51,11 +49,12 @@ public class ActionGroupsV7 implements Hideable, StaticDefinable { public ActionGroupsV7() { super(); } + public ActionGroupsV7(String agName, ActionGroupsV6 ag6) { reserved = ag6.isReserved(); hidden = ag6.isHidden(); allowed_actions = ag6.getPermissions(); - type = agName.toLowerCase().contains("cluster")?"cluster":"index"; + type = agName.toLowerCase().contains("cluster") ? "cluster" : "index"; description = "Migrated from v6"; } @@ -64,53 +63,72 @@ public ActionGroupsV7(String key, List allowed_actions) { type = "unknown"; description = "Migrated from v6 (legacy)"; } + public String getType() { return type; } + public void setType(String type) { this.type = type; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } - public boolean isReserved() { return reserved; } + public void setReserved(boolean reserved) { this.reserved = reserved; } + public boolean isHidden() { return hidden; } + public void setHidden(boolean hidden) { this.hidden = hidden; } + public List getAllowed_actions() { return allowed_actions; } + public void setAllowed_actions(List allowed_actions) { this.allowed_actions = allowed_actions; } + @JsonProperty(value = "static") public boolean isStatic() { return _static; } + @JsonProperty(value = "static") public void setStatic(boolean _static) { this._static = _static; } + @Override public String toString() { - return "ActionGroupsV7 [reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", allowed_actions=" + allowed_actions - + ", type=" + type + ", description=" + description + "]"; + return "ActionGroupsV7 [reserved=" + + reserved + + ", hidden=" + + hidden + + ", _static=" + + _static + + ", allowed_actions=" + + allowed_actions + + ", type=" + + type + + ", description=" + + description + + "]"; } - - - } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java index 337dec8e31..87de6a31b0 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/ConfigV7.java @@ -85,20 +85,29 @@ public ConfigV7(ConfigV6 c6) { dynamic.authc = new Authc(); - dynamic.authc.domains.putAll(c6.dynamic.authc.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthcDomain(entry.getValue())))); + dynamic.authc.domains.putAll( + c6.dynamic.authc.getDomains() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthcDomain(entry.getValue()))) + ); dynamic.authz = new Authz(); - dynamic.authz.domains.putAll(c6.dynamic.authz.getDomains().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthzDomain(entry.getValue())))); + dynamic.authz.domains.putAll( + c6.dynamic.authz.getDomains() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthzDomain(entry.getValue()))) + ); dynamic.auth_failure_listeners = new AuthFailureListeners(); - dynamic.auth_failure_listeners.listeners.putAll(c6.dynamic.auth_failure_listeners.getListeners().entrySet().stream().collect(Collectors.toMap( - entry -> entry.getKey(), - entry -> new AuthFailureListener(entry.getValue())))); + dynamic.auth_failure_listeners.listeners.putAll( + c6.dynamic.auth_failure_listeners.getListeners() + .entrySet() + .stream() + .collect(Collectors.toMap(entry -> entry.getKey(), entry -> new AuthFailureListener(entry.getValue()))) + ); } @Override @@ -108,7 +117,6 @@ public String toString() { public static class Dynamic { - public String filtered_alias_mode = "warn"; public boolean disable_rest_auth; public boolean disable_intertransport_auth; @@ -128,8 +136,17 @@ public static class Dynamic { @Override public String toString() { - return "Dynamic [filtered_alias_mode=" + filtered_alias_mode + ", kibana=" + kibana + ", http=" + http + ", authc=" + authc + ", authz=" - + authz + "]"; + return "Dynamic [filtered_alias_mode=" + + filtered_alias_mode + + ", kibana=" + + kibana + + ", http=" + + http + + ", authc=" + + authc + + ", authz=" + + authz + + "]"; } } @@ -144,27 +161,35 @@ public static class Kibana { public String server_username = "kibanaserver"; public String opendistro_role = null; public String index = ".kibana"; + @Override public String toString() { - return "Kibana [multitenancy_enabled=" + multitenancy_enabled + ", private_tenant_enabled=" + - private_tenant_enabled + ", default_tenant=" + default_tenant + ", server_username=" + - server_username + ", opendistro_role=" + opendistro_role - + ", index=" + index + "]"; + return "Kibana [multitenancy_enabled=" + + multitenancy_enabled + + ", private_tenant_enabled=" + + private_tenant_enabled + + ", default_tenant=" + + default_tenant + + ", server_username=" + + server_username + + ", opendistro_role=" + + opendistro_role + + ", index=" + + index + + "]"; } - - } public static class Http { public boolean anonymous_auth_enabled = false; public Xff xff = new Xff(); + @Override public String toString() { return "Http [anonymous_auth_enabled=" + anonymous_auth_enabled + ", xff=" + xff + "]"; } - } public static class AuthFailureListeners { @@ -181,7 +206,6 @@ public Map getListeners() { return listeners; } - } public static class AuthFailureListener { @@ -193,8 +217,6 @@ public static class AuthFailureListener { public int max_blocked_clients = 100_000; public int max_tracked_clients = 100_000; - - public AuthFailureListener() { super(); } @@ -223,20 +245,21 @@ public String asJson() { public static class Xff { public boolean enabled = false; public String internalProxies = Pattern.compile( - "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + - "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + - "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + - "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}").toString(); - public String remoteIpHeader="X-Forwarded-For"; + "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" + + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" + + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" + + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}" + ).toString(); + public String remoteIpHeader = "X-Forwarded-For"; + @Override public String toString() { - return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader+"]"; + return "Xff [enabled=" + enabled + ", internalProxies=" + internalProxies + ", remoteIpHeader=" + remoteIpHeader + "]"; } - } public static class Authc { @@ -259,17 +282,15 @@ public String toString() { return "Authc [domains=" + domains + "]"; } - - } public static class AuthcDomain { @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean http_enabled= true; + public boolean http_enabled = true; @JsonInclude(JsonInclude.Include.NON_NULL) - public boolean transport_enabled= true; - //public boolean enabled= true; + public boolean transport_enabled = true; + // public boolean enabled= true; public int order = 0; public HttpAuthenticator http_authenticator = new HttpAuthenticator(); public AuthcBackend authentication_backend = new AuthcBackend(); @@ -283,10 +304,10 @@ public AuthcDomain(ConfigV6.AuthcDomain v6) { super(); http_enabled = v6.http_enabled && v6.enabled; transport_enabled = v6.transport_enabled && v6.enabled; -// if(v6.enabled)vv { -// http_enabled = true; -// transport_enabled = true; -// } + // if(v6.enabled)vv { + // http_enabled = true; + // transport_enabled = true; + // } order = v6.order; http_authenticator = new HttpAuthenticator(v6.http_authenticator); authentication_backend = new AuthcBackend(v6.authentication_backend); @@ -295,12 +316,21 @@ public AuthcDomain(ConfigV6.AuthcDomain v6) { @Override public String toString() { - return "AuthcDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled + ", order=" + order - + ", http_authenticator=" + http_authenticator + ", authentication_backend=" + authentication_backend + ", description=" - + description + "]"; + return "AuthcDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", order=" + + order + + ", http_authenticator=" + + http_authenticator + + ", authentication_backend=" + + authentication_backend + + ", description=" + + description + + "]"; } - } public static class HttpAuthenticator { @@ -313,14 +343,12 @@ public HttpAuthenticator() { super(); } - public HttpAuthenticator(ConfigV6.HttpAuthenticator v6) { this.challenge = v6.challenge; this.type = v6.type; this.config = v6.config; } - @JsonIgnore public String configAsJson() { try { @@ -330,34 +358,26 @@ public String configAsJson() { } } - @Override public String toString() { return "HttpAuthenticator [challenge=" + challenge + ", type=" + type + ", config=" + config + "]"; } - } public static class AuthzBackend { public String type = "noop"; public Map config = Collections.emptyMap(); - - public AuthzBackend() { super(); } - - public AuthzBackend(ConfigV6.AuthzBackend v6) { this.type = v6.type; this.config = v6.config; } - - @JsonIgnore public String configAsJson() { try { @@ -367,35 +387,26 @@ public String configAsJson() { } } - - @Override public String toString() { return "AuthzBackend [type=" + type + ", config=" + config + "]"; } - } public static class AuthcBackend { public String type = InternalAuthenticationBackend.class.getName(); public Map config = Collections.emptyMap(); - - public AuthcBackend() { super(); } - - public AuthcBackend(ConfigV6.AuthcBackend v6) { this.type = v6.type; this.config = v6.config; } - - @JsonIgnore public String configAsJson() { try { @@ -405,14 +416,11 @@ public String configAsJson() { } } - - @Override public String toString() { return "AuthcBackend [type=" + type + ", config=" + config + "]"; } - } public static class Authz { @@ -434,7 +442,6 @@ public String toString() { return "Authz [domains=" + domains + "]"; } - } public static class AuthzDomain { @@ -458,11 +465,17 @@ public AuthzDomain(ConfigV6.AuthzDomain v6) { @Override public String toString() { - return "AuthzDomain [http_enabled=" + http_enabled + ", transport_enabled=" + transport_enabled - + ", authorization_backend=" + authorization_backend + ", description=" + description + "]"; + return "AuthzDomain [http_enabled=" + + http_enabled + + ", transport_enabled=" + + transport_enabled + + ", authorization_backend=" + + authorization_backend + + ", description=" + + description + + "]"; } - } } diff --git a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java index a7aed05cc1..8f10df04a4 100644 --- a/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java +++ b/src/main/java/org/opensearch/security/securityconf/impl/v7/InternalUserV7.java @@ -41,30 +41,38 @@ public class InternalUserV7 implements Hideable, Hashed, StaticDefinable { - private String hash; - private boolean reserved; - private boolean hidden; - private boolean service; - private boolean enabled; - @JsonProperty(value = "static") - private boolean _static; - private List backend_roles = Collections.emptyList(); - private Map attributes = Collections.emptyMap(); - private String description; - private List opendistro_security_roles = Collections.emptyList(); - - private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes) { - super(); - this.hash = hash; - this.reserved = reserved; - this.hidden = hidden; - this.backend_roles = backend_roles; - this.attributes = attributes; - this.enabled = true; - this.service = false; - } - - private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes, Boolean enabled, Boolean service) { + private String hash; + private boolean reserved; + private boolean hidden; + private boolean service; + private boolean enabled; + @JsonProperty(value = "static") + private boolean _static; + private List backend_roles = Collections.emptyList(); + private Map attributes = Collections.emptyMap(); + private String description; + private List opendistro_security_roles = Collections.emptyList(); + + private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes) { + super(); + this.hash = hash; + this.reserved = reserved; + this.hidden = hidden; + this.backend_roles = backend_roles; + this.attributes = attributes; + this.enabled = true; + this.service = false; + } + + private InternalUserV7( + String hash, + boolean reserved, + boolean hidden, + List backend_roles, + Map attributes, + Boolean enabled, + Boolean service + ) { super(); this.hash = hash; this.reserved = reserved; @@ -75,111 +83,129 @@ private InternalUserV7(String hash, boolean reserved, boolean hidden, List