Skip to content

Add javadoc to the AuthorizationEngine interface #37620

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

Merged
merged 3 commits into from
Jan 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.authz;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.transport.TransportRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
* <p>
* An AuthorizationEngine is responsible for making the core decisions about whether a request
* should be authorized or not. The engine can and usually will be called multiple times during
* the authorization of a request. Security categorizes requests into a few different buckets
* and uses the action name as the indicator of what a request will perform. Internally, the
* action name is used to map a {@link TransportRequest} to the actual
* {@link org.elasticsearch.action.support.TransportAction} that will handle the request.
* </p><br>
* <p>
* Requests can be a <em>cluster</em> request or an <em>indices</em> request. Cluster requests
* are requests that tend to be global in nature; they could affect the whole cluster.
* Indices requests are those that deal with specific indices; the actions could have the affect
* of reading data, modifying data, creating an index, deleting an index, or modifying metadata.
* </p><br>
* <p>
* Each call to the engine will contain a {@link RequestInfo} object that contains the request,
* action name, and the authentication associated with the request. This data is provided by the
* engine so that all information about the request can be used to make the authorization decision.
* </p><br>
* The methods of the engine will be called in the following order:
* <ol>
* <li>{@link #resolveAuthorizationInfo(RequestInfo, ActionListener)} to retrieve information
* necessary to authorize the given user. It is important to note that the {@link RequestInfo}
* may contain an {@link Authentication} object that actually has two users when the
* <i>run as</i> feature is used and this method should resolve the information for both.
* To check for the presence of run as, use the {@link User#isRunAs()} method on the user
* retrieved using the {@link Authentication#getUser()} method.</li>
* <li>{@link #authorizeRunAs(RequestInfo, AuthorizationInfo, ActionListener)} if the request
* is making use of the run as feature. This method is used to ensure the authenticated user
* can actually impersonate the user running the request.</li>
* <li>{@link #authorizeClusterAction(RequestInfo, AuthorizationInfo, ActionListener)} if the
* request is a cluster level operation.</li>
* <li>{@link #authorizeIndexAction(RequestInfo, AuthorizationInfo, AsyncSupplier, Function, ActionListener)} if
* the request is a an index action. This method may be called multiple times for a single
* request as the request may be made up of sub-requests that also need to be authorized. The async supplier
* for resolved indices will invoke the
* {@link #loadAuthorizedIndices(RequestInfo, AuthorizationInfo, Map, ActionListener)} method
* if it is used as part of the authorization process.</li>
* </ol>
* <br><p>
* <em>NOTE:</em> the {@link #loadAuthorizedIndices(RequestInfo, AuthorizationInfo, Map, ActionListener)}
* method may be called prior to {@link #authorizeIndexAction(RequestInfo, AuthorizationInfo, AsyncSupplier, Function, ActionListener)}
* in cases where wildcards need to be expanded.
* </p><br>
* Authorization engines can be called from various threads including network threads that should
* not be blocked waiting for I/O. Network threads in elasticsearch are limited and we rely on
* asynchronous processing to ensure optimal use of network threads; this is unlike many other Java
* based servers that have a thread for each concurrent request and blocking operations could take
* place on those threads. Given this it is imperative that the implementations used here do not
* block when calling out to an external service or waiting on some data.
*/
public interface AuthorizationEngine {

/**
* Asynchronously resolves any necessary information to authorize the given user(s). This could
* include retrieval of permissions from an index or external system.
*
* @param requestInfo object contain the request and associated information such as the action
* and associated user(s)
* @param listener the listener to be notified of success using {@link ActionListener#onResponse(Object)}
* or failure using {@link ActionListener#onFailure(Exception)}
*/
void resolveAuthorizationInfo(RequestInfo requestInfo, ActionListener<AuthorizationInfo> listener);

/**
* Asynchronously authorizes an attempt for a user to run as another user.
*
* @param requestInfo object contain the request and associated information such as the action
* and associated user(s)
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param listener the listener to be notified of the authorization result
*/
void authorizeRunAs(RequestInfo requestInfo, AuthorizationInfo authorizationInfo, ActionListener<AuthorizationResult> listener);

/**
* Asynchronously authorizes a cluster action.
*
* @param requestInfo object contain the request and associated information such as the action
* and associated user(s)
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param listener the listener to be notified of the authorization result
*/
void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo, ActionListener<AuthorizationResult> listener);

/**
* Asynchronously authorizes an action that operates on an index. The indices and aliases that
* the request is attempting to operate on can be retrieved using the {@link AsyncSupplier} for
* {@link ResolvedIndices}. The resolved indices will contain the exact list of indices and aliases
* that the request is attempting to take action on; in other words this supplier handles wildcard
* expansion and datemath expressions.
*
* @param requestInfo object contain the request and associated information such as the action
* and associated user(s)
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param indicesAsyncSupplier the asynchronous supplier for the indices that this request is
* attempting to operate on
* @param aliasOrIndexFunction a function that when given a string name, returns the cluster
* metadata specific to that alias or index
* @param listener the listener to be notified of the authorization result
*/
void authorizeIndexAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
AsyncSupplier<ResolvedIndices> indicesAsyncSupplier, Function<String, AliasOrIndex> aliasOrIndexFunction,
ActionListener<IndexAuthorizationResult> listener);

/**
*
* @param requestInfo object contain the request and associated information such as the action
* and associated user(s)
* @param authorizationInfo information needed from authorization that was previously retrieved
* from {@link #resolveAuthorizationInfo(RequestInfo, ActionListener)}
* @param aliasAndIndexLookup a function that when given a string name, returns the cluster
* metadata specific to that alias or index
* @param listener the listener to be notified of the authorization result
*/
void loadAuthorizedIndices(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
Map<String, AliasOrIndex> aliasAndIndexLookup, ActionListener<List<String>> listener);

/**
* Interface for objects that contains the information needed to authorize a request
*/
interface AuthorizationInfo {

/**
* @return a map representation of the authorization information. This map will be used to
* augment the data that is audited, so in the case of RBAC this map could contain the
* role names.
*/
Map<String, Object> asMap();

/**
* This method should be overridden in case of run as. Authorization info is only retrieved
* a single time and should represent the information to authorize both run as and the
* operation being performed.
*/
default AuthorizationInfo getAuthenticatedUserAuthorizationInfo() {
return this;
}
}

/**
* Implementation of authorization info that is used in cases where we were not able to resolve
* the authorization info
*/
final class EmptyAuthorizationInfo implements AuthorizationInfo {

public static final EmptyAuthorizationInfo INSTANCE = new EmptyAuthorizationInfo();

private EmptyAuthorizationInfo() {}

@Override
public Map<String, Object> asMap() {
return Collections.emptyMap();
}
}

/**
* A class that encapsulates information about the request that is being authorized including
* the actual transport request, the authentication, and the action being invoked.
*/
final class RequestInfo {

private final Authentication authentication;
private final TransportRequest request;
private final String action;

public RequestInfo(Authentication authentication, TransportRequest request, String action) {
this.authentication = authentication;
this.request = request;
this.action = action;
}

public String getAction() {
return action;
}

public Authentication getAuthentication() {
return authentication;
}

public TransportRequest getRequest() {
return request;
}
}

/**
* Represents the result of authorization. This includes whether the actions should be granted
* and if this should be considered an auditable event.
*/
class AuthorizationResult {

private final boolean granted;
private final boolean auditable;

/**
* Create an authorization result with the provided granted value that is auditable
*/
public AuthorizationResult(boolean granted) {
this(granted, true);
}

public AuthorizationResult(boolean granted, boolean auditable) {
this.granted = granted;
this.auditable = auditable;
}

public boolean isGranted() {
return granted;
}

public boolean isAuditable() {
return auditable;
}

/**
* Returns a new authorization result that is granted and auditable
*/
public static AuthorizationResult granted() {
return new AuthorizationResult(true);
}

/**
* Returns a new authorization result that is denied and auditable
*/
public static AuthorizationResult deny() {
return new AuthorizationResult(false);
}
}

/**
* An extension of {@link AuthorizationResult} that is specific to index requests. Index requests
* need to return a {@link IndicesAccessControl} object representing the users permissions to indices
* that are being operated on.
*/
class IndexAuthorizationResult extends AuthorizationResult {

private final IndicesAccessControl indicesAccessControl;

public IndexAuthorizationResult(boolean auditable, IndicesAccessControl indicesAccessControl) {
super(indicesAccessControl == null || indicesAccessControl.isGranted(), auditable);
this.indicesAccessControl = indicesAccessControl;
}

public IndicesAccessControl getIndicesAccessControl() {
return indicesAccessControl;
}
}

@FunctionalInterface
interface AsyncSupplier<V> {

/**
* Asynchronously retrieves the value that is being supplied and notifies the listener upon
* completion.
*/
void getAsync(ActionListener<V> listener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.core.security.authz;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.elasticsearch.xpack.core.security.authz.IndicesAndAliasesResolverField.NO_INDEX_PLACEHOLDER;

/**
* Stores a collection of index names separated into "local" and "remote".
* This allows the resolution and categorization to take place exactly once per-request.
*/
public final class ResolvedIndices {
private final List<String> local;
private final List<String> remote;

public ResolvedIndices(List<String> local, List<String> remote) {
this.local = Collections.unmodifiableList(local);
this.remote = Collections.unmodifiableList(remote);
}

/**
* Returns the collection of index names that have been stored as "local" indices.
* This is a <code>List</code> because order may be important. For example <code>[ "a*" , "-a1" ]</code> is interpreted differently
* to <code>[ "-a1", "a*" ]</code>. As a consequence, this list <em>may contain duplicates</em>.
*/
public List<String> getLocal() {
return local;
}

/**
* Returns the collection of index names that have been stored as "remote" indices.
*/
public List<String> getRemote() {
return remote;
}

/**
* @return <code>true</code> if both the {@link #getLocal() local} and {@link #getRemote() remote} index lists are empty.
*/
public boolean isEmpty() {
return local.isEmpty() && remote.isEmpty();
}

/**
* @return <code>true</code> if the {@link #getRemote() remote} index lists is empty, and the local index list contains the
* {@link IndicesAndAliasesResolverField#NO_INDEX_PLACEHOLDER no-index-placeholder} and nothing else.
*/
public boolean isNoIndicesPlaceholder() {
return remote.isEmpty() && local.size() == 1 && local.contains(NO_INDEX_PLACEHOLDER);
}

public String[] toArray() {
final String[] array = new String[local.size() + remote.size()];
int i = 0;
for (String index : local) {
array[i++] = index;
}
for (String index : remote) {
array[i++] = index;
}
return array;
}

/**
* Builder class for ResolvedIndices that allows for the building of a list of indices
* without the need to construct new objects and merging them together
*/
public static class Builder {

private final List<String> local = new ArrayList<>();
private final List<String> remote = new ArrayList<>();

/** add a local index name */
public void addLocal(String index) {
local.add(index);
}

/** adds the array of local index names */
public void addLocal(String[] indices) {
local.addAll(Arrays.asList(indices));
}

/** adds the list of local index names */
public void addLocal(List<String> indices) {
local.addAll(indices);
}

/** adds the list of remote index names */
public void addRemote(List<String> indices) {
remote.addAll(indices);
}

/** @return <code>true</code> if both the local and remote index lists are empty. */
public boolean isEmpty() {
return local.isEmpty() && remote.isEmpty();
}

/** @return a immutable ResolvedIndices instance with the local and remote index lists */
public ResolvedIndices build() {
return new ResolvedIndices(local, remote);
}
}
}
Loading