Skip to content

Commit

Permalink
IT placeholders, refactor to support assuming identities
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Nied <petern@amazon.com>
  • Loading branch information
peternied committed Jul 19, 2023
1 parent aeaa324 commit 1c8dbe5
Show file tree
Hide file tree
Showing 25 changed files with 421 additions and 207 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.CollectionTerminatedException;
import org.apache.lucene.search.ScoreMode;

import org.opensearch.ExceptionsHelper;
import org.opensearch.action.ActionListener;
import org.opensearch.action.admin.cluster.node.stats.NodeStats;
import org.opensearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.opensearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.opensearch.action.index.IndexRequest;
import org.opensearch.action.index.IndexResponse;
import org.opensearch.action.support.IndicesOptions;
import org.opensearch.action.support.WriteRequest;
import org.opensearch.client.Client;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.common.breaker.CircuitBreaker;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.unit.TimeValue;
import org.opensearch.common.util.concurrent.AtomicArray;
import org.opensearch.core.common.Strings;
import org.opensearch.core.xcontent.ObjectParser;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.index.IndexSettings;
import org.opensearch.index.query.QueryShardContext;
import org.opensearch.index.query.RangeQueryBuilder;
import org.opensearch.index.shard.IndexShard;
import org.opensearch.indices.IndicesService;
import org.opensearch.indices.replication.common.ReplicationType;
import org.opensearch.plugins.Plugin;
import org.opensearch.plugins.SearchPlugin;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.search.DocValueFormat;
import org.opensearch.search.SearchHit;
import org.opensearch.search.aggregations.AbstractAggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.aggregations.Aggregations;
import org.opensearch.search.aggregations.Aggregator;
import org.opensearch.search.aggregations.AggregatorBase;
import org.opensearch.search.aggregations.AggregatorFactories;
import org.opensearch.search.aggregations.AggregatorFactory;
import org.opensearch.search.aggregations.CardinalityUpperBound;
import org.opensearch.search.aggregations.InternalAggregation;
import org.opensearch.search.aggregations.LeafBucketCollector;
import org.opensearch.search.aggregations.bucket.terms.LongTerms;
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.opensearch.search.aggregations.metrics.InternalMax;
import org.opensearch.search.aggregations.support.ValueType;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.fetch.FetchSubPhase;
import org.opensearch.search.fetch.FetchSubPhaseProcessor;
import org.opensearch.search.internal.SearchContext;
import org.opensearch.test.OpenSearchIntegTestCase;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

