Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
18417b3
WIP on extension settings
cwperks May 11, 2023
38b27c8
Merge branch 'main' into extension-security-settings
cwperks May 11, 2023
b3ba5b4
Merge branch 'main' into extension-security-settings
cwperks May 12, 2023
1957eb3
WIP on Scheduled Job Identity Manager
cwperks May 15, 2023
eb2051b
Add ToXContent for user
cwperks May 15, 2023
369d9cc
Use Search Query instead of Get Request since docIds aren't unique ac…
cwperks May 15, 2023
e6005c1
Change indentation
cwperks May 15, 2023
3b5b028
Remove extensions settings changes
cwperks May 15, 2023
14d9111
Remove extensions settings changes
cwperks May 15, 2023
fd64dc8
Merge branch 'main' into scheduled-job-identity
cwperks May 15, 2023
f1513de
Remove references
cwperks May 15, 2023
1a183a3
Remove reference to AD
cwperks May 16, 2023
9dc058f
Update ScheduledJobIdentity
cwperks May 16, 2023
871b057
Add SecurityIndices for security index related methods
cwperks May 16, 2023
7ad9dd6
WIP on tests
cwperks May 16, 2023
c75c65b
Merge branch 'main' into scheduled-job-identity
cwperks May 23, 2023
f4c55bf
Use ScheduledJobOperator from core
cwperks May 26, 2023
fa21481
Create BearerToken
cwperks May 30, 2023
83e8f3c
Implemente deleteUserDetails
cwperks May 31, 2023
ec6d843
Merge branch 'main' into scheduled-job-identity
cwperks Jun 1, 2023
0203554
Run spotlessApply
cwperks Jun 1, 2023
9fd6350
Merge branch 'main' into scheduled-job-identity
cwperks Jun 6, 2023
34887e8
Return ROOT for user not in threadcontext
cwperks Jun 6, 2023
8c2d18b
Update bearer token
cwperks Jun 8, 2023
34195a7
Merge branch 'main' into scheduled-job-identity
cwperks Jun 20, 2023
84121b1
Run spotlessApply
cwperks Jun 20, 2023
609fa51
Merge branch 'main' into scheduled-job-identity
cwperks Jun 21, 2023
c02c40c
Use durable threadcontext
cwperks Jun 22, 2023
85ce526
Merge branch 'main' into scheduled-job-identity
cwperks Jul 3, 2023
238c103
Update extension point names
cwperks Jul 3, 2023
c485095
Merge branch 'main' into scheduled-job-identity
cwperks Jul 6, 2023
dddfc6a
Create convertOperatorToUser
cwperks Jul 6, 2023
612a691
WIP on extracting user info from token
cwperks Jul 6, 2023
699eeba
Merge branch 'main' into scheduled-job-identity
cwperks Jul 10, 2023
28098f7
Run spotlessApply
cwperks Jul 10, 2023
0fcd476
No header overlap
cwperks Jul 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,18 @@
import org.opensearch.extensions.ExtensionsManager;
import org.opensearch.http.HttpServerTransport;
import org.opensearch.http.HttpServerTransport.Dispatcher;
import org.opensearch.identity.ScheduledJobIdentityManager;
import org.opensearch.identity.Subject;
import org.opensearch.identity.noop.NoopTokenManager;
import org.opensearch.identity.tokens.TokenManager;
import org.opensearch.index.Index;
import org.opensearch.index.IndexModule;
import org.opensearch.index.cache.query.QueryCache;
import org.opensearch.indices.IndicesService;
import org.opensearch.indices.SystemIndexDescriptor;
import org.opensearch.indices.breaker.CircuitBreakerService;
import org.opensearch.plugins.ClusterPlugin;
import org.opensearch.plugins.IdentityPlugin;
import org.opensearch.plugins.MapperPlugin;
import org.opensearch.repositories.RepositoriesService;
import org.opensearch.rest.RestController;
Expand Down Expand Up @@ -145,6 +150,8 @@
import org.opensearch.security.http.SecurityHttpServerTransport;
import org.opensearch.security.http.SecurityNonSslHttpServerTransport;
import org.opensearch.security.http.XFFResolver;
import org.opensearch.security.identity.SecurityScheduledJobIdentityManager;
import org.opensearch.security.identity.SecuritySubject;
import org.opensearch.security.privileges.PrivilegesEvaluator;
import org.opensearch.security.privileges.PrivilegesInterceptor;
import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator;
Expand Down Expand Up @@ -193,7 +200,7 @@
import org.opensearch.watcher.ResourceWatcherService;
// CS-ENFORCE-SINGLE

