Skip to content

Commit

Permalink
Allow authorization engines as an extension (#37785)
Browse files Browse the repository at this point in the history
Authorization engines can now be registered by implementing a plugin,
which also has a service implementation of a security extension. Only
one extension may register an authorization engine and this engine will
be used for all users except reserved realm users and internal users.
  • Loading branch information
jaymode authored Jan 29, 2019
1 parent d628008 commit 3280607
Show file tree
Hide file tree
Showing 16 changed files with 781 additions and 48 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ allprojects {
"org.elasticsearch.plugin:aggs-matrix-stats-client:${version}": ':modules:aggs-matrix-stats',
"org.elasticsearch.plugin:percolator-client:${version}": ':modules:percolator',
"org.elasticsearch.plugin:rank-eval-client:${version}": ':modules:rank-eval',
// for security example plugins
"org.elasticsearch.plugin:x-pack-core:${version}": ':x-pack:plugin:core',
"org.elasticsearch.client.x-pack-transport:${version}": ':x-pack:transport-client'
]

/*
Expand Down
46 changes: 46 additions & 0 deletions plugins/examples/security-authorization-engine/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
apply plugin: 'elasticsearch.esplugin'

esplugin {
name 'security-authorization-engine'
description 'An example spi extension plugin for security that implements an Authorization Engine'
classname 'org.elasticsearch.example.AuthorizationEnginePlugin'
extendedPlugins = ['x-pack-security']
}

dependencies {
compileOnly "org.elasticsearch.plugin:x-pack-core:${version}"
testCompile "org.elasticsearch.client.x-pack-transport:${version}"
}


integTestRunner {
systemProperty 'tests.security.manager', 'false'
}

integTestCluster {
dependsOn buildZip
setting 'xpack.security.enabled', 'true'
setting 'xpack.ilm.enabled', 'false'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.monitoring.enabled', 'false'
setting 'xpack.license.self_generated.type', 'trial'

// This is important, so that all the modules are available too.
// There are index templates that use token filters that are in analysis-module and
// processors are being used that are in ingest-common module.
distribution = 'default'

setupCommand 'setupDummyUser',
'bin/elasticsearch-users', 'useradd', 'test_user', '-p', 'x-pack-test-password', '-r', 'custom_superuser'
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_user',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
check.dependsOn integTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.example;

import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin;

/**
* Plugin class that is required so that the code contained here may be loaded as a plugin.
* Additional items such as settings and actions can be registered using this plugin class.
*/
public class AuthorizationEnginePlugin extends Plugin implements ActionPlugin {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.example;

import org.elasticsearch.action.ActionListener;
import org.elasticsearch.cluster.metadata.AliasOrIndex;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
import org.elasticsearch.xpack.core.security.authz.ResolvedIndices;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
import org.elasticsearch.xpack.core.security.user.User;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
* A custom implementation of an authorization engine. This engine is extremely basic in that it
* authorizes based upon the name of a single role. If users have this role they are granted access.
*/
public class CustomAuthorizationEngine implements AuthorizationEngine {

@Override
public void resolveAuthorizationInfo(RequestInfo requestInfo, ActionListener<AuthorizationInfo> listener) {
final Authentication authentication = requestInfo.getAuthentication();
if (authentication.getUser().isRunAs()) {
final CustomAuthorizationInfo authenticatedUserAuthzInfo =
new CustomAuthorizationInfo(authentication.getUser().authenticatedUser().roles(), null);
listener.onResponse(new CustomAuthorizationInfo(authentication.getUser().roles(), authenticatedUserAuthzInfo));
} else {
listener.onResponse(new CustomAuthorizationInfo(authentication.getUser().roles(), null));
}
}

@Override
public void authorizeRunAs(RequestInfo requestInfo, AuthorizationInfo authorizationInfo, ActionListener<AuthorizationResult> listener) {
if (isSuperuser(requestInfo.getAuthentication().getUser().authenticatedUser())) {
listener.onResponse(AuthorizationResult.granted());
} else {
listener.onResponse(AuthorizationResult.deny());
}
}

@Override
public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
ActionListener<AuthorizationResult> listener) {
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
listener.onResponse(AuthorizationResult.granted());
} else {
listener.onResponse(AuthorizationResult.deny());
}
}

@Override
public void authorizeIndexAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
AsyncSupplier<ResolvedIndices> indicesAsyncSupplier,
Function<String, AliasOrIndex> aliasOrIndexFunction,
ActionListener<IndexAuthorizationResult> listener) {
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
indicesAsyncSupplier.getAsync(ActionListener.wrap(resolvedIndices -> {
Map<String, IndexAccessControl> indexAccessControlMap = new HashMap<>();
for (String name : resolvedIndices.getLocal()) {
indexAccessControlMap.put(name, new IndexAccessControl(true, FieldPermissions.DEFAULT, null));
}
IndicesAccessControl indicesAccessControl =
new IndicesAccessControl(true, Collections.unmodifiableMap(indexAccessControlMap));
listener.onResponse(new IndexAuthorizationResult(true, indicesAccessControl));
}, listener::onFailure));
} else {
listener.onResponse(new IndexAuthorizationResult(true, IndicesAccessControl.DENIED));
}
}

@Override
public void loadAuthorizedIndices(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
Map<String, AliasOrIndex> aliasAndIndexLookup, ActionListener<List<String>> listener) {
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
listener.onResponse(new ArrayList<>(aliasAndIndexLookup.keySet()));
} else {
listener.onResponse(Collections.emptyList());
}
}

public static class CustomAuthorizationInfo implements AuthorizationInfo {

private final String[] roles;
private final CustomAuthorizationInfo authenticatedAuthzInfo;

CustomAuthorizationInfo(String[] roles, CustomAuthorizationInfo authenticatedAuthzInfo) {
this.roles = roles;
this.authenticatedAuthzInfo = authenticatedAuthzInfo;
}

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

@Override
public CustomAuthorizationInfo getAuthenticatedUserAuthorizationInfo() {
return authenticatedAuthzInfo;
}
}

private boolean isSuperuser(User user) {
return Arrays.binarySearch(user.roles(), "custom_superuser") > -1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.elasticsearch.example;

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.xpack.core.security.SecurityExtension;
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;

/**
* Security extension class that registers the custom authorization engine to be used
*/
public class ExampleAuthorizationEngineExtension implements SecurityExtension {

@Override
public AuthorizationEngine getAuthorizationEngine(Settings settings) {
return new CustomAuthorizationEngine();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.elasticsearch.example.ExampleAuthorizationEngineExtension
Loading

0 comments on commit 3280607

Please sign in to comment.