public class ApplicationScopeIT extends OpenSearchIntegTestCase {

// TODO: Test case, verify that application with no scopes cannot SEARCH

// TODO: Test case, verify that allowed application can SEARCH

// TODO: Test case, verify that SUPER_USER access application can SEARCH,...
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.CollectionTerminatedException;
import org.apache.lucene.search.ScoreMode;

import org.opensearch.ExceptionsHelper;
import org.opensearch.action.ActionListener;
import org.opensearch.action.admin.cluster.node.stats.NodeStats;
import org.opensearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.opensearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.opensearch.action.index.IndexRequest;
import org.opensearch.action.index.IndexResponse;
import org.opensearch.action.support.IndicesOptions;
import org.opensearch.action.support.WriteRequest;
import org.opensearch.client.Client;
import org.opensearch.cluster.metadata.IndexMetadata;
import org.opensearch.common.breaker.CircuitBreaker;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.common.settings.Settings;
import org.opensearch.common.unit.TimeValue;
import org.opensearch.common.util.concurrent.AtomicArray;
import org.opensearch.core.common.Strings;
import org.opensearch.core.xcontent.ObjectParser;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.index.IndexSettings;
import org.opensearch.index.query.QueryShardContext;
import org.opensearch.index.query.RangeQueryBuilder;
import org.opensearch.index.shard.IndexShard;
import org.opensearch.indices.IndicesService;
import org.opensearch.indices.replication.common.ReplicationType;
import org.opensearch.plugins.Plugin;
import org.opensearch.plugins.SearchPlugin;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.search.DocValueFormat;
import org.opensearch.search.SearchHit;
import org.opensearch.search.aggregations.AbstractAggregationBuilder;
import org.opensearch.search.aggregations.AggregationBuilder;
import org.opensearch.search.aggregations.Aggregations;
import org.opensearch.search.aggregations.Aggregator;
import org.opensearch.search.aggregations.AggregatorBase;
import org.opensearch.search.aggregations.AggregatorFactories;
import org.opensearch.search.aggregations.AggregatorFactory;
import org.opensearch.search.aggregations.CardinalityUpperBound;
import org.opensearch.search.aggregations.InternalAggregation;
import org.opensearch.search.aggregations.LeafBucketCollector;
import org.opensearch.search.aggregations.bucket.terms.LongTerms;
import org.opensearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.opensearch.search.aggregations.metrics.InternalMax;
import org.opensearch.search.aggregations.support.ValueType;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.fetch.FetchSubPhase;
import org.opensearch.search.fetch.FetchSubPhaseProcessor;
import org.opensearch.search.internal.SearchContext;
import org.opensearch.test.OpenSearchIntegTestCase;

import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertAcked;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;

public class IdentityServiceIT extends OpenSearchIntegTestCase {

// TODO: Verify that application aware subject is persisted on the same request

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opensearch.Application;
import org.opensearch.identity.Application;
import org.opensearch.OpenSearchException;
import org.opensearch.Version;
import org.opensearch.cluster.node.DiscoveryNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch;
package org.opensearch.identity;

import java.security.Principal;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.opensearch.identity.scopes.ApplicationScope;
import org.opensearch.identity.scopes.ApplicationScope;
import org.opensearch.identity.scopes.Scope;
import org.opensearch.identity.tokens.AuthToken;
Expand All @@ -32,14 +38,15 @@
@SuppressWarnings("overrides")
public class ApplicationAwareSubject implements Subject {

private final Subject wrapped;
private final AtomicReference<Application> assumedApplication = new AtomicReference<>();
private final Supplier<Subject> wrapped;
private final ApplicationManager applicationManager;

/**
* We wrap a basic Subject object to create an ApplicationAwareSubject -- this should come from the IdentityService
* @param wrapped The Subject to be wrapped
*/
public ApplicationAwareSubject(final Subject wrapped, final ApplicationManager applicationManager) {
public ApplicationAwareSubject(final Supplier<Subject> wrapped, final ApplicationManager applicationManager) {
this.wrapped = wrapped;
this.applicationManager = applicationManager;
}
Expand All @@ -50,8 +57,16 @@ public ApplicationAwareSubject(final Subject wrapped, final ApplicationManager a
* @return true if allowed, false if none of the scopes are allowed.
*/
public boolean isAllowed(final List<Scope> scopes) {
final boolean isSubjectApplicationAllowed = isAllowed(getApplication(), scopes);

final Optional<Principal> assumedApplicationPrincipal = Optional.ofNullable(assumedApplication.get())
.map(Application::getPrincipal);
final boolean isAssumedApplicationAllowed = isAllowed(assumedApplicationPrincipal, scopes);

return isSubjectApplicationAllowed && isAssumedApplicationAllowed;
}

final Optional<Principal> application = this.getApplication();
private boolean isAllowed(final Optional<Principal> application, final List<Scope> scopes) {
if (application.isEmpty()) {
// If there is no application, actions are allowed by default
return true;
Expand All @@ -76,29 +91,28 @@ public boolean isAllowed(final List<Scope> scopes) {
return hasMatchingScopes;
}

// Passthroughs for wrapped subject
public synchronized <T> T runAs(final Application application, final Callable<T> callable) {
if (!assumedApplication.compareAndSet(null, application)) {
throw new AssumeIdentityException("Subject is already running as another application, " + assumedApplication.get() + ", tried to become " + application);
}
try {
return callable.call();
} catch (final Exception e) {
throw new RuntimeException(e);
} finally {
assumedApplication.set(null);
}
}

public Principal getPrincipal() {
return wrapped.getPrincipal();
return wrapped.get().getPrincipal();
}

public void authenticate(final AuthToken token) {
wrapped.authenticate(token);
wrapped.get().authenticate(token);
}

public Optional<Principal> getApplication() {
return wrapped.getApplication();
}
// end Passthroughs for wrapped subject

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ApplicationAwareSubject)) {
return false;
}
final ApplicationAwareSubject other = (ApplicationAwareSubject) obj;
return Objects.equals(this.wrapped, other.wrapped);
return wrapped.get().getApplication();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.identity;

/**
* An exception thrown when the subject is not authorized for the given permission
*
* @opensearch.experimental
*/
public class AssumeIdentityException extends RuntimeException {
public AssumeIdentityException(final String message) {
super(message);
}
}
34 changes: 32 additions & 2 deletions server/src/main/java/org/opensearch/identity/IdentityService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@

package org.opensearch.identity;

import java.security.Principal;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.opensearch.OpenSearchException;
import org.opensearch.common.settings.Settings;
import org.opensearch.identity.noop.NoopIdentityPlugin;
import org.opensearch.identity.noop.NoopSubject;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.plugins.IdentityPlugin;

import org.opensearch.common.util.concurrent.ThreadContext;
/**
* Identity and access control for OpenSearch
*
Expand All @@ -26,6 +30,7 @@ public class IdentityService {
private final Settings settings;
private final IdentityPlugin identityPlugin;
private final ApplicationManager applicationManager;
private final AtomicReference<ThreadContext> threadContext = new AtomicReference<>();

public IdentityService(
final Settings settings,
Expand All @@ -49,11 +54,17 @@ public IdentityService(
}
}

public void associateThreadContext(final ThreadContext threadContext) {
if (!this.threadContext.compareAndSet(null, threadContext)) {
throw new OpenSearchException("Thread context was already associated to identity service");
}
}

/**
* Gets the current Subject
*/
public ApplicationAwareSubject getSubject() {
return new ApplicationAwareSubject(identityPlugin.getSubject(), applicationManager);
return getSubjectFromContext().orElseGet(this::createSubjectAndPutInContext);
}

/**
Expand All @@ -62,4 +73,23 @@ public ApplicationAwareSubject getSubject() {
public TokenManager getTokenManager() {
return identityPlugin.getTokenManager();
}

private static final String SUBJECT_CONTEXT_KEY = "application_aware_subject";
private Optional<ApplicationAwareSubject> getSubjectFromContext() {
return Optional.ofNullable(threadContext.get())
.map(context -> context.getPersistent(SUBJECT_CONTEXT_KEY))
.map(sub -> {
if (sub instanceof ApplicationAwareSubject) {
return (ApplicationAwareSubject)sub;
}
return null;
});
}

private ApplicationAwareSubject createSubjectAndPutInContext() {
final ApplicationAwareSubject newSubject = new ApplicationAwareSubject(identityPlugin::getSubject, applicationManager);
Optional.ofNullable(threadContext.get())
.ifPresent(context -> context.putPersistent(SUBJECT_CONTEXT_KEY, newSubject));
return newSubject;
}
}
2 changes: 1 addition & 1 deletion server/src/main/java/org/opensearch/identity/Subject.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import java.util.Optional;

/**
* An individual, process, or device that causes information to flow among objects or change to the system state.
* An individual, process, or device that causes information to flow among objects or changes to the system state.
*
* @opensearch.experimental
*/
Expand Down
Loading

0 comments on commit 1c8dbe5

Please sign in to comment.