public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin {
public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin implements ClusterPlugin, MapperPlugin, IdentityPlugin {

private static final String KEYWORD = ".keyword";
private static final Logger actionTrace = LogManager.getLogger("opendistro_security_action_trace");
Expand All @@ -212,6 +219,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
private volatile ConfigurationRepository cr;
private volatile AdminDNs adminDns;
private volatile ClusterService cs;
private volatile SecuritySubject subject = new SecuritySubject();
private static volatile DiscoveryNode localNode;
private volatile AuditLog auditLog;
private volatile BackendRegistry backendRegistry;
Expand All @@ -226,6 +234,8 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin
private volatile Salt salt;
private volatile OpensearchDynamicSetting<Boolean> transportPassiveAuthSetting;

private volatile ScheduledJobIdentityManager scheduledJobIdentityManager;

public static boolean isActionTraceEnabled() {
return actionTrace.isTraceEnabled();
}
Expand Down Expand Up @@ -990,6 +1000,8 @@ public Collection<Object> createComponents(

cr = ConfigurationRepository.create(settings, this.configPath, threadPool, localClient, clusterService, auditLog);

subject.setThreadContext(threadPool.getThreadContext());

userService = new UserService(cs, cr, settings, localClient);

final XFFResolver xffResolver = new XFFResolver(threadPool);
Expand Down Expand Up @@ -1065,6 +1077,8 @@ public Collection<Object> createComponents(
);
components.add(principalExtractor);

scheduledJobIdentityManager = new SecurityScheduledJobIdentityManager(cs, localClient, threadPool);

// NOTE: We need to create DefaultInterClusterRequestEvaluator before creating ConfigurationRepository since the latter requires
// security index to be accessible which means
// communciation with other nodes is already up. However for the communication to be up, there needs to be trusted nodes_dn. Hence
Expand All @@ -1084,6 +1098,7 @@ public Collection<Object> createComponents(
components.add(si);
components.add(dcf);
components.add(userService);
components.add(scheduledJobIdentityManager);

return components;

Expand Down Expand Up @@ -1886,6 +1901,21 @@ private static String handleKeyword(final String field) {
return field;
}

@Override
public Subject getSubject() {
return subject;
}

@Override
public ScheduledJobIdentityManager getScheduledJobIdentityManager() {
return scheduledJobIdentityManager;
}

@Override
public TokenManager getTokenManager() {
return new NoopTokenManager();
}

public static DiscoveryNode getLocalNode() {
return localNode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).g
if (adminDns.isAdminDN(sslPrincipal)) {
// PKI authenticated REST call
threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal));
threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, new User(sslPrincipal));
auditLog.logSucceededLogin(sslPrincipal, true, null, request);
return true;
}
Expand Down Expand Up @@ -362,6 +363,10 @@ && isBlocked(((InetSocketAddress) request.getHttpChannel().getRemoteAddress()).g
ConfigConstants.OPENDISTRO_SECURITY_USER,
impersonatedUser == null ? authenticatedUser : impersonatedUser
);
threadContext.putPersistent(
ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER,
impersonatedUser == null ? authenticatedUser : impersonatedUser
);
auditLog.logSucceededLogin(
(impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(),
false,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.identity;

import java.io.IOException;
import java.time.Instant;

import com.google.common.base.Objects;

import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.io.stream.Writeable;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.security.user.User;

import static org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken;

/**
* Scheduled Job Identity.
*/
public class ScheduledJobIdentity implements Writeable, ToXContentObject {
public static final String JOB_ID_FIELD = "job_id";
public static final String JOB_INDEX_FIELD = "job_index";
public static final String LAST_UPDATE_TIME_FIELD = "last_update_time";
public static final String CREATED_TIME_FIELD = "created_time";
public static final String USER_FIELD = "user";

private final String jobId;
private final String jobIndex;
private final Instant createdTime;
private final Instant lastUpdateTime;
private final User user;

public ScheduledJobIdentity(String jobId, String jobIndex, Instant createdTime, Instant lastUpdateTime, User user) {
this.jobId = jobId;
this.jobIndex = jobIndex;
this.createdTime = createdTime;
this.lastUpdateTime = lastUpdateTime;
this.user = user;
}

public ScheduledJobIdentity(StreamInput input) throws IOException {
jobId = input.readString();
jobIndex = input.readString();
createdTime = input.readInstant();
lastUpdateTime = input.readInstant();
if (input.readBoolean()) {
user = new User(input);
} else {
user = null;
}
}

/**
* Parse content parser to {@link java.time.Instant}.
*
* @param parser json based content parser
* @return instance of {@link java.time.Instant}
* @throws IOException IOException if content can't be parsed correctly
*/
public static Instant toInstant(XContentParser parser) throws IOException {
if (parser.currentToken() == null || parser.currentToken() == XContentParser.Token.VALUE_NULL) {
return null;
}
if (parser.currentToken().isValue()) {
return Instant.ofEpochMilli(parser.longValue());
}
return null;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
XContentBuilder xContentBuilder = builder.startObject()
.field(JOB_ID_FIELD, jobId)
.field(JOB_INDEX_FIELD, jobIndex)
.field(CREATED_TIME_FIELD, createdTime.toEpochMilli())
.field(LAST_UPDATE_TIME_FIELD, lastUpdateTime.toEpochMilli());
if (user != null) {
xContentBuilder.field(USER_FIELD, user);
}
return xContentBuilder.endObject();
}

@Override
public void writeTo(StreamOutput output) throws IOException {
output.writeString(jobId);
output.writeString(jobIndex);
output.writeInstant(createdTime);
output.writeInstant(lastUpdateTime);
if (user != null) {
output.writeBoolean(true); // user exists
user.writeTo(output);
} else {
output.writeBoolean(false); // user does not exist
}
}

public static ScheduledJobIdentity parse(XContentParser parser) throws IOException {
String jobId = null;
String jobIndex = null;
Instant createdTime = null;
Instant lastUpdateTime = null;
User user = null;

ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser);
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
String fieldName = parser.currentName();
parser.nextToken();

switch (fieldName) {
case JOB_ID_FIELD:
jobId = parser.text();
break;
case JOB_INDEX_FIELD:
jobIndex = parser.text();
break;
case CREATED_TIME_FIELD:
createdTime = toInstant(parser);
break;
case LAST_UPDATE_TIME_FIELD:
lastUpdateTime = toInstant(parser);
break;
case USER_FIELD:
user = User.parse(parser);
break;
default:
parser.skipChildren();
break;
}
}
return new ScheduledJobIdentity(jobId, jobIndex, createdTime, lastUpdateTime, user);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ScheduledJobIdentity that = (ScheduledJobIdentity) o;
return Objects.equal(getJobId(), that.getJobId())
&& Objects.equal(getJobIndex(), that.getJobIndex())
&& Objects.equal(getCreatedTime(), that.getCreatedTime())
&& Objects.equal(getLastUpdateTime(), that.getLastUpdateTime());
}

@Override
public int hashCode() {
return Objects.hashCode(jobId, jobIndex, createdTime, lastUpdateTime);
}

public String getJobId() {
return jobId;
}

public String getJobIndex() {
return jobIndex;
}

public Instant getCreatedTime() {
return createdTime;
}

public Instant getLastUpdateTime() {
return lastUpdateTime;
}

public User getUser() {
return user;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/

package org.opensearch.security.identity;

import java.util.function.Supplier;

import org.opensearch.security.util.ThrowingSupplierWrapper;

import static org.opensearch.security.identity.SecurityIndices.SCHEDULED_JOB_IDENTITY_INDEX;

/**
* Represent a security index
*
*/
public enum SecurityIndex {

// throw RuntimeException since we don't know how to handle the case when the mapping reading throws IOException
SCHEDULED_JOB_IDENTITY(
SCHEDULED_JOB_IDENTITY_INDEX,
ThrowingSupplierWrapper.throwingSupplierWrapper(SecurityIndices::getScheduledJobIdentityMappings)
);

private final String indexName;
private final String mapping;

SecurityIndex(String name, Supplier<String> mappingSupplier) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use Supplier here? Asking because we are just returning a String and not a custom class object

this.indexName = name;
this.mapping = mappingSupplier.get();
}

public String getIndexName() {
return indexName;
}

public String getMapping() {
return mapping;
}

}
Loading