From 8a92e579b855ad024963f6a0711b85f7022ff7d1 Mon Sep 17 00:00:00 2001 From: Peter Nied Date: Fri, 16 Sep 2022 17:13:55 -0500 Subject: [PATCH] [Feature/Identity] Initial interface for native authentication support (#4515) * Identity and Auth Manager Adding a noop implementation of an authentication manager for use tracking identity information within the OpenSearch systems. Also see - https://github.com/opensearch-project/OpenSearch/issues/4514 - https://github.com/opensearch-project/OpenSearch/issues/3846 - https://github.com/opensearch-project/opensearch-sdk-java/blob/main/SECURITY.md Signed-off-by: Peter Nied --- .ci/bwcVersions | 1 + CHANGELOG.md | 7 +- .../SegmentReplicationSnapshotIT.java | 279 ++++++++++++++++++ .../src/main/java/org/opensearch/Version.java | 1 + .../identity/AuthenticationManager.java | 23 ++ .../org/opensearch/identity/Identity.java | 36 +++ .../org/opensearch/identity/Principals.java | 36 +++ .../opensearch/identity/StringPrincipal.java | 51 ++++ .../java/org/opensearch/identity/Subject.java | 25 ++ .../noop/NoopAuthenticationManager.java | 26 ++ .../opensearch/identity/noop/NoopSubject.java | 46 +++ .../identity/noop/package-info.java | 10 + .../org/opensearch/identity/package-info.java | 10 + .../opensearch/index/mapper/FieldMapper.java | 29 +- .../main/java/org/opensearch/node/Node.java | 6 + .../opensearch/identity/IdentityTests.java | 25 ++ .../identity/StringPrincipalTests.java | 49 +++ .../noop/NoopAuthenticationManagerTests.java | 29 ++ .../identity/noop/NoopSubjectTests.java | 34 +++ 19 files changed, 711 insertions(+), 12 deletions(-) create mode 100644 server/src/internalClusterTest/java/org/opensearch/snapshots/SegmentReplicationSnapshotIT.java create mode 100644 server/src/main/java/org/opensearch/identity/AuthenticationManager.java create mode 100644 server/src/main/java/org/opensearch/identity/Identity.java create mode 100644 server/src/main/java/org/opensearch/identity/Principals.java create mode 100644 server/src/main/java/org/opensearch/identity/StringPrincipal.java create mode 100644 server/src/main/java/org/opensearch/identity/Subject.java create mode 100644 server/src/main/java/org/opensearch/identity/noop/NoopAuthenticationManager.java create mode 100644 server/src/main/java/org/opensearch/identity/noop/NoopSubject.java create mode 100644 server/src/main/java/org/opensearch/identity/noop/package-info.java create mode 100644 server/src/main/java/org/opensearch/identity/package-info.java create mode 100644 server/src/test/java/org/opensearch/identity/IdentityTests.java create mode 100644 server/src/test/java/org/opensearch/identity/StringPrincipalTests.java create mode 100644 server/src/test/java/org/opensearch/identity/noop/NoopAuthenticationManagerTests.java create mode 100644 server/src/test/java/org/opensearch/identity/noop/NoopSubjectTests.java diff --git a/.ci/bwcVersions b/.ci/bwcVersions index 1dc8dc955f7c6..e82101896818e 100644 --- a/.ci/bwcVersions +++ b/.ci/bwcVersions @@ -51,4 +51,5 @@ BWC_VERSION: - "2.2.1" - "2.2.2" - "2.3.0" + - "2.3.1" - "2.4.0" diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a47bbff73a1a..e55703031384f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - Update previous release bwc version to 2.4.0 ([#4455](https://github.com/opensearch-project/OpenSearch/pull/4455)) - 2.3.0 release notes ([#4457](https://github.com/opensearch-project/OpenSearch/pull/4457)) - Added missing javadocs for `:distribution:tools` modules ([#4483](https://github.com/opensearch-project/OpenSearch/pull/4483)) +- Add BWC version 2.3.1 ([#4513](https://github.com/opensearch-project/OpenSearch/pull/4513)) +- [Segment Replication] Add snapshot and restore tests for segment replication feature ([#3993](https://github.com/opensearch-project/OpenSearch/pull/3993)) +- [Security] Initial interface for native authentication support ([#4515](https://github.com/opensearch-project/OpenSearch/pull/4515)) + ### Dependencies - Bumps `reactive-streams` from 1.0.3 to 1.0.4 @@ -64,6 +68,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - `opensearch.bat` fails to execute when install path includes spaces ([#4362](https://github.com/opensearch-project/OpenSearch/pull/4362)) - Getting security exception due to access denied 'java.lang.RuntimePermission' 'accessDeclaredMembers' when trying to get snapshot with S3 IRSA ([#4469](https://github.com/opensearch-project/OpenSearch/pull/4469)) - Fixed flaky test `ResourceAwareTasksTests.testTaskIdPersistsInThreadContext` ([#4484](https://github.com/opensearch-project/OpenSearch/pull/4484)) +- Fixed the ignore_malformed setting to also ignore objects ([#4494](https://github.com/opensearch-project/OpenSearch/pull/4494)) ### Security - CVE-2022-25857 org.yaml:snakeyaml DOS vulnerability ([#4341](https://github.com/opensearch-project/OpenSearch/pull/4341)) @@ -88,4 +93,4 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) [Unreleased]: https://github.com/opensearch-project/OpenSearch/compare/2.2.0...HEAD -[2.x]: https://github.com/opensearch-project/OpenSearch/compare/2.2.0...2.x \ No newline at end of file +[2.x]: https://github.com/opensearch-project/OpenSearch/compare/2.2.0...2.x diff --git a/server/src/internalClusterTest/java/org/opensearch/snapshots/SegmentReplicationSnapshotIT.java b/server/src/internalClusterTest/java/org/opensearch/snapshots/SegmentReplicationSnapshotIT.java new file mode 100644 index 0000000000000..d92f2af3f4bfd --- /dev/null +++ b/server/src/internalClusterTest/java/org/opensearch/snapshots/SegmentReplicationSnapshotIT.java @@ -0,0 +1,279 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.snapshots; + +import com.carrotsearch.randomizedtesting.RandomizedTest; +import org.junit.BeforeClass; +import org.opensearch.action.admin.cluster.snapshots.create.CreateSnapshotResponse; +import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotRequestBuilder; +import org.opensearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse; +import org.opensearch.action.admin.indices.delete.DeleteIndexRequest; +import org.opensearch.action.admin.indices.settings.get.GetSettingsRequest; +import org.opensearch.action.admin.indices.settings.get.GetSettingsResponse; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.cluster.metadata.IndexMetadata; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.FeatureFlags; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.indices.replication.common.ReplicationType; +import org.opensearch.rest.RestStatus; +import org.opensearch.test.BackgroundIndexer; +import org.opensearch.test.InternalTestCluster; +import org.opensearch.test.OpenSearchIntegTestCase; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked; +import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount; + +@OpenSearchIntegTestCase.ClusterScope(scope = OpenSearchIntegTestCase.Scope.TEST, numDataNodes = 0) +public class SegmentReplicationSnapshotIT extends AbstractSnapshotIntegTestCase { + private static final String INDEX_NAME = "test-segrep-idx"; + private static final String RESTORED_INDEX_NAME = INDEX_NAME + "-restored"; + private static final int SHARD_COUNT = 1; + private static final int REPLICA_COUNT = 1; + private static final int DOC_COUNT = 1010; + + private static final String REPOSITORY_NAME = "test-segrep-repo"; + private static final String SNAPSHOT_NAME = "test-segrep-snapshot"; + + @BeforeClass + public static void assumeFeatureFlag() { + assumeTrue("Segment replication Feature flag is enabled", Boolean.parseBoolean(System.getProperty(FeatureFlags.REPLICATION_TYPE))); + } + + public Settings segRepEnableIndexSettings() { + return getShardSettings().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.SEGMENT).build(); + } + + public Settings docRepEnableIndexSettings() { + return getShardSettings().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.DOCUMENT).build(); + } + + public Settings.Builder getShardSettings() { + return Settings.builder() + .put(super.indexSettings()) + .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, SHARD_COUNT) + .put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, REPLICA_COUNT); + } + + public Settings restoreIndexSegRepSettings() { + return Settings.builder().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.SEGMENT).build(); + } + + public Settings restoreIndexDocRepSettings() { + return Settings.builder().put(IndexMetadata.SETTING_REPLICATION_TYPE, ReplicationType.DOCUMENT).build(); + } + + @Override + protected boolean addMockInternalEngine() { + return false; + } + + public void ingestData(int docCount, String indexName) throws Exception { + try ( + BackgroundIndexer indexer = new BackgroundIndexer( + indexName, + "_doc", + client(), + -1, + RandomizedTest.scaledRandomIntBetween(2, 5), + false, + random() + ) + ) { + indexer.start(docCount); + waitForDocs(docCount, indexer); + refresh(indexName); + } + } + + // Start cluster with provided settings and return the node names as list + public List startClusterWithSettings(Settings indexSettings, int replicaCount) throws Exception { + // Start primary + final String primaryNode = internalCluster().startNode(); + List nodeNames = new ArrayList<>(); + nodeNames.add(primaryNode); + for (int i = 0; i < replicaCount; i++) { + nodeNames.add(internalCluster().startNode()); + } + createIndex(INDEX_NAME, indexSettings); + ensureGreen(INDEX_NAME); + // Ingest data + ingestData(DOC_COUNT, INDEX_NAME); + return nodeNames; + } + + public void createSnapshot() { + // Snapshot declaration + Path absolutePath = randomRepoPath().toAbsolutePath(); + // Create snapshot + createRepository(REPOSITORY_NAME, "fs", absolutePath); + CreateSnapshotResponse createSnapshotResponse = client().admin() + .cluster() + .prepareCreateSnapshot(REPOSITORY_NAME, SNAPSHOT_NAME) + .setWaitForCompletion(true) + .setIndices(INDEX_NAME) + .get(); + assertThat( + createSnapshotResponse.getSnapshotInfo().successfulShards(), + equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()) + ); + assertThat(createSnapshotResponse.getSnapshotInfo().state(), equalTo(SnapshotState.SUCCESS)); + } + + public RestoreSnapshotResponse restoreSnapshotWithSettings(Settings indexSettings) { + RestoreSnapshotRequestBuilder builder = client().admin() + .cluster() + .prepareRestoreSnapshot(REPOSITORY_NAME, SNAPSHOT_NAME) + .setWaitForCompletion(false) + .setRenamePattern(INDEX_NAME) + .setRenameReplacement(RESTORED_INDEX_NAME); + if (indexSettings != null) { + builder.setIndexSettings(indexSettings); + } + return builder.get(); + } + + public void testRestoreOnSegRep() throws Exception { + // Start cluster with one primary and one replica node + startClusterWithSettings(segRepEnableIndexSettings(), 1); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME)); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT"); + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT); + } + + public void testSnapshotOnSegRep_RestoreOnSegRepDuringIngestion() throws Exception { + startClusterWithSettings(segRepEnableIndexSettings(), 1); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME)); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + ingestData(5000, RESTORED_INDEX_NAME); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT"); + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT + 5000); + } + + public void testSnapshotOnDocRep_RestoreOnSegRep() throws Exception { + startClusterWithSettings(docRepEnableIndexSettings(), 1); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexSegRepSettings()); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT"); + + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT); + } + + public void testSnapshotOnSegRep_RestoreOnDocRep() throws Exception { + // Start a cluster with one primary and one replica + startClusterWithSettings(segRepEnableIndexSettings(), 1); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexDocRepSettings()); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "DOCUMENT"); + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT); + } + + public void testSnapshotOnDocRep_RestoreOnDocRep() throws Exception { + startClusterWithSettings(docRepEnableIndexSettings(), 1); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(restoreIndexDocRepSettings()); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "DOCUMENT"); + + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT); + } + + public void testRestoreOnReplicaNode() throws Exception { + List nodeNames = startClusterWithSettings(segRepEnableIndexSettings(), 1); + final String primaryNode = nodeNames.get(0); + createSnapshot(); + // Delete index + assertAcked(client().admin().indices().delete(new DeleteIndexRequest(INDEX_NAME)).get()); + assertFalse("index [" + INDEX_NAME + "] should have been deleted", indexExists(INDEX_NAME)); + + // stop the primary node so that restoration happens on replica node + internalCluster().stopRandomNode(InternalTestCluster.nameFilter(primaryNode)); + + RestoreSnapshotResponse restoreSnapshotResponse = restoreSnapshotWithSettings(null); + + // Assertions + assertThat(restoreSnapshotResponse.status(), equalTo(RestStatus.ACCEPTED)); + internalCluster().startNode(); + ensureGreen(RESTORED_INDEX_NAME); + GetSettingsResponse settingsResponse = client().admin() + .indices() + .getSettings(new GetSettingsRequest().indices(RESTORED_INDEX_NAME)) + .get(); + assertEquals(settingsResponse.getSetting(RESTORED_INDEX_NAME, "index.replication.type"), "SEGMENT"); + SearchResponse resp = client().prepareSearch(RESTORED_INDEX_NAME).setQuery(QueryBuilders.matchAllQuery()).get(); + assertHitCount(resp, DOC_COUNT); + } +} diff --git a/server/src/main/java/org/opensearch/Version.java b/server/src/main/java/org/opensearch/Version.java index 978f0ee2186f2..9c53a0f449a40 100644 --- a/server/src/main/java/org/opensearch/Version.java +++ b/server/src/main/java/org/opensearch/Version.java @@ -98,6 +98,7 @@ public class Version implements Comparable, ToXContentFragment { public static final Version V_2_2_1 = new Version(2020199, org.apache.lucene.util.Version.LUCENE_9_3_0); public static final Version V_2_2_2 = new Version(2020299, org.apache.lucene.util.Version.LUCENE_9_3_0); public static final Version V_2_3_0 = new Version(2030099, org.apache.lucene.util.Version.LUCENE_9_3_0); + public static final Version V_2_3_1 = new Version(2030199, org.apache.lucene.util.Version.LUCENE_9_3_0); public static final Version V_2_4_0 = new Version(2040099, org.apache.lucene.util.Version.LUCENE_9_3_0); public static final Version V_3_0_0 = new Version(3000099, org.apache.lucene.util.Version.LUCENE_9_4_0); public static final Version CURRENT = V_3_0_0; diff --git a/server/src/main/java/org/opensearch/identity/AuthenticationManager.java b/server/src/main/java/org/opensearch/identity/AuthenticationManager.java new file mode 100644 index 0000000000000..e5dfdd972798d --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/AuthenticationManager.java @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +/** + * Authentication management for OpenSearch. + * + * Retrieve the current subject or switch to a subject + * */ +public interface AuthenticationManager { + + /** + * Get the current subject + * */ + public Subject getSubject(); + +} diff --git a/server/src/main/java/org/opensearch/identity/Identity.java b/server/src/main/java/org/opensearch/identity/Identity.java new file mode 100644 index 0000000000000..c7eba88f42733 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/Identity.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +/** + * Application wide access for identity systems + */ +public final class Identity { + private static AuthenticationManager AUTH_MANAGER = null; + + /** + * Do not allow instances of this class to be created + */ + private Identity() {} + + /** + * Gets the Authentication Manager for this application + */ + public static AuthenticationManager getAuthManager() { + return AUTH_MANAGER; + } + + /** + * Gets the Authentication Manager for this application + */ + public static void setAuthManager(final AuthenticationManager authManager) { + AUTH_MANAGER = authManager; + } + +} diff --git a/server/src/main/java/org/opensearch/identity/Principals.java b/server/src/main/java/org/opensearch/identity/Principals.java new file mode 100644 index 0000000000000..a70e107d68bb5 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/Principals.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import java.security.Principal; + +/** + * Available OpenSearch internal principals + */ +public enum Principals { + + /** + * Represents a principal which has not been authenticated + */ + UNAUTHENTICATED(new StringPrincipal("Unauthenticated")); + + private final Principal principal; + + private Principals(final Principal principal) { + this.principal = principal; + } + + /** + * Returns the underlying principal for this + */ + public Principal getPrincipal() { + return principal; + } + +} diff --git a/server/src/main/java/org/opensearch/identity/StringPrincipal.java b/server/src/main/java/org/opensearch/identity/StringPrincipal.java new file mode 100644 index 0000000000000..ecd2660dbf6e9 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/StringPrincipal.java @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import java.security.Principal; +import java.util.Objects; + +/** + * Create a principal from a string + */ +class StringPrincipal implements Principal { + + private final String name; + + /** + * Creates a principal for an identity specified as a string + * @param name A persistent string that represent an identity + */ + StringPrincipal(final String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + final Principal that = (Principal) obj; + return Objects.equals(name, that.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public String toString() { + return "StringPrincipal(" + "name=" + name + ")"; + } +} diff --git a/server/src/main/java/org/opensearch/identity/Subject.java b/server/src/main/java/org/opensearch/identity/Subject.java new file mode 100644 index 0000000000000..7ea23d3a107aa --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/Subject.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import java.security.Principal; + +/** + * An individual, process, or device that causes information to flow among objects or change to the system state. + * + * Used to authorize activities inside of the OpenSearch ecosystem. + */ +public interface Subject { + + /** + * Get the application-wide uniquely identifying principal + * */ + public Principal getPrincipal(); + +} diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopAuthenticationManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopAuthenticationManager.java new file mode 100644 index 0000000000000..fab6a5913dd5a --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/noop/NoopAuthenticationManager.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity.noop; + +import org.opensearch.identity.AuthenticationManager; +import org.opensearch.identity.Subject; + +/** + * Implementation of authentication manager that does not enforce authentication + * + * This class and related classes in this package will not return nulls or fail permissions checks + */ +public class NoopAuthenticationManager implements AuthenticationManager { + + @Override + public Subject getSubject() { + return new NoopSubject(); + } + +} diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopSubject.java b/server/src/main/java/org/opensearch/identity/noop/NoopSubject.java new file mode 100644 index 0000000000000..295e5331722f5 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/noop/NoopSubject.java @@ -0,0 +1,46 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity.noop; + +import java.security.Principal; +import java.util.Objects; + +import org.opensearch.identity.Subject; +import org.opensearch.identity.Principals; + +/** + * Implementation of subject that is always authenticated + * + * This class and related classes in this package will not return nulls or fail permissions checks + */ +public class NoopSubject implements Subject { + + @Override + public Principal getPrincipal() { + return Principals.UNAUTHENTICATED.getPrincipal(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null || getClass() != obj.getClass()) return false; + Subject that = (Subject) obj; + return Objects.equals(getPrincipal(), that.getPrincipal()); + } + + @Override + public int hashCode() { + return Objects.hash(getPrincipal()); + } + + @Override + public String toString() { + return "NoopSubject(principal=" + getPrincipal() + ")"; + } +} diff --git a/server/src/main/java/org/opensearch/identity/noop/package-info.java b/server/src/main/java/org/opensearch/identity/noop/package-info.java new file mode 100644 index 0000000000000..fcdd70db7f020 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/noop/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Classes for the noop authentication in OpenSearch */ +package org.opensearch.identity.noop; diff --git a/server/src/main/java/org/opensearch/identity/package-info.java b/server/src/main/java/org/opensearch/identity/package-info.java new file mode 100644 index 0000000000000..67df93b2a98a9 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/package-info.java @@ -0,0 +1,10 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** Core classes responsible for handling identity in OpenSearch */ +package org.opensearch.identity; diff --git a/server/src/main/java/org/opensearch/index/mapper/FieldMapper.java b/server/src/main/java/org/opensearch/index/mapper/FieldMapper.java index 137ca4be1ca87..3acf5d4ea85ee 100644 --- a/server/src/main/java/org/opensearch/index/mapper/FieldMapper.java +++ b/server/src/main/java/org/opensearch/index/mapper/FieldMapper.java @@ -34,6 +34,7 @@ import com.carrotsearch.hppc.cursors.ObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor; + import org.apache.lucene.document.Field; import org.apache.lucene.document.FieldType; import org.apache.lucene.index.IndexOptions; @@ -268,6 +269,8 @@ public void parse(ParseContext context) throws IOException { try { parseCreateField(context); } catch (Exception e) { + boolean ignore_malformed = false; + if (context.indexSettings() != null) ignore_malformed = IGNORE_MALFORMED_SETTING.get(context.indexSettings().getSettings()); String valuePreview = ""; try { XContentParser parser = context.parser(); @@ -278,23 +281,27 @@ public void parse(ParseContext context) throws IOException { valuePreview = complexValue.toString(); } } catch (Exception innerException) { + if (ignore_malformed == false) { + throw new MapperParsingException( + "failed to parse field [{}] of type [{}] in document with id '{}'. " + "Could not parse field value preview,", + e, + fieldType().name(), + fieldType().typeName(), + context.sourceToParse().id() + ); + } + } + + if (ignore_malformed == false) { throw new MapperParsingException( - "failed to parse field [{}] of type [{}] in document with id '{}'. " + "Could not parse field value preview,", + "failed to parse field [{}] of type [{}] in document with id '{}'. " + "Preview of field's value: '{}'", e, fieldType().name(), fieldType().typeName(), - context.sourceToParse().id() + context.sourceToParse().id(), + valuePreview ); } - - throw new MapperParsingException( - "failed to parse field [{}] of type [{}] in document with id '{}'. " + "Preview of field's value: '{}'", - e, - fieldType().name(), - fieldType().typeName(), - context.sourceToParse().id(), - valuePreview - ); } multiFields.parse(this, context); } diff --git a/server/src/main/java/org/opensearch/node/Node.java b/server/src/main/java/org/opensearch/node/Node.java index 92e9815313fa0..015b63ae1681e 100644 --- a/server/src/main/java/org/opensearch/node/Node.java +++ b/server/src/main/java/org/opensearch/node/Node.java @@ -127,6 +127,9 @@ import org.opensearch.gateway.MetaStateService; import org.opensearch.gateway.PersistedClusterStateService; import org.opensearch.http.HttpServerTransport; +import org.opensearch.identity.AuthenticationManager; +import org.opensearch.identity.Identity; +import org.opensearch.identity.noop.NoopAuthenticationManager; import org.opensearch.index.IndexSettings; import org.opensearch.index.analysis.AnalysisRegistry; import org.opensearch.index.engine.EngineFactory; @@ -449,6 +452,9 @@ protected Node( resourcesToClose.add(nodeEnvironment); localNodeFactory = new LocalNodeFactory(settings, nodeEnvironment.nodeId()); + final AuthenticationManager authManager = new NoopAuthenticationManager(); + Identity.setAuthManager(authManager); + final List> executorBuilders = pluginsService.getExecutorBuilders(settings); runnableTaskListener = new AtomicReference<>(); diff --git a/server/src/test/java/org/opensearch/identity/IdentityTests.java b/server/src/test/java/org/opensearch/identity/IdentityTests.java new file mode 100644 index 0000000000000..d7601b2f39af2 --- /dev/null +++ b/server/src/test/java/org/opensearch/identity/IdentityTests.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.mockito.Mockito.mock; +import static org.hamcrest.Matchers.equalTo; + +public class IdentityTests extends OpenSearchTestCase { + + public void testGetAuthManagerSetAndGets() { + final AuthenticationManager authManager = mock(AuthenticationManager.class); + Identity.setAuthManager(authManager); + + assertThat(Identity.getAuthManager(), equalTo(authManager)); + } + +} diff --git a/server/src/test/java/org/opensearch/identity/StringPrincipalTests.java b/server/src/test/java/org/opensearch/identity/StringPrincipalTests.java new file mode 100644 index 0000000000000..6ed9ff5328a19 --- /dev/null +++ b/server/src/test/java/org/opensearch/identity/StringPrincipalTests.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity; + +import org.opensearch.test.OpenSearchTestCase; + +import java.security.Principal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; + +public class StringPrincipalTests extends OpenSearchTestCase { + + public void testSamePrincipal() { + final Principal p1 = new StringPrincipal("p1"); + + assertThat(p1, equalTo(p1)); + assertThat(p1.hashCode(), equalTo(p1.hashCode())); + assertThat(p1.toString(), equalTo(p1.toString())); + } + + public void testDifferentStringPrincipalAreDiffer() { + final Principal p1 = new StringPrincipal("p1"); + final Principal p2 = new StringPrincipal("p2"); + + assertThat(p1, not(equalTo(p2))); + assertThat(p1.hashCode(), not(equalTo(p2.hashCode()))); + assertThat(p1.toString(), not(equalTo(p2.toString()))); + } + + public void testHashCode() { + final Principal p1 = new StringPrincipal("p1"); + final Principal p1Duplicated = new StringPrincipal("p1"); + assertThat(p1.hashCode(), equalTo(p1Duplicated.hashCode())); + } + + public void testToString() { + final Principal p1 = new StringPrincipal("p1"); + assertThat(p1.toString(), equalTo("StringPrincipal(name=p1)")); + } + +} diff --git a/server/src/test/java/org/opensearch/identity/noop/NoopAuthenticationManagerTests.java b/server/src/test/java/org/opensearch/identity/noop/NoopAuthenticationManagerTests.java new file mode 100644 index 0000000000000..075d085f63b12 --- /dev/null +++ b/server/src/test/java/org/opensearch/identity/noop/NoopAuthenticationManagerTests.java @@ -0,0 +1,29 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity.noop; + +import org.opensearch.test.OpenSearchTestCase; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.equalTo; + +public class NoopAuthenticationManagerTests extends OpenSearchTestCase { + + public void testGetSubject() { + assertThat(new NoopAuthenticationManager().getSubject(), not(nullValue())); + } + + public void testConsistentSubjects() { + NoopAuthenticationManager authManager = new NoopAuthenticationManager(); + assertThat(authManager.getSubject(), equalTo(authManager.getSubject())); + } + +} diff --git a/server/src/test/java/org/opensearch/identity/noop/NoopSubjectTests.java b/server/src/test/java/org/opensearch/identity/noop/NoopSubjectTests.java new file mode 100644 index 0000000000000..ad1e5e9d8c8a0 --- /dev/null +++ b/server/src/test/java/org/opensearch/identity/noop/NoopSubjectTests.java @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.identity.noop; + +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.identity.Subject; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; + +public class NoopSubjectTests extends OpenSearchTestCase { + + public void testAllNoopSubjectAreEquals() { + assertThat(new NoopSubject(), equalTo(new NoopSubject())); + } + + public void testHashCode() { + final Subject s1 = new NoopSubject(); + final Subject s2 = new NoopSubject(); + assertThat(s1.hashCode(), equalTo(s2.hashCode())); + } + + public void testToString() { + final Subject s = new NoopSubject(); + assertThat(s.toString(), equalTo("NoopSubject(principal=StringPrincipal(name=Unauthenticated))")); + } + +}