Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin Security - Initial changes for roles injection #546

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
2 changes: 1 addition & 1 deletion plugin-descriptor.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ java.version=1.8
# elasticsearch release. This version is checked when the plugin
# is loaded so Elasticsearch will refuse to start in the presence of
# plugins with the incorrect elasticsearch.version.
elasticsearch.version=7.7.1
elasticsearch.version=7.7.0
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Version change is only for testing on 7.7.0

8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.release>8</maven.compiler.release>

<elasticsearch.version>7.7.1</elasticsearch.version>
<elasticsearch.version>7.7.0</elasticsearch.version>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Version change is only for testing on 7.7.0


<!-- deps -->
<netty-native.version>2.0.25.Final</netty-native.version>
Expand Down Expand Up @@ -484,6 +484,12 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.amazon.opendistroforelasticsearch.commons</groupId>
<artifactId>opendistro-commons</artifactId>
<version>1.8.0.0</version>
</dependency>

</dependencies>

<build>
Expand Down
4 changes: 2 additions & 2 deletions securityconfig/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ config:
type: noop
clientcert_auth_domain:
description: "Authenticate via SSL client certificates"
http_enabled: false
http_enabled: true
transport_enabled: false
order: 2
http_authenticator:
Expand Down Expand Up @@ -244,4 +244,4 @@ config:
# time_window_seconds: 3600
# block_expiry_seconds: 600
# max_blocked_clients: 100000
# max_tracked_clients: 100000
# max_tracked_clients: 100000
39 changes: 19 additions & 20 deletions securityconfig/roles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,29 @@ kibana_read_only:
security_rest_api_access:
reserved: true

# Allows users to view alerts
alerting_view_alerts:
reserved: true

# Allows users to use all alerting functionality : TODO: tighten this further.
alerting_role:
reserved: false
cluster_permissions:
- cluster_monitor
index_permissions:
- index_patterns:
- ".opendistro-alerting-alert*"
- '.opendistro-alerting-config'
allowed_actions:
- read

# Allows users to view and acknowledge alerts
alerting_crud_alerts:
reserved: true
index_permissions:
- indices_all
dls: '{"bool":{"should":[{"match":{"monitor.createdBy":"${user.name}"}},{"match":{"destination.createdBy":"${user.name}"}}]}}'
- index_patterns:
- ".opendistro-alerting-alert*"
- '.opendistro-alerting-alert*'
allowed_actions:
- crud

# Allows users to use all alerting functionality
alerting_full_access:
reserved: true
index_permissions:
- indices_all
dls: '{"match": {"monitor_created_by": "${user.name}"}}'
- index_patterns:
- ".opendistro-alerting-config"
- ".opendistro-alerting-alert*"
- "*"
allowed_actions:
- crud
- indices_monitor
- indices:admin/aliases/get
- indices:admin/mappings/get
- indices:monitor


Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@
import java.util.stream.Stream;

import com.amazon.opendistroforelasticsearch.security.configuration.OpenDistroSecurityFlsDlsIndexSearcherWrapper;
import com.amazon.opendistroforelasticsearch.security.rolesinfo.RolesInfoAction;
import com.amazon.opendistroforelasticsearch.security.ssl.rest.OpenDistroSecuritySSLReloadCertsAction;
import com.amazon.opendistroforelasticsearch.security.ssl.rest.OpenDistroSecuritySSLCertsInfoAction;
import com.amazon.opendistroforelasticsearch.security.transport.TransportRolesInfoAction;
import org.apache.lucene.search.QueryCachingPolicy;
import org.apache.lucene.search.Weight;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Expand Down Expand Up @@ -468,6 +470,8 @@ public UnaryOperator<RestHandler> getRestHandlerWrapper(final ThreadContext thre
if(!disabled && !sslOnly) {
actions.add(new ActionHandler<>(ConfigUpdateAction.INSTANCE, TransportConfigUpdateAction.class));
actions.add(new ActionHandler<>(WhoAmIAction.INSTANCE, TransportWhoAmIAction.class));
TransportRolesInfoAction.setEvaluator(evaluator, threadPool); //fixme: better way to solve this.
actions.add(new ActionHandler<>(RolesInfoAction.INSTANCE, TransportRolesInfoAction.class));
}
return actions;
}
Expand Down Expand Up @@ -946,6 +950,7 @@ public List<Setting<?>> getSettings() {
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_LOAD_STATIC_RESOURCES, true, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_SSL_CERT_RELOAD_ENABLED, false, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_ACCEPT_INVALID_CONFIG, false, Property.NodeScope, Property.Filtered));
settings.add(Setting.boolSetting(ConfigConstants.OPENDISTRO_SECURITY_INJECT_ROLE_ENABLED, true, Property.NodeScope, Property.Filtered));
}

return settings;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.amazon.opendistroforelasticsearch.security.auth;

import com.amazon.opendistroforelasticsearch.security.auditlog.AuditLog;
import com.amazon.opendistroforelasticsearch.security.support.ConfigConstants;
import com.amazon.opendistroforelasticsearch.security.user.User;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;

