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

Allow authorization engines as an extension #37785

Merged
merged 11 commits into from
Jan 29, 2019
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
[role="xpack"]
[[custom-authorization]]
=== Customizing authorization

If the authorization system that is provided by the {es} {security-features}
does not meet your needs, the authorization system can be overridden for users
other than the reserved and internal users. You do this by implementing an
authorization engine as an SPI loaded security extension that is part of an
ordinary elasticsearch plugin.

[[implementing-authorization-engine]]
==== Implementing an authorization engine

Sample code that illustrates the structure and implementation of a custom
authorization engine is provided in the
https://github.com/elastic/elasticsearch/tree/master/x-pack/qa/security-example-authorization-engine[elasticsearch]
repository on GitHub. You can use this code as a starting point for creating your
own authorization engine.

To create an authorization engine, you need to:

. Implement the `org.elasticsearch.xpack.core.security.authz.AuthorizationEngine`
interface in a class with the desired authorization behavior.
. Implement the `org.elasticsearch.xpack.core.security.authz.Authorization.AuthorizationInfo`
interface in a class that contains the necessary information to authorize the request.

To package your authorization engine as a plugin:

. Implement a plugin class that extends `org.elasticsearch.plugins.Plugin`
. Implement an extension class for your authorization engine that extends
`org.elasticsearch.xpack.core.security.SecurityExtension`. There you need to
override the following method:
+
[source,java]
----------------------------------------------------
@Override
public AuthorizationEngine getAuthorizationEngine(Settings settings) {
...
}
----------------------------------------------------
+
The `getAuthorizationEngine` method is used to provide the authorization engine
implementation.

. Create a build configuration file for the plugin; Gradle is our recommendation.
. Create a `plugin-descriptor.properties` file as described in the
<<plugin-authors,plugin authors>> section.
jaymode marked this conversation as resolved.
Show resolved Hide resolved
. Create a `META-INF/services/org.elasticsearch.xpack.core.security.SecurityExtension` descriptor file for the
extension that contains the fully qualified class name of your `org.elasticsearch.xpack.core.security.SecurityExtension` implementation
. Bundle all in a single zip file.

[[using-authorization-engine]]
==== Using an authorization engine

To use an authorization engine:

. Install the authorization engine extension on each node in the cluster. You run
`bin/elasticsearch-plugin` with the `install` sub-command and specify the URL
pointing to the zip file that contains the extension. For example:
+
[source,shell]
----------------------------------------
bin/elasticsearch-plugin install file:///<path>/my-authorization-engine-1.0.zip
----------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this example a repetition of the one below on purpose? If there's a different example we could use, I think that would be more helpful.

Copy link
Member Author

Choose a reason for hiding this comment

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

that was an oops


. Restart Elasticsearch.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.core.security.authc.AuthenticationFailureHandler;
import org.elasticsearch.xpack.core.security.authc.Realm;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult;

Expand Down Expand Up @@ -79,6 +80,18 @@ default AuthenticationFailureHandler getAuthenticationFailureHandler() {
return Collections.emptyList();
}

/**
* Returns a authorization engine for authorizing requests, or null to use the default authorization mechanism.
*
* Only one installed extension may have an authorization engine. If more than
* one extension returns a non-null authorization engine, an error is raised.
*
* @param settings The configured settings for the node
*/
default AuthorizationEngine getAuthorizationEngine(Settings settings) {
return null;
}

