Skip to content

Commit 3280607

Browse files
authored
Allow authorization engines as an extension (#37785)
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.
1 parent d628008 commit 3280607

File tree

16 files changed

+781
-48
lines changed

16 files changed

+781
-48
lines changed

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ allprojects {
232232
"org.elasticsearch.plugin:aggs-matrix-stats-client:${version}": ':modules:aggs-matrix-stats',
233233
"org.elasticsearch.plugin:percolator-client:${version}": ':modules:percolator',
234234
"org.elasticsearch.plugin:rank-eval-client:${version}": ':modules:rank-eval',
235+
// for security example plugins
236+
"org.elasticsearch.plugin:x-pack-core:${version}": ':x-pack:plugin:core',
237+
"org.elasticsearch.client.x-pack-transport:${version}": ':x-pack:transport-client'
235238
]
236239

237240
/*
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
apply plugin: 'elasticsearch.esplugin'
2+
3+
esplugin {
4+
name 'security-authorization-engine'
5+
description 'An example spi extension plugin for security that implements an Authorization Engine'
6+
classname 'org.elasticsearch.example.AuthorizationEnginePlugin'
7+
extendedPlugins = ['x-pack-security']
8+
}
9+
10+
dependencies {
11+
compileOnly "org.elasticsearch.plugin:x-pack-core:${version}"
12+
testCompile "org.elasticsearch.client.x-pack-transport:${version}"
13+
}
14+
15+
16+
integTestRunner {
17+
systemProperty 'tests.security.manager', 'false'
18+
}
19+
20+
integTestCluster {
21+
dependsOn buildZip
22+
setting 'xpack.security.enabled', 'true'
23+
setting 'xpack.ilm.enabled', 'false'
24+
setting 'xpack.ml.enabled', 'false'
25+
setting 'xpack.monitoring.enabled', 'false'
26+
setting 'xpack.license.self_generated.type', 'trial'
27+
28+
// This is important, so that all the modules are available too.
29+
// There are index templates that use token filters that are in analysis-module and
30+
// processors are being used that are in ingest-common module.
31+
distribution = 'default'
32+
33+
setupCommand 'setupDummyUser',
34+
'bin/elasticsearch-users', 'useradd', 'test_user', '-p', 'x-pack-test-password', '-r', 'custom_superuser'
35+
waitCondition = { node, ant ->
36+
File tmpFile = new File(node.cwd, 'wait.success')
37+
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
38+
dest: tmpFile.toString(),
39+
username: 'test_user',
40+
password: 'x-pack-test-password',
41+
ignoreerrors: true,
42+
retries: 10)
43+
return tmpFile.exists()
44+
}
45+
}
46+
check.dependsOn integTest
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.example;
21+
22+
import org.elasticsearch.plugins.ActionPlugin;
23+
import org.elasticsearch.plugins.Plugin;
24+
25+
/**
26+
* Plugin class that is required so that the code contained here may be loaded as a plugin.
27+
* Additional items such as settings and actions can be registered using this plugin class.
28+
*/
29+
public class AuthorizationEnginePlugin extends Plugin implements ActionPlugin {
30+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.example;
21+
22+
import org.elasticsearch.action.ActionListener;
23+
import org.elasticsearch.cluster.metadata.AliasOrIndex;
24+
import org.elasticsearch.xpack.core.security.authc.Authentication;
25+
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
26+
import org.elasticsearch.xpack.core.security.authz.ResolvedIndices;
27+
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl;
28+
import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl;
29+
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions;
30+
import org.elasticsearch.xpack.core.security.user.User;
31+
32+
import java.util.ArrayList;
33+
import java.util.Arrays;
34+
import java.util.Collections;
35+
import java.util.HashMap;
36+
import java.util.List;
37+
import java.util.Map;
38+
import java.util.function.Function;
39+
40+
/**
41+
* A custom implementation of an authorization engine. This engine is extremely basic in that it
42+
* authorizes based upon the name of a single role. If users have this role they are granted access.
43+
*/
44+
public class CustomAuthorizationEngine implements AuthorizationEngine {
45+
46+
@Override
47+
public void resolveAuthorizationInfo(RequestInfo requestInfo, ActionListener<AuthorizationInfo> listener) {
48+
final Authentication authentication = requestInfo.getAuthentication();
49+
if (authentication.getUser().isRunAs()) {
50+
final CustomAuthorizationInfo authenticatedUserAuthzInfo =
51+
new CustomAuthorizationInfo(authentication.getUser().authenticatedUser().roles(), null);
52+
listener.onResponse(new CustomAuthorizationInfo(authentication.getUser().roles(), authenticatedUserAuthzInfo));
53+
} else {
54+
listener.onResponse(new CustomAuthorizationInfo(authentication.getUser().roles(), null));
55+
}
56+
}
57+
58+
@Override
59+
public void authorizeRunAs(RequestInfo requestInfo, AuthorizationInfo authorizationInfo, ActionListener<AuthorizationResult> listener) {
60+
if (isSuperuser(requestInfo.getAuthentication().getUser().authenticatedUser())) {
61+
listener.onResponse(AuthorizationResult.granted());
62+
} else {
63+
listener.onResponse(AuthorizationResult.deny());
64+
}
65+
}
66+
67+
@Override
68+
public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
69+
ActionListener<AuthorizationResult> listener) {
70+
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
71+
listener.onResponse(AuthorizationResult.granted());
72+
} else {
73+
listener.onResponse(AuthorizationResult.deny());
74+
}
75+
}
76+
77+
@Override
78+
public void authorizeIndexAction(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
79+
AsyncSupplier<ResolvedIndices> indicesAsyncSupplier,
80+
Function<String, AliasOrIndex> aliasOrIndexFunction,
81+
ActionListener<IndexAuthorizationResult> listener) {
82+
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
83+
indicesAsyncSupplier.getAsync(ActionListener.wrap(resolvedIndices -> {
84+
Map<String, IndexAccessControl> indexAccessControlMap = new HashMap<>();
85+
for (String name : resolvedIndices.getLocal()) {
86+
indexAccessControlMap.put(name, new IndexAccessControl(true, FieldPermissions.DEFAULT, null));
87+
}
88+
IndicesAccessControl indicesAccessControl =
89+
new IndicesAccessControl(true, Collections.unmodifiableMap(indexAccessControlMap));
90+
listener.onResponse(new IndexAuthorizationResult(true, indicesAccessControl));
91+
}, listener::onFailure));
92+
} else {
93+
listener.onResponse(new IndexAuthorizationResult(true, IndicesAccessControl.DENIED));
94+
}
95+
}
96+
97+
@Override
98+
public void loadAuthorizedIndices(RequestInfo requestInfo, AuthorizationInfo authorizationInfo,
99+
Map<String, AliasOrIndex> aliasAndIndexLookup, ActionListener<List<String>> listener) {
100+
if (isSuperuser(requestInfo.getAuthentication().getUser())) {
101+
listener.onResponse(new ArrayList<>(aliasAndIndexLookup.keySet()));
102+
} else {
103+
listener.onResponse(Collections.emptyList());
104+
}
105+
}
106+
107+
public static class CustomAuthorizationInfo implements AuthorizationInfo {
108+
109+
private final String[] roles;
110+
private final CustomAuthorizationInfo authenticatedAuthzInfo;
111+
112+
CustomAuthorizationInfo(String[] roles, CustomAuthorizationInfo authenticatedAuthzInfo) {
113+
this.roles = roles;
114+
this.authenticatedAuthzInfo = authenticatedAuthzInfo;
115+
}
116+
117+
@Override
118+
public Map<String, Object> asMap() {
119+
return Collections.singletonMap("roles", roles);
120+
}
121+
122+
@Override
123+
public CustomAuthorizationInfo getAuthenticatedUserAuthorizationInfo() {
124+
return authenticatedAuthzInfo;
125+
}
126+
}
127+
128+
private boolean isSuperuser(User user) {
129+
return Arrays.binarySearch(user.roles(), "custom_superuser") > -1;
130+
}
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.example;
21+
22+
import org.elasticsearch.common.settings.Settings;
23+
import org.elasticsearch.xpack.core.security.SecurityExtension;
24+
import org.elasticsearch.xpack.core.security.authz.AuthorizationEngine;
25+
26+
/**
27+
* Security extension class that registers the custom authorization engine to be used
28+
*/
29+
public class ExampleAuthorizationEngineExtension implements SecurityExtension {
30+
31+
@Override
32+
public AuthorizationEngine getAuthorizationEngine(Settings settings) {
33+
return new CustomAuthorizationEngine();
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.elasticsearch.example.ExampleAuthorizationEngineExtension

0 commit comments

Comments
 (0)