import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.concurrent.ThreadContext;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

final public class RoleInjector {
protected final Logger log = LogManager.getLogger(RoleInjector.class);
private Boolean injectRoleEnabled;
private final String injectRoleStr;
private ThreadContext threadContext = null;
private final AuditLog auditLog;

public RoleInjector(final Settings settings, final ThreadContext ctx, final AuditLog auditLog) {
this.threadContext = ctx;
this.auditLog = auditLog;
this.injectRoleEnabled = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_INJECT_ROLE_ENABLED,
false);
this.injectRoleStr = ctx.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECT_ROLE);

if(log.isDebugEnabled()){
log.debug("Injected role enabled: "+injectRoleEnabled());
log.debug("Injected role: "+injectRoleStr);
}
}

public boolean injectRoleEnabled() {
return injectRoleEnabled && (injectRoleStr != null && !injectRoleStr.isEmpty());
}

public Set<String> getInjectedRoles() {
if (!injectRoleEnabled())
return null;

//todo: any additional checks for user?
// backend roles
Set<String> newMappedRoles = new HashSet<>();
if (!Strings.isNullOrEmpty(injectRoleStr)) {
if (injectRoleStr.length() > 0) {
newMappedRoles.addAll(Arrays.asList(injectRoleStr.split(",")));
}
}
return newMappedRoles;
}

public User getUser(){
User user = new User("pluginadmin");
try {
InetAddress iAdress = InetAddress.getByName("127.0.0.1");
int port = 9300;
threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, new TransportAddress(iAdress, port));
} catch (UnknownHostException e) {
log.error("Cannot parse remote IP or port:", e);
}
//todo: investigate
//threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, AuditLog.Origin.TRANSPORT.toString());
return user;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Collections;
import java.util.List;

