diff --git a/README.md b/README.md index f0f6321d9d..b9ca3b80da 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ OpenSearch Security is a plugin for OpenSearch that offers encryption, authentic - [Test and Build](#test-and-build) - [Config hot reloading](#config-hot-reloading) - [Onboarding new APIs](#onboarding-new-apis) + - [System Index Protection](#system-index-protection) - [Contributing](#contributing) - [Getting Help](#getting-help) - [Code of Conduct](#code-of-conduct) @@ -119,6 +120,38 @@ It is common practice to create new transport actions to perform different tasks 2. Register the action in the [OpenSearch Security plugin](https://github.com/opensearch-project/security). Each new action is registered in the plugin as a new permission. Usually, plugins will define different roles for their plugin (e.g., read-only access, write access). Each role will contain a set of permissions. An example of adding a new permission to the `anomaly_read_access` role for the [Anomaly Detection plugin](https://github.com/opensearch-project/anomaly-detection) can be found in [this PR](https://github.com/opensearch-project/security/pull/997/files). 3. Register the action in the [OpenSearch Dashboards Security plugin](https://github.com/opensearch-project/security-dashboards-plugin). This plugin maintains the full list of possible permissions, so users can see all of them when creating new roles or searching permissions via Dashboards. An example of adding different permissions can be found in [this PR](https://github.com/opensearch-project/security-dashboards-plugin/pull/689/files). +```mermaid +sequenceDiagram + participant Client + participant OpenSearch + participant SecurityPlugin + participant Cluster as Plugin + + Client->>OpenSearch: Request + OpenSearch->>SecurityPlugin: Request + SecurityPlugin->>SecurityPlugin: Add Auth information to request context + OpenSearch->>Cluster: Client Request + Cluster->>SecurityPlugin: Execute transport layer action + SecurityPlugin->>SecurityPlugin: Check if action is allowed + alt Allowed + SecurityPlugin->>OpenSearch: Continue request + OpenSearch-->>Cluster: Transport layer action result + else Denied + SecurityPlugin-->>OpenSearch: Return 403 Forbidden + OpenSearch-->>Client: 403 Forbidden + end + alt Plugin run outside user context + Cluster->>Cluster: Stash context + Cluster->>SecurityPlugin: Execute transport layer action outside user context + SecurityPlugin-->>SecurityPlugin: Check if action is allowed + SecurityPlugin->>OpenSearch: Continue request + OpenSearch-->>Cluster: Transport layer action result + Cluster->>Cluster: Restore user context + end + Cluster-->>SecurityPlugin: Result + SecurityPlugin-->>OpenSearch: Result + OpenSearch-->>Client: Result +``` ### System Index Protection diff --git a/TRIAGING.md b/TRIAGING.md index 88ed2412ff..2c4ea32fdf 100644 --- a/TRIAGING.md +++ b/TRIAGING.md @@ -24,7 +24,7 @@ Meetings are lightly structured as follows: 1. Announcements: If there are any announcements to be made they will happen at the start of the meeting. 2. Review of new issues: The meetings always start with reviewing all untriaged [issues](https://github.com/search?q=label%3Auntriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) for the security and security-dashboards repositories. -3. Untriaged items: Review any [issues](https://github.com/search?q=-label%3Atriaged+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) that might have had the 'untriaged' label removed but require additional triage discussion. +3. Untriaged items: Review any [issues](https://github.com/search?q=-label%3Atriaged+is%3Aopen++is%3Aissue+repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues) that might have had the 'untriaged' label removed but require additional triage discussion. 4. Open discussion: Next, we open the floor in case anyone wants to highlight an issue. 5. Backlog discussion: Then, we review issues from the [backlogs](https://github.com/search?q=label%3A%22sprint+backlog%22+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=created&o=desc) of the security and security-dashboards repositories. 6. Least recent discussed issue: Finally, to close out the meeting we will [review the oldest](https://github.com/search?q=+is%3Aopen++repo%3Aopensearch-project%2Fsecurity+repo%3Aopensearch-project%2Fsecurity-dashboards-plugin&type=issues&ref=advsearch&s=updated&o=asc) issues from both repositories, security and security-dashboards, to help identify issues that have languished. diff --git a/build.gradle b/build.gradle index 64798b957e..8cfe97ea80 100644 --- a/build.gradle +++ b/build.gradle @@ -75,16 +75,16 @@ licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') spotless { - java { - // 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', '\\#') - targetExclude('src/integrationTest/**') - } - format("integrationTest", JavaExtension) { - target('src/integrationTest/java/**/*.java') - importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') - indentWithTabs(4) - } + java { + // 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', '\\#') + targetExclude('src/integrationTest/**') + } + format("integrationTest", JavaExtension) { + target('src/integrationTest/java/**/*.java') + importOrder('java', 'javax', '', 'com.amazon', 'org.opensearch', '\\#') + indentWithTabs(4) + } } spotbugs { @@ -135,17 +135,17 @@ test { } jacoco { excludes = [ - "com.sun.jndi.dns.*", - "com.sun.security.sasl.gsskerb.*", - "java.sql.*", - "javax.script.*", - "org.jcp.xml.dsig.internal.dom.*", - "sun.nio.cs.ext.*", - "sun.security.ec.*", - "sun.security.jgss.*", - "sun.security.pkcs11.*", - "sun.security.smartcardio.*", - "sun.util.resources.provider.*" + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" ] } } @@ -159,17 +159,17 @@ task opensslTest(type: Test) { } jacoco { excludes = [ - "com.sun.jndi.dns.*", - "com.sun.security.sasl.gsskerb.*", - "java.sql.*", - "javax.script.*", - "org.jcp.xml.dsig.internal.dom.*", - "sun.nio.cs.ext.*", - "sun.security.ec.*", - "sun.security.jgss.*", - "sun.security.pkcs11.*", - "sun.security.smartcardio.*", - "sun.util.resources.provider.*" + "com.sun.jndi.dns.*", + "com.sun.security.sasl.gsskerb.*", + "java.sql.*", + "javax.script.*", + "org.jcp.xml.dsig.internal.dom.*", + "sun.nio.cs.ext.*", + "sun.security.ec.*", + "sun.security.jgss.*", + "sun.security.pkcs11.*", + "sun.security.smartcardio.*", + "sun.util.resources.provider.*" ] } } @@ -462,7 +462,7 @@ dependencies { testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' // Kafka test execution testRuntimeOnly 'org.springframework.retry:spring-retry:1.3.3' - testRuntimeOnly ('org.springframework:spring-core:5.3.26') { + testRuntimeOnly ('org.springframework:spring-core:5.3.27') { exclude(group:'org.springframework', module: 'spring-jcl' ) } testRuntimeOnly 'org.scala-lang:scala-library:2.13.9' diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.jar b/bwc-test/gradle/wrapper/gradle-wrapper.jar index ccebba7710..c1962a79e2 100644 Binary files a/bwc-test/gradle/wrapper/gradle-wrapper.jar and b/bwc-test/gradle/wrapper/gradle-wrapper.jar differ diff --git a/bwc-test/gradle/wrapper/gradle-wrapper.properties b/bwc-test/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1e..560c9869de 100644 --- a/bwc-test/gradle/wrapper/gradle-wrapper.properties +++ b/bwc-test/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f diff --git a/bwc-test/gradlew b/bwc-test/gradlew index 79a61d421c..aeb74cbb43 100755 --- a/bwc-test/gradlew +++ b/bwc-test/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/checkstyle/sun_checks.xml b/checkstyle/sun_checks.xml index 5ffbedaf5a..8df7afffc7 100644 --- a/checkstyle/sun_checks.xml +++ b/checkstyle/sun_checks.xml @@ -204,6 +204,13 @@ + + + + + + + @@ -211,7 +218,7 @@ - + diff --git a/config/roles.yml b/config/roles.yml index fd485423fe..00ebdf6edb 100644 --- a/config/roles.yml +++ b/config/roles.yml @@ -297,6 +297,8 @@ security_analytics_read_access: reserved: true cluster_permissions: - 'cluster:admin/opensearch/securityanalytics/alerts/get' + - 'cluster:admin/opensearch/securityanalytics/correlations/findings' + - 'cluster:admin/opensearch/securityanalytics/correlations/list' - 'cluster:admin/opensearch/securityanalytics/detector/get' - 'cluster:admin/opensearch/securityanalytics/detector/search' - 'cluster:admin/opensearch/securityanalytics/findings/get' @@ -310,6 +312,7 @@ security_analytics_full_access: reserved: true cluster_permissions: - 'cluster:admin/opensearch/securityanalytics/alerts/*' + - 'cluster:admin/opensearch/securityanalytics/correlations/*' - 'cluster:admin/opensearch/securityanalytics/detector/*' - 'cluster:admin/opensearch/securityanalytics/findings/*' - 'cluster:admin/opensearch/securityanalytics/mapping/*' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7710..c1962a79e2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bdc9a83b1e..560c9869de 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionSha256Sum=e111cb9948407e26351227dabce49822fb88c37ee72f1d1582a69c68af2e702f diff --git a/gradlew b/gradlew index 79a61d421c..aeb74cbb43 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java index 0fbaf78435..f346bedd31 100644 --- a/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java +++ b/src/integrationTest/java/org/opensearch/security/SnapshotSteps.java @@ -41,7 +41,7 @@ public SnapshotSteps(RestHighLevelClient restHighLevelClient) { // CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here public org.opensearch.action.support.master.AcknowledgedResponse createSnapshotRepository(String repositoryName, String snapshotDirPath, String type) - //CS-ENFORCE-SINGLE + // CS-ENFORCE-SINGLE throws IOException { PutRepositoryRequest createRepositoryRequest = new PutRepositoryRequest().name(repositoryName).type(type) .settings(Map.of("location", snapshotDirPath)); @@ -70,7 +70,7 @@ public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshotR return snapshotClient.deleteRepository(request, DEFAULT); } - //CS-SUPPRESS-SINGLE: RegexpSingleline: It is not possible to use phrase "cluster manager" instead of master here + //CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here public org.opensearch.action.support.master.AcknowledgedResponse deleteSnapshot(String repositoryName, String snapshotName) throws IOException { //CS-ENFORCE-SINGLE return snapshotClient.delete(new DeleteSnapshotRequest(repositoryName, snapshotName), DEFAULT); diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java index 75f933e0d3..fbd2c1f8e8 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificateMetadata.java @@ -9,7 +9,7 @@ */ package org.opensearch.test.framework.certificate; - +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions, keeping this rule disable for the whole file import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -218,3 +218,4 @@ ExtendedKeyUsage getExtendedKeyUsage() { return new ExtendedKeyUsage(usages); } } +// CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java index 887e197369..8d821a4571 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/CertificatesIssuer.java @@ -26,6 +26,7 @@ package org.opensearch.test.framework.certificate; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions, keeping this rule disable for the whole file import java.math.BigInteger; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; @@ -222,3 +223,4 @@ private BigInteger generateNextCertificateSerialNumber() { return BigInteger.valueOf(ID_COUNTER.incrementAndGet()); } } +// CS-ENFORCE-SINGLE diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java index 8e9aba68e5..9ee2ec8a02 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/PublicKeyUsage.java @@ -14,6 +14,7 @@ import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.KeyUsage; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extension is used to refer to certificate extensions /** * The class is associated with certificate extensions related to key usages. These extensions are defined by * RFC 5280 and describes allowed usage of public kay which is embedded in @@ -25,6 +26,7 @@ * * @see RFC 5280 */ +// CS-ENFORCE-SINGLE enum PublicKeyUsage { DIGITAL_SIGNATURE(KeyUsage.digitalSignature), KEY_CERT_SIGN(KeyUsage.keyCertSign), diff --git a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java index 14e6357330..bc5f5f267d 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java +++ b/src/integrationTest/java/org/opensearch/test/framework/certificate/TestCertificates.java @@ -60,8 +60,8 @@ public class TestCertificates { private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA"; private static final String ADMIN_DN = "CN=kirk,OU=client,O=client,L=test,C=de"; private static final int CERTIFICATE_VALIDITY_DAYS = 365; - private static final String CERTIFICATE_FILE_EXTENSION = ".cert"; - private static final String KEY_FILE_EXTENSION = ".key"; + private static final String CERTIFICATE_FILE_EXT = ".cert"; + private static final String KEY_FILE_EXT = ".key"; private final CertificateData caCertificate; private final CertificateData adminCertificate; private final List nodeCertificates; @@ -106,7 +106,7 @@ public CertificateData createSelfSignedCertificate(String distinguishedName) { * @return file which contains certificate in PEM format, defined by RFC 1421 */ public File getRootCertificate() { - return createTempFile("root", CERTIFICATE_FILE_EXTENSION, caCertificate.certificateInPemFormat()); + return createTempFile("root", CERTIFICATE_FILE_EXT, caCertificate.certificateInPemFormat()); } public CertificateData getRootCertificateData() { @@ -120,7 +120,7 @@ public CertificateData getRootCertificateData() { */ public File getNodeCertificate(int node) { CertificateData certificateData = getNodeCertificateData(node); - return createTempFile("node-" + node, CERTIFICATE_FILE_EXTENSION, certificateData.certificateInPemFormat()); + return createTempFile("node-" + node, CERTIFICATE_FILE_EXT, certificateData.certificateInPemFormat()); } public CertificateData getNodeCertificateData(int node) { @@ -178,7 +178,7 @@ public CertificateData getLdapCertificateData() { */ public File getNodeKey(int node, String privateKeyPassword) { CertificateData certificateData = nodeCertificates.get(node); - return createTempFile("node-" + node, KEY_FILE_EXTENSION, certificateData.privateKeyInPemFormat(privateKeyPassword)); + return createTempFile("node-" + node, KEY_FILE_EXT, certificateData.privateKeyInPemFormat(privateKeyPassword)); } /** @@ -187,7 +187,7 @@ public File getNodeKey(int node, String privateKeyPassword) { * @return file which contains certificate in PEM format, defined by RFC 1421 */ public File getAdminCertificate() { - return createTempFile("admin", CERTIFICATE_FILE_EXTENSION, adminCertificate.certificateInPemFormat()); + return createTempFile("admin", CERTIFICATE_FILE_EXT, adminCertificate.certificateInPemFormat()); } public CertificateData getAdminCertificateData() { @@ -202,7 +202,7 @@ public CertificateData getAdminCertificateData() { * by RFC 1421 */ public File getAdminKey(String privateKeyPassword) { - return createTempFile("admin", KEY_FILE_EXTENSION, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); + return createTempFile("admin", KEY_FILE_EXT, adminCertificate.privateKeyInPemFormat(privateKeyPassword)); } public String[] getAdminDNs() { diff --git a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java index d88b65875e..47f4820944 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java +++ b/src/integrationTest/java/org/opensearch/test/framework/matcher/GetSettingsResponseContainsIndicesMatcher.java @@ -38,7 +38,11 @@ protected boolean matchesSafely(GetSettingsResponse response, Description mismat if (!indexToSettings.containsKey(index)) { mismatchDescription .appendText("Response contains settings of indices: ") +<<<<<<< HEAD .appendValue(indexToSettings.keySet().toArray()); +======= + .appendValue(indexToSettings.keySet()); +>>>>>>> main return false; } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 87ca878fc6..815f96f0c5 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -26,6 +26,7 @@ package org.opensearch.security; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions import java.io.IOException; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -94,6 +95,7 @@ import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; +import org.opensearch.extensions.ExtensionsManager; import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; import org.opensearch.index.Index; @@ -191,6 +193,7 @@ import org.opensearch.transport.TransportResponseHandler; import org.opensearch.transport.TransportService; import org.opensearch.watcher.ResourceWatcherService; +// CS-ENFORCE-SINGLE public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin { @@ -333,7 +336,7 @@ public Object run() { final List filesWithWrongPermissions = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().filter(p -> checkFilePermissions(p)).collect(Collectors.toList()); @@ -363,7 +366,7 @@ public List run() { final List files = AccessController.doPrivileged(new PrivilegedAction>() { @Override public List run() { - final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); + final Path confPath = new Environment(settings, configPath).configDir().toAbsolutePath(); if(Files.isDirectory(confPath, LinkOption.NOFOLLOW_LINKS)) { try (Stream s = Files.walk(confPath)) { return s.distinct().map(p -> sha256(p)).collect(Collectors.toList()); @@ -460,8 +463,8 @@ private boolean checkFilePermissions(final Path p) { @Override public List getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings, - IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, - IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { + IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster) { final List handlers = new ArrayList(1); @@ -669,7 +672,7 @@ public List getTransportInterceptors(NamedWriteableRegistr @Override public TransportRequestHandler interceptHandler(String action, String executor, - boolean forceExecution, TransportRequestHandler actualHandler) { + boolean forceExecution, TransportRequestHandler actualHandler) { return new TransportRequestHandler() { @@ -688,7 +691,7 @@ public AsyncSender interceptSender(AsyncSender sender) { @Override public void sendRequest(Connection connection, String action, - TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { + TransportRequest request, TransportRequestOptions options, TransportResponseHandler handler) { si.sendRequestDecorate(sender, connection, action, request, options, handler); } }; @@ -701,7 +704,7 @@ public void sendRequest(Connection connection, Str @Override public Map> getTransports(Settings settings, ThreadPool threadPool, PageCacheRecycler pageCacheRecycler, - CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { + CircuitBreakerService circuitBreakerService, NamedWriteableRegistry namedWriteableRegistry, NetworkService networkService) { Map> transports = new HashMap>(); if(SSLConfig.isSslOnlyMode()) { @@ -718,12 +721,12 @@ public Map> getTransports(Settings settings, ThreadP @Override public Map> getHttpTransports(Settings settings, ThreadPool threadPool, BigArrays bigArrays, - PageCacheRecycler pageCacheRecycler, CircuitBreakerService circuitBreakerService, NamedXContentRegistry xContentRegistry, - NetworkService networkService, Dispatcher dispatcher, ClusterSettings clusterSettings) { + 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); + networkService, dispatcher, clusterSettings); } if(!disabled) { @@ -749,9 +752,9 @@ public Map> getHttpTransports(Settings set @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) { + ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, + Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, Supplier repositoriesServiceSupplier) { SSLConfig.registerClusterSettingsChangeListener(clusterService.getClusterSettings()); if(SSLConfig.isSslOnlyMode()) { @@ -832,7 +835,7 @@ public Collection createComponents(Client localClient, ClusterService cl 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) { @@ -896,8 +899,8 @@ public Settings additionalSettings() { builder.put(super.additionalSettings()); 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"); + 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(); } @@ -905,7 +908,7 @@ public Settings additionalSettings() { public List> getSettings() { List> settings = new ArrayList>(); settings.addAll(super.getSettings()); - + settings.add(Setting.boolSetting(ConfigConstants.SECURITY_SSL_ONLY, false, Property.NodeScope, Property.Filtered)); // currently dual mode is supported only when ssl_only is enabled, but this stance would change in future @@ -923,26 +926,26 @@ public List> getSettings() { 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.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.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_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)); @@ -950,10 +953,10 @@ public List> getSettings() { 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)); - + // 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)); @@ -975,7 +978,7 @@ public List> getSettings() { 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); Arrays.stream(FilterEntries.values()).map(filterEntry -> { @@ -1000,12 +1003,12 @@ public List> getSettings() { throw new RuntimeException("Please add support for new FilterEntries value '" + filterEntry.name() + "'"); } }).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)); - + // 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)); @@ -1023,25 +1026,25 @@ public List> getSettings() { 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)); - + // 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)); - - + + // 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_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.groupSetting(ConfigConstants.SECURITY_RESTAPI_ENDPOINTS_DISABLED + ".", Property.NodeScope)); @@ -1050,7 +1053,7 @@ public List> getSettings() { 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)); - + // 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 @@ -1083,7 +1086,7 @@ public List> getSettings() { 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 +1101,7 @@ public List getSettingsFilter() { settingsFilter.add("plugins.security.*"); return settingsFilter; } - + @Override public void onNodeStarted() { log.info("Node started"); @@ -1187,14 +1190,19 @@ public static class GuiceHolder implements LifecycleComponent { private static IndicesService indicesService; private static PitService pitService; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions + private static ExtensionsManager extensionsManager; + @Inject public GuiceHolder(final RepositoriesService repositoriesService, - final TransportService remoteClusterService, IndicesService indicesService, PitService pitService) { + final TransportService remoteClusterService, IndicesService indicesService, PitService pitService, ExtensionsManager extensionsManager) { GuiceHolder.repositoriesService = repositoriesService; GuiceHolder.remoteClusterService = remoteClusterService.getRemoteClusterService(); GuiceHolder.indicesService = indicesService; GuiceHolder.pitService = pitService; + GuiceHolder.extensionsManager = extensionsManager; } + // CS-ENFORCE-SINGLE public static RepositoriesService getRepositoriesService() { return repositoriesService; @@ -1210,6 +1218,10 @@ public static IndicesService getIndicesService() { 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; } + // CS-ENFORCE-SINGLE + @Override public void close() { diff --git a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java index c0ecd6b9be..4bb27a6cbb 100644 --- a/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java +++ b/src/main/java/org/opensearch/security/configuration/DlsFilterLevelActionHandler.java @@ -142,7 +142,7 @@ private boolean handle() { threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE, request.toString()); try { - if (!createQueryExtension()) { + if (!modifyQuery()) { return true; } @@ -186,7 +186,7 @@ private boolean handle(SearchRequest searchRequest, StoredContext ctx) { if (localClusterAlias != null) { try { - createQueryExtension(localClusterAlias); + modifyQuery(localClusterAlias); } catch (Exception e) { log.error("Unable to handle filter level DLS", e); listener.onFailure(new OpenSearchSecurityException("Unable to handle filter level DLS", e)); @@ -387,11 +387,11 @@ private GetResult searchHitToGetResult(SearchHit hit) { documentFields, metadataFields); } - private boolean createQueryExtension() throws IOException { - return createQueryExtension(null); + private boolean modifyQuery() throws IOException { + return modifyQuery(null); } - private boolean createQueryExtension(String localClusterAlias) throws IOException { + private boolean modifyQuery(String localClusterAlias) throws IOException { Map> filterLevelQueries = evaluatedDlsFlsConfig.getDlsQueriesByIndex(); BoolQueryBuilder dlsQueryBuilder = QueryBuilders.boolQuery().minimumShouldMatch(1); 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 0551c108a1..6c1169f35a 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 @@ -31,12 +31,14 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestRequest; import org.opensearch.rest.RestRequest.Method; +import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.AdminDNs; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.dlic.rest.validation.AbstractConfigurationValidator; import org.opensearch.security.dlic.rest.validation.InternalUsersValidator; import org.opensearch.security.privileges.PrivilegesEvaluator; +import org.opensearch.security.securityconf.Hashed; import org.opensearch.security.securityconf.impl.CType; import org.opensearch.security.securityconf.impl.SecurityDynamicConfiguration; import org.opensearch.security.ssl.transport.PrincipalExtractor; @@ -50,18 +52,20 @@ 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( new Route(Method.GET, "/user/{name}"), new Route(Method.GET, "/user/"), + new Route(Method.POST, "/user/{name}/authtoken"), new Route(Method.DELETE, "/user/{name}"), new Route(Method.PUT, "/user/{name}"), // corrected mapping, introduced in OpenSearch Security new Route(Method.GET, "/internalusers/{name}"), new Route(Method.GET, "/internalusers/"), + new Route(Method.POST, "/internalusers/{name}/authtoken"), new Route(Method.DELETE, "/internalusers/{name}"), new Route(Method.PUT, "/internalusers/{name}"), new Route(Method.PATCH, "/internalusers/"), @@ -127,11 +131,11 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C // changes try { - if (request.hasParam("owner")) { - ((ObjectNode) content).put("owner", request.param("owner")); + if (request.hasParam("service")) { + ((ObjectNode) content).put("service", request.param("service")); } - if (request.hasParam("isEnabled")) { - ((ObjectNode) content).put("isEnabled", request.param("isEnabled")); + if (request.hasParam("enabled")) { + ((ObjectNode) content).put("enabled", request.param("enabled")); } ((ObjectNode) content).put("name", username); internalUsersConfiguration = userService.createOrUpdateAccount((ObjectNode) content); @@ -144,8 +148,28 @@ protected void handlePut(RestChannel channel, final RestRequest request, final C throw new IOException(ex); } + // for existing users, hash is optional + if (userExisted && securityJsonNode.get("hash").asString() == null) { + // 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."); + return; + } + contentAsNode.put("hash", hash); + } + + internalUsersConfiguration.remove(username); + + // checks complete, create or update the user + 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) { @@ -158,6 +182,63 @@ public void onResponse(IndexResponse response) { }); } + /** + * Overrides the GET request functionality to allow for the special case of requesting an auth token. + * + * @param channel The channel the request is coming through + * @param request The request itself + * @param client The client executing the request + * @param content The content of the request parsed into a node + * @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{ + + final String username = request.param("name"); + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), true); + filter(internalUsersConfiguration); // Hides hashes + + // no specific resource requested + if (username == null || username.length() == 0) { + + notImplemented(channel, Method.POST); + return; + } + + final boolean userExisted = internalUsersConfiguration.exists(username); + + if (!userExisted) { + notFound(channel, "Resource '" + username + "' not found."); + return; + } + + String authToken = ""; + try { + 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 + + notImplemented(channel, Method.POST); + return; + } + } catch (UserServiceException ex) { + badRequestResponse(channel, ex.getMessage()); + return; + } + catch (IOException ex) { + throw new IOException(ex); + } + + if (!authToken.isEmpty()) { + createdResponse(channel, "'" + username + "' authtoken generated " + authToken); + } else { + badRequestResponse(channel, "'" + username + "' authtoken failed to be created."); + } + } + + @Override protected void filter(SecurityDynamicConfiguration builder) { super.filter(builder); 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 4a1467f3a6..b697a9485b 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 @@ -44,6 +44,8 @@ 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(); @@ -58,7 +60,20 @@ private InternalUserV7(String hash, boolean reserved, boolean hidden, List backend_roles, Map attributes, Boolean enabled, Boolean service) { + super(); + this.hash = hash; + this.reserved = reserved; + this.hidden = hidden; + this.backend_roles = backend_roles; + this.attributes = attributes; + this.enabled = enabled; + this.service = service; + } public InternalUserV7() { super(); @@ -80,7 +95,6 @@ public String getHash() { public void setHash(String hash) { this.hash = hash; } - public boolean isHidden() { @@ -114,9 +128,17 @@ public void setAttributes(Map attributes) { this.attributes = attributes; } + public boolean enabled() { + return this.enabled; + } + + public boolean service() { + return this.service; + } + @Override public String toString() { - return "InternalUserV7 [hash=" + hash + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles=" + return "InternalUserV7 [hash=" + hash + ", enabled=" + enabled + ", service=" + service + ", reserved=" + reserved + ", hidden=" + hidden + ", _static=" + _static + ", backend_roles=" + backend_roles + ", attributes=" + attributes + ", description=" + description + "]"; } @@ -134,6 +156,14 @@ public void setDescription(String description) { this.description = description; } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setService(boolean service) { + this.service = service; + } + public boolean isReserved() { return reserved; } diff --git a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java index 8b704c84d3..01e9714d02 100644 --- a/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java +++ b/src/main/java/org/opensearch/security/ssl/DefaultSecurityKeyStore.java @@ -110,8 +110,10 @@ private void printJCEWarnings() { final int aesMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES"); if (aesMaxKeyLength < 256) { + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions log.info("AES-256 not supported, max key length for AES is {} bit." + " (This is not an issue, it just limits possible encryption strength. To enable AES 256, install 'Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files')", aesMaxKeyLength); + // CS-ENFORCE-SINGLE } } catch (final NoSuchAlgorithmException e) { log.error("AES encryption not supported (SG 1). ", e); diff --git a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java index 81d625126b..0a12ffc2b5 100644 --- a/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java +++ b/src/main/java/org/opensearch/security/ssl/util/CertificateValidator.java @@ -52,6 +52,7 @@ import java.util.HashSet; import java.util.Set; +// CS-SUPPRESS-SINGLE: RegexpSingleline certification extensions is unrelated to OpenSearch extensions /** * Convenience class to handle validation of certificates, aliases and keystores * @@ -62,6 +63,7 @@ * IMPORTANT: at least one of the above mechanisms *MUST* be configured and * operational, otherwise certificate validation *WILL FAIL* unconditionally. */ +// CS-ENFORCE-SINGLE public class CertificateValidator { diff --git a/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java b/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java index fc2c258888..89f6f7ff3b 100644 --- a/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java +++ b/src/main/java/org/opensearch/security/ssl/util/TLSUtil.java @@ -21,7 +21,9 @@ public class TLSUtil { private static final int SSL_CONTENT_TYPE_ALERT = 21; private static final int SSL_CONTENT_TYPE_HANDSHAKE = 22; private static final int SSL_CONTENT_TYPE_APPLICATION_DATA = 23; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions heartbeat needs special handling by security extension private static final int SSL_CONTENT_TYPE_EXTENSION_HEARTBEAT = 24; + // CS-ENFORCE-SINGLE private static final int SSL_RECORD_HEADER_LENGTH = 5; private TLSUtil() { @@ -39,9 +41,11 @@ public static boolean isTLS(ByteBuf buffer) { case SSL_CONTENT_TYPE_ALERT: case SSL_CONTENT_TYPE_HANDSHAKE: case SSL_CONTENT_TYPE_APPLICATION_DATA: + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions heartbeat needs special handling by security extension case SSL_CONTENT_TYPE_EXTENSION_HEARTBEAT: tls = true; break; + // CS-ENFORCE-SINGLE default: // SSLv2 or bad data tls = false; diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index f81d89810b..7ed780f413 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -97,6 +97,9 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_TRUSTED_CLUSTER_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_trustedcluster_request"; + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions + public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST = OPENDISTRO_SECURITY_CONFIG_PREFIX+"ssl_transport_extension_request"; + // CS-ENFORCE-SINGLE /** * Set by the SSL plugin, this is the peer node certificate on the transport layer diff --git a/src/main/java/org/opensearch/security/support/HeaderHelper.java b/src/main/java/org/opensearch/security/support/HeaderHelper.java index e9ff50c6af..a4ee123347 100644 --- a/src/main/java/org/opensearch/security/support/HeaderHelper.java +++ b/src/main/java/org/opensearch/security/support/HeaderHelper.java @@ -44,7 +44,12 @@ public static boolean isDirectRequest(final ThreadContext context) { return "direct".equals(context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE)) || context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_CHANNEL_TYPE) == null; } - + + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions + public static boolean isExtensionRequest(final ThreadContext context) { + return context.getTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST) == Boolean.TRUE; + } + // CS-ENFORCE-SINGLE public static String getSafeFromHeader(final ThreadContext context, final String headerName) { diff --git a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java index 161ad72528..2427a48f8a 100644 --- a/src/main/java/org/opensearch/security/tools/SecurityAdmin.java +++ b/src/main/java/org/opensearch/security/tools/SecurityAdmin.java @@ -210,8 +210,10 @@ public static int execute(final String[] args) throws Exception { options.addOption( "nhnv", "disable-host-name-verification", false, "Disable hostname verification" ); options.addOption(Option.builder("ts").longOpt("truststore").hasArg().argName("file").desc("Path to truststore (JKS/PKCS12 format)").build()); options.addOption(Option.builder("ks").longOpt("keystore").hasArg().argName("file").desc("Path to keystore (JKS/PKCS12 format").build()); + // CS-SUPPRESS-SINGLE: RegexpSingleline file extensions is unrelated to OpenSearch extensions options.addOption(Option.builder("tst").longOpt("truststore-type").hasArg().argName("type").desc("JKS or PKCS12, if not given we use the file extension to dectect the type").build()); options.addOption(Option.builder("kst").longOpt("keystore-type").hasArg().argName("type").desc("JKS or PKCS12, if not given we use the file extension to dectect the type").build()); + // CS-ENFORCE-SINGLE options.addOption(Option.builder("tspass").longOpt("truststore-password").hasArg().argName("password").desc("Truststore password").build()); options.addOption(Option.builder("kspass").longOpt("keystore-password").hasArg().argName("password").desc("Keystore password").build()); options.addOption(Option.builder("cd").longOpt("configdir").hasArg().argName("directory").desc("Directory for config files").build()); @@ -1410,6 +1412,7 @@ private static RestHighLevelClient getRestHighLevelClient(SSLContext sslContext, .setSslContext(sslContext) .setTlsVersions(supportedProtocols) .setCiphers(supportedCipherSuites) + .setHostnameVerifier(hnv) // See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219 .setTlsDetailsFactory(new Factory() { @Override diff --git a/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java b/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java index 46bf36b27a..71a1ea3275 100644 --- a/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java +++ b/src/main/java/org/opensearch/security/transport/OIDClusterRequestEvaluator.java @@ -33,11 +33,13 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.transport.TransportRequest; +// CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions /** * Implementation to evaluate a certificate extension with a given OID * and value to the same value found on the peer certificate * */ +// CS-ENFORCE-SINGLE public final class OIDClusterRequestEvaluator implements InterClusterRequestEvaluator { private final String certOid; @@ -49,8 +51,10 @@ public OIDClusterRequestEvaluator(final Settings settings) { public boolean isInterClusterRequest(TransportRequest request, X509Certificate[] localCerts, X509Certificate[] peerCerts, final String principal) { if (localCerts != null && localCerts.length > 0 && peerCerts != null && peerCerts.length > 0) { + // CS-SUPPRESS-SINGLE: RegexpSingleline Java Cryptography Extension is unrelated to OpenSearch extensions final byte[] localValue = localCerts[0].getExtensionValue(certOid); final byte[] peerValue = peerCerts[0].getExtensionValue(certOid); + // CS-ENFORCE-SINGLE if (localValue != null && peerValue != null) { return Arrays.equals(localValue, peerValue); } diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index d534e04f90..860f955292 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -26,6 +26,7 @@ package org.opensearch.security.transport; +// CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import java.util.Objects; @@ -41,7 +42,9 @@ import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.transport.TransportAddress; import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.extensions.ExtensionsManager; import org.opensearch.search.internal.ShardSearchRequest; +import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auditlog.AuditLog.Origin; import org.opensearch.security.ssl.SslExceptionHandler; @@ -60,6 +63,7 @@ import org.opensearch.transport.TransportRequestHandler; import static org.opensearch.security.OpenSearchSecurityPlugin.isActionTraceEnabled; +// CS-ENFORCE-SINGLE public class SecurityRequestHandler extends SecuritySSLRequestHandler { @@ -193,10 +197,13 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { //if the incoming request is an internal:* or a shard request allow only if request was sent by a server node //if transport channel is not a netty channel but a direct or local channel (e.g. send via network) then allow it (regardless of beeing a internal: or shard request) //also allow when issued from a remote cluster for cross cluster search + // CS-SUPPRESS-SINGLE: RegexpSingleline Used to allow/disallow TLS connections to extensions if ( !HeaderHelper.isInterClusterRequest(getThreadContext()) && !HeaderHelper.isTrustedClusterRequest(getThreadContext()) + && !HeaderHelper.isExtensionRequest(getThreadContext()) && !task.getAction().equals("internal:transport/handshake") && (task.getAction().startsWith("internal:") || task.getAction().contains("["))) { + // CS-ENFORCE-SINGLE auditLog.logMissingPrivileges(task.getAction(), request, task); log.error("Internal or shard requests ("+task.getAction()+") not allowed from a non-server node for transport type "+transportChannel.getChannelType()); @@ -216,14 +223,16 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { transportChannel.sendResponse(ex); return; } else { - if(getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN) == null) { getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.TRANSPORT.toString()); } //network intercluster request or cross search cluster request + // CS-SUPPRESS-SINGLE: RegexpSingleline Used to allow/disallow TLS connections to extensions if(HeaderHelper.isInterClusterRequest(getThreadContext()) - || HeaderHelper.isTrustedClusterRequest(getThreadContext())) { + || HeaderHelper.isTrustedClusterRequest(getThreadContext()) + || HeaderHelper.isExtensionRequest(getThreadContext())) { + // CS-ENFORCE-SINGLE final String userHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); final String injectedRolesHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER); @@ -256,7 +265,6 @@ else if(!Strings.isNullOrEmpty(injectedUserHeader)) { } } else { - //this is a netty request from a non-server node (maybe also be internal: or a shard request) //and therefore issued by a transport client @@ -326,6 +334,16 @@ protected void addAdditionalContextValues(final String action, final TransportRe } } + // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions + String extensionUniqueId = getThreadContext().getHeader("extension_unique_id"); + if (extensionUniqueId != null) { + ExtensionsManager extManager = OpenSearchSecurityPlugin.GuiceHolder.getExtensionsManager(); + if (extManager.lookupInitializedExtensionById(extensionUniqueId).isPresent()) { + getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_SSL_TRANSPORT_EXTENSION_REQUEST, Boolean.TRUE); + } + } + // CS-ENFORCE-SINGLE + super.addAdditionalContextValues(action, request, localCerts, peerCerts, principal); } } diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index c6eea8ef4c..4a4193590e 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -12,19 +12,29 @@ package org.opensearch.security.user; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.collect.ImmutableList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.ExceptionsHelper; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; import org.opensearch.client.Client; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Inject; import org.opensearch.common.settings.Settings; +import org.opensearch.common.xcontent.XContentHelper; +import org.opensearch.common.xcontent.XContentType; import org.opensearch.security.DefaultObjectMapper; import org.opensearch.security.configuration.ConfigurationRepository; import org.opensearch.security.securityconf.DynamicConfigFactory; @@ -46,6 +56,8 @@ public class UserService { static ConfigurationRepository configurationRepository; String securityIndex; Client client; + + User tokenUser; final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; final static String RESTRICTED_CHARACTER_USE_MESSAGE = "A restricted character(s) was detected in the account name. Please remove: "; @@ -54,7 +66,10 @@ public class UserService { final static String SERVICE_ACCOUNT_HASH_MESSAGE = "A password hash cannot be provided for service account. Failed to register service account: "; final static String NO_ACCOUNT_NAME_MESSAGE = "No account name was specified in the request."; - private static CType getConfigName() { + + final static String FAILED_ACCOUNT_RETRIEVAL_MESSAGE = "The account specified could not be accessed at this time."; + final static String AUTH_TOKEN_GENERATION_MESSAGE = "An auth token could not be generated for the specified account."; + private static CType getUserConfigName() { return CType.INTERNALUSERS; } @@ -96,20 +111,25 @@ protected static final SecurityDynamicConfiguration load(final CType config, */ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentAsNode) throws IOException { - SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); - final SecurityDynamicConfiguration internalUsersConfiguration = load(getConfigName(), false); + final SecurityDynamicConfiguration internalUsersConfiguration = load(getUserConfigName(), false); String accountName = securityJsonNode.get("name").asString(); if (accountName == null || accountName.length() == 0) { // Fail if field is present but empty throw new UserServiceException(NO_ACCOUNT_NAME_MESSAGE); } - if (!securityJsonNode.get("attributes").get("owner").isNull() && !securityJsonNode.get("attributes").get("owner").equals(accountName)) { // If this is a service account + SecurityJsonNode attributeNode = securityJsonNode.get("attributes"); + + if (!attributeNode.get("service").isNull() && attributeNode.get("service").asString().equalsIgnoreCase("true")) + { // If this is a service account verifyServiceAccount(securityJsonNode, accountName); String password = generatePassword(); contentAsNode.put("hash", hash(password.toCharArray())); + contentAsNode.put("service", "true"); + } else{ + contentAsNode.put("service", "false"); } securityJsonNode = new SecurityJsonNode(contentAsNode); @@ -131,6 +151,10 @@ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentA contentAsNode.remove("password"); } + if (!attributeNode.get("enabled").isNull()) { + contentAsNode.put("enabled", securityJsonNode.get("enabled").asString()); + } + final boolean userExisted = internalUsersConfiguration.exists(accountName); // sanity checks, hash is mandatory for newly created users @@ -157,6 +181,7 @@ public SecurityDynamicConfiguration createOrUpdateAccount(ObjectNode contentA private void verifyServiceAccount(SecurityJsonNode securityJsonNode, String accountName) { + final String plainTextPassword = securityJsonNode.get("password").asString(); final String origHash = securityJsonNode.get("hash").asString(); @@ -178,4 +203,77 @@ private String generatePassword() { String generatedPassword = "superSecurePassword"; return generatedPassword; } + + /** + * This function retrieves the auth token associated with a service account. + * Fails if the provided account is not a service account or account is not enabled. + * + * @param accountName A string representing the name of the account + * @return A string auth token + */ + public String generateAuthToken(String accountName) throws IOException { + + final SecurityDynamicConfiguration internalUsersConfiguration = load(getUserConfigName(), false); + + if (!internalUsersConfiguration.exists(accountName)) { + throw new UserServiceException(FAILED_ACCOUNT_RETRIEVAL_MESSAGE); + } + + String authToken = null; + try { + DefaultObjectMapper mapper = new DefaultObjectMapper(); + JsonNode accountDetails = mapper.readTree(internalUsersConfiguration.getCEntry(accountName).toString()); + final ObjectNode contentAsNode = (ObjectNode) accountDetails; + SecurityJsonNode securityJsonNode = new SecurityJsonNode(contentAsNode); + + Optional.ofNullable(securityJsonNode.get("service")) + .map(SecurityJsonNode::asString) + .filter("true"::equalsIgnoreCase) + .orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE)); + + + Optional.ofNullable(securityJsonNode.get("enabled")) + .map(SecurityJsonNode::asString) + .filter("true"::equalsIgnoreCase) + .orElseThrow(() -> new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE)); + + // Generate a new password for the account and store the hash of it + String plainTextPassword = generatePassword(); + contentAsNode.put("hash", hash(plainTextPassword.toCharArray())); + contentAsNode.put("enabled", "true"); + contentAsNode.put("service", "true"); + + // Update the internal user associated with the auth token + internalUsersConfiguration.remove(accountName); + contentAsNode.remove("name"); + internalUsersConfiguration.putCObject(accountName, DefaultObjectMapper.readTree(contentAsNode, internalUsersConfiguration.getImplementingClass())); + saveAndUpdateConfigs(getUserConfigName().toString(), client, CType.INTERNALUSERS, internalUsersConfiguration); + + + authToken = Base64.getUrlEncoder().encodeToString((accountName + ":" + plainTextPassword).getBytes(StandardCharsets.UTF_8)); + return authToken; + + } catch (JsonProcessingException ex) { + throw new UserServiceException(FAILED_ACCOUNT_RETRIEVAL_MESSAGE); + } catch (Exception e) { + throw new UserServiceException(AUTH_TOKEN_GENERATION_MESSAGE); + } + } + + public static void saveAndUpdateConfigs(final String indexName, final Client client, final CType cType, final SecurityDynamicConfiguration configuration) { + final IndexRequest ir = new IndexRequest(indexName); + final String id = cType.toLCString(); + + configuration.removeStatic(); + + try { + client.index(ir.id(id) + .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .setIfSeqNo(configuration.getSeqNo()) + .setIfPrimaryTerm(configuration.getPrimaryTerm()) + .source(id, XContentHelper.toXContent(configuration, XContentType.JSON, false))); + } catch (IOException e) { + throw ExceptionsHelper.convertToOpenSearchException(e); + } + } } diff --git a/src/test/java/org/opensearch/security/SecurityAdminTests.java b/src/test/java/org/opensearch/security/SecurityAdminTests.java index e7953c508a..bd032fc332 100644 --- a/src/test/java/org/opensearch/security/SecurityAdminTests.java +++ b/src/test/java/org/opensearch/security/SecurityAdminTests.java @@ -19,6 +19,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; @@ -37,6 +38,10 @@ import org.opensearch.security.test.helper.rest.RestHelper.HttpResponse; import org.opensearch.security.tools.SecurityAdmin; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; +import static org.junit.Assert.assertThrows; + public class SecurityAdminTests extends SingleClusterTest { @Test @@ -71,6 +76,66 @@ public void testSecurityAdmin() throws Exception { Assert.assertEquals(HttpStatus.SC_OK, (rh.executeGetRequest("_opendistro/_security/health?pretty")).getStatusCode()); } + @Test + public void testSecurityAdminHostnameVerificationEnforced() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-cacert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add("-cert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add("-key"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-icl"); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); + + final IOException expectedException = assertThrows(IOException.class, () -> SecurityAdmin.execute(argsAsList.toArray(new String[0]))); + final String expectedMessagePattern = "Certificate for <.+> doesn't match any of the subject alternative names: \\[node-.\\.example\\.com\\]"; + assertThat(expectedException.getMessage(), matchesPattern(expectedMessagePattern)); + } + + @Test + public void testSecurityAdminHostnameVerificationNotEnforced() throws Exception { + final Settings settings = Settings.builder() + .put("plugins.security.ssl.http.enabled",true) + .put("plugins.security.ssl.http.pemtrustedcas_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/root-ca.pem")) + .put("plugins.security.ssl.http.pemcert_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.crt.pem")) + .put("plugins.security.ssl.http.pemkey_filepath", FileHelper.getAbsoluteFilePathFromClassPath("securityadmin/node.key.pem")) + .putList("plugins.security.authcz.admin_dn", List.of("CN=kirk,OU=client,O=client,L=test,C=de")) + .build(); + setup(Settings.EMPTY, null, settings, false); + + final String prefix = getResourceFolder()==null?"securityadmin/":getResourceFolder()+"/securityadmin/"; + + List argsAsList = new ArrayList<>(); + argsAsList.add("-cacert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"root-ca.pem").toFile().getAbsolutePath()); + argsAsList.add("-cert"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.crt.pem").toFile().getAbsolutePath()); + argsAsList.add("-key"); + argsAsList.add(FileHelper.getAbsoluteFilePathFromClassPath(prefix+"kirk.key.pem").toFile().getAbsolutePath()); + argsAsList.add("-p"); + argsAsList.add(String.valueOf(clusterInfo.httpPort)); + argsAsList.add("-icl"); + addDirectoryPath(argsAsList, TEST_RESOURCE_ABSOLUTE_PATH); + argsAsList.add("-nhnv"); + + int returnCode = SecurityAdmin.execute(argsAsList.toArray(new String[0])); + Assert.assertEquals(0, returnCode); + } + @Test public void testSecurityAdminInvalidCert() throws Exception { final Settings settings = Settings.builder() diff --git a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java index a3e08d5234..fc61a3f127 100644 --- a/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java +++ b/src/test/java/org/opensearch/security/TransportUserInjectorIntegTest.java @@ -73,7 +73,7 @@ public void testSecurityUserInjection() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, true) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -128,7 +128,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { .put(ConfigConstants.SECURITY_UNSUPPORTED_INJECT_USER_ENABLED, false) .build(); setup(clusterNodeSettings, new DynamicSecurityConfig().setSecurityRolesMapping("roles_transport_inject_user.yml"), Settings.EMPTY); - final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) + final Settings tcSettings = AbstractSecurityUnitTest.nodeRolesSettings(Settings.builder(), false, false) .put(minimumSecuritySettings(Settings.EMPTY).get(0)) .put("cluster.name", clusterInfo.clustername) .put("path.data", "./target/data/" + clusterInfo.clustername + "/cert/data") @@ -148,7 +148,7 @@ public void testSecurityUserInjectionWithConfigDisabled() throws Exception { CreateIndexResponse cir = node.client().admin().indices().create(new CreateIndexRequest("captain-logs-1")).actionGet(); Assert.assertTrue(cir.isAcknowledged()); } - + // with invalid backend roles UserInjectorPlugin.injectedUser = "ttt|kkk"; try (Node node = new PluginAwareNode(false, tcSettings, Netty4ModulePlugin.class, diff --git a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java index 0502f078d9..ba094e23a1 100644 --- a/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java +++ b/src/test/java/org/opensearch/security/auditlog/integration/TestAuditlogImpl.java @@ -82,13 +82,13 @@ public static List doThenWaitForMessages(final Runnable action, fi Thread.sleep(100); if (missedMessages.size() != 0) { final String missedMessagesErrorMessage = new StringBuilder() - .append("Audit messages were missed! ") - .append("Found " + (missedMessages.size()) + " messages.") - .append("Messages found during this time: \n\n") - .append(missedMessages.stream() - .map(AuditMessage::toString) - .collect(Collectors.joining("\n"))) - .toString(); + .append("Audit messages were missed! ") + .append("Found " + (missedMessages.size()) + " messages.") + .append("Messages found during this time: \n\n") + .append(missedMessages.stream() + .map(AuditMessage::toString) + .collect(Collectors.joining("\n"))) + .toString(); throw new RuntimeException(missedMessagesErrorMessage); } @@ -155,13 +155,13 @@ public List getFoundMessages() { private static String createDetailMessage(final int expectedCount, final List foundMessages) { return new StringBuilder() - .append("Did not receive all " + expectedCount + " audit messages after a short wait. ") - .append("Missing " + (expectedCount - foundMessages.size()) + " messages.") - .append("Messages found during this time: \n\n") - .append(foundMessages.stream() - .map(AuditMessage::toString) - .collect(Collectors.joining("\n"))) - .toString(); + .append("Did not receive all " + expectedCount + " audit messages after a short wait. ") + .append("Missing " + (expectedCount - foundMessages.size()) + " messages.") + .append("Messages found during this time: \n\n") + .append(foundMessages.stream() + .map(AuditMessage::toString) + .collect(Collectors.joining("\n"))) + .toString(); } } } diff --git a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java index d755751e54..60c2ccde6f 100644 --- a/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java +++ b/src/test/java/org/opensearch/security/dlic/rest/api/UserApiTest.java @@ -12,6 +12,7 @@ package org.opensearch.security.dlic.rest.api; import java.net.URLEncoder; +import java.util.Base64; import java.util.List; import org.apache.hc.core5.http.Header; @@ -37,36 +38,38 @@ public class UserApiTest extends AbstractRestApiUnitTest { - private final String ENDPOINT; + private final String ENDPOINT; protected String getEndpointPrefix() { return PLUGINS_PREFIX; } + final int USER_SETTING_SIZE = 7 * 19; // Lines per account entry * number of accounts + private static final String ENABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String DISABLED_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"false\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"false\"}" + " }\n"; private static final String ENABLED_NOT_SERVICE_ACCOUNT_BODY = "{" - + " \"attributes\": { \"owner\": \"user_is_owner_1\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"false\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String PASSWORD_SERVICE = "{ \"password\" : \"test\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String HASH_SERVICE = "{ \"owner\" : \"test_owner\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; private static final String PASSWORD_HASH_SERVICE = "{ \"password\" : \"test\", \"hash\" : \"123\"," - + " \"attributes\": { \"owner\": \"test_owner\", " - + "\"isEnabled\": \"true\"}" + + " \"attributes\": { \"service\": \"true\", " + + "\"enabled\": \"true\"}" + " }\n"; public UserApiTest(){ @@ -86,7 +89,7 @@ public void testSecurityRoles() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); response = rh.executePatchRequest(ENDPOINT + "/internalusers", "[{ \"op\": \"add\", \"path\": \"/newuser\", \"value\": {\"password\": \"newuser\", \"opendistro_security_roles\": [\"opendistro_security_all_access\"] } }]", new Header[0]); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); @@ -99,9 +102,9 @@ public void testSecurityRoles() throws Exception { @Test public void testParallelPutRequests() throws Exception { - + setup(); - + rh.keystore = "restapi/kirk-keystore.jks"; rh.sendAdminCertificate = true; @@ -136,7 +139,7 @@ public void testUserApi() throws Exception { HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(); verifyPut(); verifyPatch(true); @@ -334,13 +337,8 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); - // Add enabled non-service account - response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", - ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); - Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); // Add service account with password -- Should Fail - response = rh.executePutRequest(ENDPOINT + "/internalusers/passwordService", PASSWORD_SERVICE, restAdminHeader); Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); @@ -409,6 +407,51 @@ private void verifyPatch(final boolean sendAdminCert, Header... restAdminHeader) Assert.assertTrue(settings.get("nagilum.hash").equals("")); } + private void verifyAuthToken(final boolean sendAdminCert, Header... restAdminHeader) throws Exception { + + // Add enabled service account then generate auth token + + rh.sendAdminCertificate = sendAdminCert; + HttpResponse response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceLive", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + rh.sendAdminCertificate = sendAdminCert; + response = rh.executeGetRequest(ENDPOINT + "/internalusers/happyServiceLive", restAdminHeader); + Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceLive/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + String tokenFromResponse = response.getBody(); + byte[] decodedResponse = Base64.getUrlDecoder().decode(tokenFromResponse); + String[] decodedResponseString = new String(decodedResponse).split(":", 2); + String username = decodedResponseString[0]; + String password = decodedResponseString[1]; + Assert.assertEquals("Username is: " + username,username, "happyServiceLive"); + + // Add disabled service account then try to get its auth token + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/happyServiceDead", + DISABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_CREATED, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/happyServiceDead/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + + // Add enabled non-service account + rh.sendAdminCertificate = sendAdminCert; + response = rh.executePutRequest(ENDPOINT + "/internalusers/user_is_owner_1", + ENABLED_NOT_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(HttpStatus.SC_CREATED, response.getStatusCode()); + + response = rh.executePostRequest(ENDPOINT + "/internalusers/user_is_owner_1/authtoken", + ENABLED_SERVICE_ACCOUNT_BODY, restAdminHeader); + Assert.assertEquals(response.getBody(), HttpStatus.SC_BAD_REQUEST, response.getStatusCode()); + + } + private void verifyRoles(final boolean sendAdminCert, Header... header) throws Exception { // wrong datatypes in roles file rh.sendAdminCertificate = sendAdminCert; @@ -494,7 +537,7 @@ public void testUserApiWithRestAdminPermissions() throws Exception { HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(restApiAdminHeader); verifyPut(restApiAdminHeader); verifyPatch(false, restApiAdminHeader); @@ -512,7 +555,7 @@ public void testUserApiWithRestInternalUsersAdminPermissions() throws Exception HttpResponse response = rh.executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString(), restApiInternalUsersAdminHeader); Assert.assertEquals(response.getBody(), HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); verifyGet(restApiInternalUsersAdminHeader); verifyPut(restApiInternalUsersAdminHeader); verifyPatch(false, restApiInternalUsersAdminHeader); @@ -541,7 +584,7 @@ public void testPasswordRules() throws Exception { .executeGetRequest("_plugins/_security/api/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); addUserWithPassword("tooshoort", "", HttpStatus.SC_BAD_REQUEST); addUserWithPassword("tooshoort", "123", HttpStatus.SC_BAD_REQUEST); @@ -621,7 +664,7 @@ public void testUserApiWithDots() throws Exception { .executeGetRequest(ENDPOINT + "/" + CType.INTERNALUSERS.toLCString()); Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); - Assert.assertEquals(133, settings.size()); + Assert.assertEquals(USER_SETTING_SIZE, settings.size()); addUserWithPassword(".my.dotuser0", "$2a$12$n5nubfWATfQjSYHiWtUyeOxMIxFInUHOAx8VMmGmxFNPGpaBmeB.m", HttpStatus.SC_CREATED); @@ -722,7 +765,7 @@ public void testUserApiForNonSuperAdmin() throws Exception { // Put reserved role is forbidden for non-superadmin response = rh.executePutRequest(ENDPOINT + "/internalusers/nagilum", "{ \"opendistro_security_roles\": [\"opendistro_security_reserved\"]}", - new Header[0]); + new Header[0]); Assert.assertEquals(HttpStatus.SC_FORBIDDEN, response.getStatusCode()); Settings settings = Settings.builder().loadFromSource(response.getBody(), XContentType.JSON).build(); Assert.assertEquals(settings.get("message"), "Resource 'opendistro_security_reserved' is read-only."); diff --git a/src/test/resources/securityadmin/certificate_generation.md b/src/test/resources/securityadmin/certificate_generation.md new file mode 100644 index 0000000000..ae60ae5a07 --- /dev/null +++ b/src/test/resources/securityadmin/certificate_generation.md @@ -0,0 +1,23 @@ +# Script to generate certificates for SecurityAdmin Tests + +``` +openssl genrsa -out root-ca-key.pem 2048 +openssl req -x509 -sha256 -new -nodes -key root-ca-key.pem -subj "/DC=com/DC=example/O=Example Com Inc./OU=Example Com Inc. Root CA/CN=Example Com Inc. Root CA" -days 3650 -out root-ca.pem +openssl genrsa -out signing-key.pem 2048 +openssl req -x509 -sha256 -new -nodes -CA root-ca.pem -CAkey root-ca-key.pem -key signing-key.pem -subj "/DC=com/DC=example/O=Example Com Inc./OU=Example Com Inc. Signing CA/CN=Example Com Inc. Signing CA" -days 3650 -out signing.pem + +openssl genrsa -out node-key-temp.pem 2048 +openssl pkcs8 -inform PEM -outform PEM -in node-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out node.key.pem +openssl req -new -key node.key.pem -subj "/C=DE/L=Test/O=Test/OU=SSL/CN=node-1.example.com" -out node.csr +openssl x509 -req -days 3650 -extfile <(printf "subjectAltName=DNS:node-1.example.com,IP:127.0.0.1") -in node.csr -out node.crt.pem -CA signing.pem -CAkey signing-key.pem + +# CN=kirk,OU=client,O=client,L=Test,C=DE +openssl genrsa -out kirk-key-temp.pem 2048 +openssl pkcs8 -inform PEM -outform PEM -in kirk-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out kirk.key.pem +openssl req -new -key kirk.key.pem -subj "/C=DE/L=Test/O=client/OU=client/CN=kirk" -out kirk.csr +openssl x509 -req -days 3650 -in kirk.csr -out kirk.crt.pem -CA signing.pem -CAkey signing-key.pem +``` + +For `kirk.crt.pem` and `node.crt.pem` all certificates in the chain including `root-ca.pem` and `signing.pem` need to be included in the file. + +When bundling the certificates together in the same file the root certificate is placed at the bottom and the lowest level certificate (the node certificate) on the top. diff --git a/src/test/resources/securityadmin/kirk.crt.pem b/src/test/resources/securityadmin/kirk.crt.pem new file mode 100644 index 0000000000..b126e36c56 --- /dev/null +++ b/src/test/resources/securityadmin/kirk.crt.pem @@ -0,0 +1,69 @@ +-----BEGIN CERTIFICATE----- +MIIDajCCAlICFCVxBZmleOHXHqoyn6dQlHVWZ/t8MA0GCSqGSIb3DQEBCwUAMIGV +MRMwEQYKCZImiZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZ +MBcGA1UECgwQRXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20g +SW5jLiBTaWduaW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25p +bmcgQ0EwHhcNMjMwNTAyMTc1NzA2WhcNMzMwNDI5MTc1NzA2WjBNMQswCQYDVQQG +EwJERTENMAsGA1UEBwwEVGVzdDEPMA0GA1UECgwGY2xpZW50MQ8wDQYDVQQLDAZj +bGllbnQxDTALBgNVBAMMBGtpcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQDFYEoC+qyqLKhNpSAj3qUfhGRNmoHlpDRG2Zq+wAx6e24pODNGtyrtswF7 +7Nf3HgODMrFMCg/gJC6U78VbI4hPO63E+nQr3Q2h7kdn7E4t1VJOUY4YFROyvayD +epDWmIGwer0H+Wd+7t6TrQod/Hj5do3og5IgBaK1AS4OExanmuJ10WrfzctS9dg4 +xY2RT7pmNWVeOA1IdkPRu5T7jr72n66jSuwqbTiS+vQHdqgZsXUC+DtvMtRmRYo0 +QT4nndNYA72FFKH9bmKiLvNyeTMAn45fE+ebiZGFTcK7e5hZ+l6YTWvUlGoS54t+ +2kNxTaHl3NXr9KCwF7lT/HoS42RnAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFNe +E2ClU0OxVk5nWmQUnr3MmsFDaBe/0CfGBHLcixqRenaGlwGcrUB4B2mYF3xkGRhF +xrd2lJy3bMxYxl5Zp63atdK5s7JnHSatPFGxwJJ/9BRDeZtx0X42mCspb1ho+0yV +bUVYOiy3G/Nt7erfRb8a6ZlWk3Ri2HZ/OG3jQnQCLPstNZ5DeRlM33ltiHj3EDlz +PyRgp+n89FLKZjImY4zJdjBKdfky2PKKZGJJ+57L+fIu/2TR17Qeaxf4cRa6DWtX +8fwRHkrj9MVLvdASLwFKfdEefw/uTPLigwdrydjy+AFogfpmBvJ9CXqCq81lSROr +Pzbo7NaChtZ6Mxgd3fU= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUC1KT/DcaMNL7fjY7g361424pyWowDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGVMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxgE4w07GMwoHzxYlXSmNYv3Lx +iPvftzWrFfA15dyHcCrYnoTN+re5uTi8L40Obzr7BzL41mFHED85EfvGYl02MCai +jA2MHE7vh1JxKJ4oLMI0jtXYaBPXDb96qcfCMC+Vce0RH+I815nip1Amf/M/jMov +KlGGYSipa4otfj8ZrinMItFucpY/mEsFgzO+yQ+Go2gzyJlNeJXIuhsfdvkq76X8 ++oNrltHL1f/Y1HP7qV5eZn6uODSesHGK1VCLg7UIRk3aZRmAo5ZwTV5xb3rKhxDJ +mvBtK77TcG9CA3MXOM204G7n85aYN+Xrb+c5xQjzsR6bo1O4I37fn8sSP9/hAgMB +AAGjUzBRMB0GA1UdDgQWBBTotToiEVpUwaXfM1lPhT4Cf+wxpzAfBgNVHSMEGDAW +gBTeMJiA4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAfiHlJtB+NWDS68BRnpil500PPQzB0edqhO3h/2tnXmxtXKbH/ +0sKgCvPn3tEK8y5WrzC2UDB2F594TlGUWHiMwy1SwMkrP8gUpDS2syisaORyQ7/1 +aOktoD0eBZEWFJtGLCPR7uz/KtbZo6QsAZYhxqdE9Dn2Dw2d0YcIFogCG9ohAIWm +6Hss2CTs2z1YKXJqYb+o0jwFDRF0H7cd5HeFaMQIkz9hLURInUU3JYemONzaxc26 +peelfPOmDySzaQjqn4lQRXXze79fMs3G5hD+f8WvQqtJkSGS1CXnLvZ8PBf12jxZ +0SkzxLWyKOSz6M3tXonaxsLMXF+4dM48+NOm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE----- diff --git a/src/test/resources/securityadmin/kirk.key.pem b/src/test/resources/securityadmin/kirk.key.pem new file mode 100644 index 0000000000..ea7127c156 --- /dev/null +++ b/src/test/resources/securityadmin/kirk.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFYEoC+qyqLKhN +pSAj3qUfhGRNmoHlpDRG2Zq+wAx6e24pODNGtyrtswF77Nf3HgODMrFMCg/gJC6U +78VbI4hPO63E+nQr3Q2h7kdn7E4t1VJOUY4YFROyvayDepDWmIGwer0H+Wd+7t6T +rQod/Hj5do3og5IgBaK1AS4OExanmuJ10WrfzctS9dg4xY2RT7pmNWVeOA1IdkPR +u5T7jr72n66jSuwqbTiS+vQHdqgZsXUC+DtvMtRmRYo0QT4nndNYA72FFKH9bmKi +LvNyeTMAn45fE+ebiZGFTcK7e5hZ+l6YTWvUlGoS54t+2kNxTaHl3NXr9KCwF7lT +/HoS42RnAgMBAAECggEADtRZOzgSWQbZ7luFuqwzw9ZyotIFCHf55Yjb85ECXwF/ +GWG7mIiSlSFp7yGwaES9BtJ8N7ZZ0wFk7pPFRD+7MhjNyYr3x4PoTk5U1x4OEauB +b5j5EB4lSLyvhYFj+Huk4tmV8k9u0z6nQnkx1Wbuv++EYf/grr89plPcXfpZLWZ3 +0y84mR/ENYXtbtPSwLgMR6OY+q36hqc2a9+si4j/P0jf37jfyE8AlTGWEGdKQaN8 +iLLfyXpKxHn8zweZ3kWzOS2mZhya15S9F5YUS+d3TOt1J/3EcxMu/UJMxWBN/Tea +JvMZUgMTVyg4RN/MOzvjOUDKOUdgGIdPI6G9/nfs4QKBgQDzQPqxJBgy5BUVXOAO +/lT4QvMFnKiPxedDkSHHwPXwx+dqL6Bk/1mco3P6VDX5OfB4xp0NWkjekfyU01Ab +jvULO2MaH3MEiQuDuYs3NDY+lT/YGdlB7Asd8AMr8Clu5DHTjyz6KefS+stOfbrn +smAmpk/TdTM/lQfhZPiU4FVhSwKBgQDPt+dfqw2zm+T5g3KeJ/Ej5UsThiNJseIh +KF5NlPrRfsdIybkcbDtIiHlJ3qVn6Tq8zEvEANdwYd3/7orFilUahc9zlA7z9O2L +tRu8I9zjI3vjArzO0Wkh1NxDoGzQX6giTHDpL7+m/bjc3pj1RXjlP8IRHx7j6w3H ++ciKuTaz1QKBgG1UBBg/f7zHtA4g6vbyKiBWfsFD8qKDsPg2L3eG60KnpgOcmjsq +ZQ04jXSyCnwUJVcy9P0+Wcfm1x3Qh42LR+kfbOAdyGT+bzVp2/8YsVSZYdNvcqzl +OO3gpJxH2WdkmlxaWj2pPe8eFugVLD7cdciJMRF5+GmYQq1z4yGOXfFXAoGAV2yA +fhxhPOntGjL/x57p+ACmc4YuTfMHSItT/XUph4jDWVhFh7fpz6JY4gVKOozIAvQ9 +IzZzdkJKjFAaqf+JyArvgCadkIHShM1p6epyKksh9i6NxsIObIXJWtEnWyAXhLAF +ia9mC2OYLaWmXPyrYFlQVaJyftzMRRFVHUXMxy0CgYBT2+xAwAMxj4HVtwBthQXH +0Akawz9Gvw7/BJOGBRzJMM6H5qqejWbA/FKmvySdK2IrQ6dISjrVMr7VtjMrwumM +JO3T54O4iuFpNck1z+d4BZjvWpsxpPRg4RktwhMtJ2BZYh43aI4pFfxs6YFga+ZK +vMm+70bHS8AskkmaxAFzHw== +-----END PRIVATE KEY----- diff --git a/src/test/resources/securityadmin/node.crt.pem b/src/test/resources/securityadmin/node.crt.pem new file mode 100644 index 0000000000..adb2996edb --- /dev/null +++ b/src/test/resources/securityadmin/node.crt.pem @@ -0,0 +1,71 @@ +-----BEGIN CERTIFICATE----- +MIID4TCCAsmgAwIBAgIUKmE8oZm3QVdGZWbKLY8S4F05UugwDQYJKoZIhvcNAQEL +BQAwgZUxEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSQwIgYDVQQLDBtFeGFtcGxl +IENvbSBJbmMuIFNpZ25pbmcgQ0ExJDAiBgNVBAMMG0V4YW1wbGUgQ29tIEluYy4g +U2lnbmluZyBDQTAeFw0yMzA1MDIxODI0MzFaFw0zMzA0MjkxODI0MzFaMFYxCzAJ +BgNVBAYTAkRFMQ0wCwYDVQQHDARUZXN0MQ0wCwYDVQQKDARUZXN0MQwwCgYDVQQL +DANTU0wxGzAZBgNVBAMMEm5vZGUtMS5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKfvsczlQQAPg0iivG6O5kazZjS8z3dM6b/hmtw7 +NSlihZeXgPOoyd1BttCNB1fo/TsnikHggWRVHj8v01kNJtKmvgdtTYJIUJEZJdEH +NHFXmxR+3YhmZr1qKdkihB0Z7rv+oYrHe2MOg8jtt3VPZuOIZZXfIJWw93CajXaO +zNv/sNdPdaPI8tTJIzihYNVcMtx5koPS66xB2Rwp06MfvJ568BAXxl2QSRjWFX4v +CFOzKL8nZtr779HemWFABzCllzff7xLcjWbkaoTk7gPrgh6vzW0hE2pLAbNnlre3 +rommG/zzKgmytZ+PTvbEyhiuNjVgcCG2vhGu8myRYgoGZw8CAwEAAaNnMGUwIwYD +VR0RBBwwGoISbm9kZS0xLmV4YW1wbGUuY29thwR/AAABMB0GA1UdDgQWBBQ04XN1 +I1bs3kMQrOhNfL/rxZpeUzAfBgNVHSMEGDAWgBTotToiEVpUwaXfM1lPhT4Cf+wx +pzANBgkqhkiG9w0BAQsFAAOCAQEADAaqiuJBzK8/PYN30Xx6QnKnwj4GlzzSVY/O +AzNgfUL7QH1k6tBNlgyFom2UozIZvFuCdfJg6X5+BFWXSh4LuvODzudWUQM3bjh4 +JZJYOTTOmT6lBi+KAPbwpirj+XUVvqNlAew81b0n63uWh8yeGBa/5G0G28Dyu6Ma +E1jZDmKVLZqGByhM46IUxov7aDFk4elM2nH0JVZpFPbTjjN5bLefqwIW8Y7NjU9s +8Zvv4fFtsoKqPhFhPAMTjlg7bMOqtWIw95w0L1fRz9yCuN0LAjlqCFKDGLmaWqFK +GaQKbw86KKrPfl8y9yqBgfUN69NmE5qhgmobu0so5Yy5V69BwQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgIUC1KT/DcaMNL7fjY7g361424pyWowDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGVMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEkMCIGA1UECwwbRXhhbXBsZSBDb20gSW5jLiBTaWdu +aW5nIENBMSQwIgYDVQQDDBtFeGFtcGxlIENvbSBJbmMuIFNpZ25pbmcgQ0EwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxgE4w07GMwoHzxYlXSmNYv3Lx +iPvftzWrFfA15dyHcCrYnoTN+re5uTi8L40Obzr7BzL41mFHED85EfvGYl02MCai +jA2MHE7vh1JxKJ4oLMI0jtXYaBPXDb96qcfCMC+Vce0RH+I815nip1Amf/M/jMov +KlGGYSipa4otfj8ZrinMItFucpY/mEsFgzO+yQ+Go2gzyJlNeJXIuhsfdvkq76X8 ++oNrltHL1f/Y1HP7qV5eZn6uODSesHGK1VCLg7UIRk3aZRmAo5ZwTV5xb3rKhxDJ +mvBtK77TcG9CA3MXOM204G7n85aYN+Xrb+c5xQjzsR6bo1O4I37fn8sSP9/hAgMB +AAGjUzBRMB0GA1UdDgQWBBTotToiEVpUwaXfM1lPhT4Cf+wxpzAfBgNVHSMEGDAW +gBTeMJiA4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQAfiHlJtB+NWDS68BRnpil500PPQzB0edqhO3h/2tnXmxtXKbH/ +0sKgCvPn3tEK8y5WrzC2UDB2F594TlGUWHiMwy1SwMkrP8gUpDS2syisaORyQ7/1 +aOktoD0eBZEWFJtGLCPR7uz/KtbZo6QsAZYhxqdE9Dn2Dw2d0YcIFogCG9ohAIWm +6Hss2CTs2z1YKXJqYb+o0jwFDRF0H7cd5HeFaMQIkz9hLURInUU3JYemONzaxc26 +peelfPOmDySzaQjqn4lQRXXze79fMs3G5hD+f8WvQqtJkSGS1CXnLvZ8PBf12jxZ +0SkzxLWyKOSz6M3tXonaxsLMXF+4dM48+NOm +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE----- diff --git a/src/test/resources/securityadmin/node.key.pem b/src/test/resources/securityadmin/node.key.pem new file mode 100644 index 0000000000..fd82ed2d2c --- /dev/null +++ b/src/test/resources/securityadmin/node.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn77HM5UEAD4NI +orxujuZGs2Y0vM93TOm/4ZrcOzUpYoWXl4DzqMndQbbQjQdX6P07J4pB4IFkVR4/ +L9NZDSbSpr4HbU2CSFCRGSXRBzRxV5sUft2IZma9ainZIoQdGe67/qGKx3tjDoPI +7bd1T2bjiGWV3yCVsPdwmo12jszb/7DXT3WjyPLUySM4oWDVXDLceZKD0uusQdkc +KdOjH7yeevAQF8ZdkEkY1hV+LwhTsyi/J2ba++/R3plhQAcwpZc33+8S3I1m5GqE +5O4D64Ier81tIRNqSwGzZ5a3t66Jphv88yoJsrWfj072xMoYrjY1YHAhtr4RrvJs +kWIKBmcPAgMBAAECggEAMkXkISVkHwOF1qG47RPkRbgA2brIFLu2ohWEiXdEA96V +hXr6RHb77ztz4dzGHQAHhsTgc7YkpgeBJYNIrrjsLVVzP7/t2xmQ3M79biTNAz0p +lKoh4WpeSUfVvUXC7P9NY4PnkicDffTjaKwZJoodj/HOD16bX5R5joEF5j77fsQA +3DkCD9JxNKxRXz0lH5ICExItQEFweDDT749LunEBC2BDHQ6UDO1JGC7Js9D/ri9C +sNB/1OMO0AV/g7V0pMD6GlkO48UVYK9kxBZQAgI9YiRtlkVyD4Uy/CxzptJepkRW +uqx4lD5F98bKpwleqsPNkFCj1ifXd8WAFAD4wkC8mQKBgQC8+JLgFVSmBzzbQSmM +3iaqcm2wkhWIESV/rEtHvgJCtBZYT1NBekWdZmODfyU0Q2E/lPAYH2LgT+jgoMZ/ +JkvkrMAv8Cgh4J3Jklku29R8YyTYdouje62a69p6u02rbZFDQEeVfVVEHApnngpd +YVmOm9eSBTBya+1lpO3H394UwwKBgQDjgRNhLoO8ce1V+Txmad1/rwfst364adXf +UsfxTIP6wJTQXdm1ZVeqswKaGsVAaq6fX83XLT1H47c5newoll3V8KPLjAiAy/VR +MDMKj2Rr4ojOshdiN+5wiPOVAgmrGg8eTH0wdNchtUAbI31fzzC9n8uYl146bax3 +I4NwyOMPxQKBgQCOdhtMQeh57lTrullXsJaXwwJ8rfT7immpsbtjD5Tmsptx4gOT +Bln7CpiVJsJmfzGOXHsQxICnOLcIuUxLyRRIBhAxU6z9tTdfIiyHzgSH7bp2UhB9 +pBzCAXLJOfGY/lYXzBrrUPx6B2W0rgmEUoLQpx5CIBVg/YqQKWF1YIktPwKBgQDF +7nSn5koi15O/atoL2CsnfWaNoo+TbjDu3RyraQCiVo6iQiS5VvRQxPGMlaHri2Vl +r3psrSVVuF6euDDQlxIIohY/bxOuysQh4Kdnlp2t5ydTfUou366JJf2WNHGo9UEW +AUIhuGW7I/AkLFpV0vL6513A4mDOwMB93t3qcDxsaQKBgAyXuK+6SpVkGj0HgGBH +PyAzkoAoTcnv5gBDs/p4MiAigkAl3WqhoouWYUP2nCiQZmA4rbXcNVdClebHBD64 +jmgfXo8E2i+MHhbAWyNSWrHL7punrcdOFETw1JrvFxhPORnDLwpyfSL9DC3JfNmD +RBM+Z8nCKBWcmxtCpMqmdbdR +-----END PRIVATE KEY----- diff --git a/src/test/resources/securityadmin/root-ca.pem b/src/test/resources/securityadmin/root-ca.pem new file mode 100644 index 0000000000..eed8cd0b06 --- /dev/null +++ b/src/test/resources/securityadmin/root-ca.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIUUe5xSfjzHNOkaqCRf5AIYXQQM3cwDQYJKoZIhvcNAQEL +BQAwgY8xEzARBgoJkiaJk/IsZAEZFgNjb20xFzAVBgoJkiaJk/IsZAEZFgdleGFt +cGxlMRkwFwYDVQQKDBBFeGFtcGxlIENvbSBJbmMuMSEwHwYDVQQLDBhFeGFtcGxl +IENvbSBJbmMuIFJvb3QgQ0ExITAfBgNVBAMMGEV4YW1wbGUgQ29tIEluYy4gUm9v +dCBDQTAeFw0yMzA1MDIxNzU3MDVaFw0zMzA0MjkxNzU3MDVaMIGPMRMwEQYKCZIm +iZPyLGQBGRYDY29tMRcwFQYKCZImiZPyLGQBGRYHZXhhbXBsZTEZMBcGA1UECgwQ +RXhhbXBsZSBDb20gSW5jLjEhMB8GA1UECwwYRXhhbXBsZSBDb20gSW5jLiBSb290 +IENBMSEwHwYDVQQDDBhFeGFtcGxlIENvbSBJbmMuIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDTQTojW8vphvADeNvMhFyfV0p7EA77bxQf +XBzbwGXqjeS4X1WeisbOi+HvBvrmg3olzzA2vVH+5gT+5S6Q62BX4oyCyyqoK/3n +gc+8JBLGpACEeLQotLE238L8wzM+L4WblZretvAi85JZ09ur0yZ7C6QE3QeGMRrL +9OjHuCtzSAJO3t8uuf+IwDMM/8k822reski+iVsNxHVsBkTDFbHbVKFuHadqaMRp +G2wFINnSi4L/hMAQtIvJasjiW26kZKLd8WckDYGgZaFc1l46RR7Pj/lULBCdc86X +INuL1M411RjB08tqMTTjqvQhMWlv+qVkoVlyx97iFKWo5gNz2FbRAgMBAAGjUzBR +MB0GA1UdDgQWBBTeMJiA4CPf0XcafDPDTzO+iylLfzAfBgNVHSMEGDAWgBTeMJiA +4CPf0XcafDPDTzO+iylLfzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQAz7tZirV9htIc3bNE0IxJ1F1oMfQChH4kgZiw8coLZ6dElzUzBhF3JZEyL +CDxnI0Q94l+Wg6KGUNSAqlYcXbcWYhgml0B6oCGp30GlyhbK16OrapKcHitjYoKB +rNtf5H4Ks0/I9YK9NKCLrFPsp9Qt5qStQuhZcumJbct8irXLPmrVTLKrIqCkBmP5 +7P7v9Vud5/TxWTjLUZo+eS/AkJurOdDZDf+lVmpcbsez6HsSusNu5E7BDwLcPIFQ +MukDp/SRLInq8I8cA5t5U+tiQgsUCdLMIaLQ72EJuCId9XB8oyhP/rOJy+xwNnLW +ZngkAWtN8JWNoaA8FkLYbJOGLikP +-----END CERTIFICATE-----