/**
* Loads the XPackSecurityExtensions from the given class loader
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
import org.elasticsearch.xpack.core.security.authz.AuthorizationServiceField;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.SecurityIndexSearcherWrapper;
Expand Down Expand Up @@ -484,7 +485,7 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
// minimal
getLicenseState().addListener(allRolesStore::invalidateAll);
final AuthorizationService authzService = new AuthorizationService(settings, allRolesStore, clusterService,
auditTrailService, failureHandler, threadPool, anonymousUser);
auditTrailService, failureHandler, threadPool, anonymousUser, getAuthorizationEngine());
components.add(nativeRolesStore); // used by roles actions
components.add(reservedRolesStore); // used by roles actions
components.add(allRolesStore); // for SecurityFeatureSet and clear roles cache
Expand Down Expand Up @@ -514,6 +515,25 @@ Collection<Object> createComponents(Client client, ThreadPool threadPool, Cluste
return components;
}

private AuthorizationEngine getAuthorizationEngine() {
AuthorizationEngine authorizationEngine = null;
String extensionName = null;
for (SecurityExtension extension : securityExtensions) {
final AuthorizationEngine extensionEngine = extension.getAuthorizationEngine(settings);
if (extensionEngine != null && authorizationEngine != null) {
throw new IllegalStateException("Extensions [" + extensionName + "] and [" + extension.toString() + "] "
+ "both set an authorization engine");
}
authorizationEngine = extensionEngine;
extensionName = extension.toString();
}

if (authorizationEngine != null) {
logger.debug("Using authorization engine from extension [" + extensionName + "]");
}
return authorizationEngine;
}

private AuthenticationFailureHandler createAuthenticationFailureHandler(final Realms realms) {
AuthenticationFailureHandler failureHandler = null;
String extensionName = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.elasticsearch.action.update.UpdateAction;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
Expand Down Expand Up @@ -90,12 +91,13 @@ public class AuthorizationService {
private final ThreadContext threadContext;
private final AnonymousUser anonymousUser;
private final AuthorizationEngine rbacEngine;
private final AuthorizationEngine authorizationEngine;
private final boolean isAnonymousEnabled;
private final boolean anonymousAuthzExceptionEnabled;

public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, ClusterService clusterService,
AuditTrailService auditTrail, AuthenticationFailureHandler authcFailureHandler,
ThreadPool threadPool, AnonymousUser anonymousUser) {
ThreadPool threadPool, AnonymousUser anonymousUser, @Nullable AuthorizationEngine authorizationEngine) {
this.clusterService = clusterService;
this.auditTrail = auditTrail;
this.indicesAndAliasesResolver = new IndicesAndAliasesResolver(settings, clusterService);
Expand All @@ -105,6 +107,7 @@ public AuthorizationService(Settings settings, CompositeRolesStore rolesStore, C
this.isAnonymousEnabled = AnonymousUser.isAnonymousEnabled(settings);
this.anonymousAuthzExceptionEnabled = ANONYMOUS_AUTHORIZATION_EXCEPTION_SETTING.get(settings);
this.rbacEngine = new RBACEngine(settings, rolesStore);
this.authorizationEngine = authorizationEngine == null ? this.rbacEngine : authorizationEngine;
this.settings = settings;
}

Expand Down Expand Up @@ -293,14 +296,22 @@ private void authorizeAction(final RequestInfo requestInfo, final String request
}
}

private AuthorizationEngine getRunAsAuthorizationEngine(final Authentication authentication) {
return ClientReservedRealm.isReserved(authentication.getUser().authenticatedUser().principal(), settings) ?
rbacEngine : rbacEngine;
// pkg-private for testing
AuthorizationEngine getRunAsAuthorizationEngine(final Authentication authentication) {
return getAuthorizationEngineForUser(authentication.getUser().authenticatedUser());
}

private AuthorizationEngine getAuthorizationEngine(final Authentication authentication) {
return ClientReservedRealm.isReserved(authentication.getUser().principal(), settings) ?
rbacEngine : rbacEngine;
// pkg-private for testing
AuthorizationEngine getAuthorizationEngine(final Authentication authentication) {
return getAuthorizationEngineForUser(authentication.getUser());
}

private AuthorizationEngine getAuthorizationEngineForUser(final User user) {
if (ClientReservedRealm.isReserved(user.principal(), settings) || isInternalUser(user)) {
return rbacEngine;
} else {
return authorizationEngine;
}
}

private void authorizeSystemUser(final Authentication authentication, final String action, final String requestId,
Expand Down Expand Up @@ -490,13 +501,13 @@ private void putTransientIfNonExisting(String key, Object value) {
}
}

ElasticsearchSecurityException denial(String auditRequestId, Authentication authentication, String action, TransportRequest request,
AuthorizationInfo authzInfo) {
private ElasticsearchSecurityException denial(String auditRequestId, Authentication authentication, String action,
TransportRequest request, AuthorizationInfo authzInfo) {
return denial(auditRequestId, authentication, action, request, authzInfo, null);
}

ElasticsearchSecurityException denial(String auditRequestId, Authentication authentication, String action, TransportRequest request,
AuthorizationInfo authzInfo, Exception cause) {
private ElasticsearchSecurityException denial(String auditRequestId, Authentication authentication, String action,
TransportRequest request, AuthorizationInfo authzInfo, Exception cause) {
auditTrail.accessDenied(auditRequestId, authentication, action, request, authzInfo);
return denialException(authentication, action, cause);
}
Expand Down
Loading