import com.amazon.opendistroforelasticsearch.security.rest.RestRolesInfoAction;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
Expand All @@ -39,14 +40,15 @@ public class OpenDistroSecurityRestApiActions {
public static Collection<RestHandler> getHandler(Settings settings, Path configPath, RestController controller, Client client,
AdminDNs adminDns, ConfigurationRepository cr, ClusterService cs, PrincipalExtractor principalExtractor,
final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) {
final List<RestHandler> handlers = new ArrayList<RestHandler>(13);
final List<RestHandler> handlers = new ArrayList<RestHandler>(14);
handlers.add(new InternalUsersApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new RolesMappingApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new RolesApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new ActionGroupsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new FlushCacheApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new OpenDistroSecurityConfigAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new PermissionsInfoAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new RestRolesInfoAction(settings, controller, evaluator, threadPool));
handlers.add(new AuthTokenProcessorAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new TenantsApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
handlers.add(new MigrateApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
Expand All @@ -55,5 +57,4 @@ public static Collection<RestHandler> getHandler(Settings settings, Path configP
handlers.add(new NodesDnApiAction(settings, configPath, controller, client, adminDns, cr, cs, principalExtractor, evaluator, threadPool, auditLog));
return Collections.unmodifiableCollection(handlers);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.UUID;
import java.util.stream.Collectors;

import com.amazon.opendistroforelasticsearch.security.auth.RoleInjector;
import com.amazon.opendistroforelasticsearch.security.resolver.IndexResolverReplacer;
import com.amazon.opendistroforelasticsearch.security.support.WildcardMatcher;
import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -90,6 +91,7 @@ public class OpenDistroSecurityFilter implements ActionFilter {

protected final Logger log = LogManager.getLogger(this.getClass());
protected final Logger actionTrace = LogManager.getLogger("opendistro_security_action_trace");
private final Settings settings;
private final PrivilegesEvaluator evalp;
private final AdminDNs adminDns;
private DlsFlsRequestValve dlsFlsValve;
Expand All @@ -103,6 +105,7 @@ public class OpenDistroSecurityFilter implements ActionFilter {
public OpenDistroSecurityFilter(final Settings settings, final PrivilegesEvaluator evalp, final AdminDNs adminDns,
DlsFlsRequestValve dlsFlsValve, AuditLog auditLog, ThreadPool threadPool, ClusterService cs,
final CompatConfig compatConfig, final IndexResolverReplacer indexResolverReplacer) {
this.settings = settings;
this.evalp = evalp;
this.adminDns = adminDns;
this.dlsFlsValve = dlsFlsValve;
Expand Down Expand Up @@ -148,7 +151,10 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap
attachSourceFieldContext(request);
}

final User user = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
final RoleInjector roleInjector = new RoleInjector(settings, threadContext, auditLog);

final User user = roleInjector.injectRoleEnabled() ? roleInjector.getUser() :
threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER);
final boolean userIsAdmin = isUserAdmin(user, adminDns);
final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext);
final boolean trustedClusterRequest = HeaderHelper.isTrustedClusterRequest(threadContext);
Expand Down Expand Up @@ -228,6 +234,7 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap

if(Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN))
&& (interClusterRequest || HeaderHelper.isDirectRequest(threadContext))
&& !roleInjector.injectRoleEnabled()
) {

chain.proceed(task, action, request, listener);
Expand Down Expand Up @@ -264,7 +271,7 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap
log.trace("Evaluate permissions for user: {}", user.getName());
}

final PrivilegesEvaluatorResponse pres = eval.evaluate(user, action, request, task);
final PrivilegesEvaluatorResponse pres = eval.evaluate(user, action, request, task, roleInjector);

if (log.isDebugEnabled()) {
log.debug(pres);
Expand All @@ -278,6 +285,7 @@ private <Request extends ActionRequest, Response extends ActionResponse> void ap
chain.proceed(task, action, request, listener);
return;
} else {
//todo: audit, log, exp needs to have role-inject to have new exception message
auditLog.logMissingPrivileges(action, request, task);
log.debug("no permissions for {}", pres.getMissingPrivileges());
listener.onFailure(new ElasticsearchSecurityException("no permissions for " + pres.getMissingPrivileges()+ " and "+user, RestStatus.FORBIDDEN));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.greenrobot.eventbus.Subscribe;

import com.amazon.opendistroforelasticsearch.security.auditlog.AuditLog;
import com.amazon.opendistroforelasticsearch.security.auth.RoleInjector;
import com.amazon.opendistroforelasticsearch.security.configuration.ClusterInfoHolder;
import com.amazon.opendistroforelasticsearch.security.configuration.ConfigurationRepository;
import com.amazon.opendistroforelasticsearch.security.resolver.IndexResolverReplacer;
Expand Down Expand Up @@ -163,7 +164,8 @@ public boolean isInitialized() {
return configModel !=null && configModel.getSecurityRoles() != null && dcm != null;
}

public PrivilegesEvaluatorResponse evaluate(final User user, String action0, final ActionRequest request, Task task) {
public PrivilegesEvaluatorResponse evaluate(final User user, String action0, final ActionRequest request,
Task task, final RoleInjector roleInjector) {

if (!isInitialized()) {
throw new ElasticsearchSecurityException("Open Distro Security is not initialized.");
Expand All @@ -174,7 +176,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin
}

final TransportAddress caller = Objects.requireNonNull((TransportAddress) this.threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS));
final Set<String> mappedRoles = mapRoles(user, caller);
final Set<String> mappedRoles = roleInjector.injectRoleEnabled() ? roleInjector.getInjectedRoles() : mapRoles(user, caller);
final SecurityRoles securityRoles = getSecurityRoles(mappedRoles);

final PrivilegesEvaluatorResponse presponse = new PrivilegesEvaluatorResponse();
Expand All @@ -183,6 +185,7 @@ public PrivilegesEvaluatorResponse evaluate(final User user, String action0, fin
if (log.isDebugEnabled()) {
log.debug("### evaluate permissions for {} on {}", user, clusterService.localNode().getName());
log.debug("action: "+action0+" ("+request.getClass().getSimpleName()+")");
log.debug("mapped roles: {}",mappedRoles.toString());
}

final Resolved requestedResolved = irr.resolveRequest(request);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.amazon.opendistroforelasticsearch.security.rest;


import com.amazon.opendistroforelasticsearch.security.privileges.PrivilegesEvaluator;
import com.amazon.opendistroforelasticsearch.security.rolesinfo.RolesInfoAction;
import com.amazon.opendistroforelasticsearch.security.rolesinfo.RolesInfoRequest;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.BytesRestResponse;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.rest.action.RestToXContentListener;
import org.elasticsearch.threadpool.ThreadPool;

import java.io.IOException;
import java.util.Collections;
import java.util.List;


/**
* Follwing : Similar to PermissionsInfoAction,
* Not going with roles/user API, this is giving role names not roles details.
*/
public class RestRolesInfoAction extends BaseRestHandler {

private static final List<Route> routes = Collections.singletonList(
new Route(RestRequest.Method.GET, "/_opendistro/_security/api/rolesinfo")
);

private final Logger log = LogManager.getLogger(this.getClass());
private final PrivilegesEvaluator evaluator;
private final ThreadContext threadContext;

public RestRolesInfoAction(final Settings settings, final RestController controller, final PrivilegesEvaluator evaluator, final ThreadPool threadPool) {
super();
this.threadContext = threadPool.getThreadContext();
this.evaluator = evaluator;
}

@Override
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
try {
RolesInfoRequest rolesInfoRequest = new RolesInfoRequest();
return channel -> client.admin().cluster().execute(RolesInfoAction.INSTANCE, rolesInfoRequest,
new RestToXContentListener<>(channel));
} catch (final Exception ex){
XContentBuilder builder = JsonXContent.contentBuilder();
builder.startObject();
builder.field("error", ex.toString());
builder.endObject();
return channel -> channel.sendResponse(new BytesRestResponse(RestStatus.INTERNAL_SERVER_ERROR, builder));
}
}

@Override
public List<Route> routes() {
return routes;
}

@Override
public String getName() {
return "OpenDistro RolesInfo Action";
}
}
Loading