|
| 1 | +/* |
| 2 | + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one |
| 3 | + * or more contributor license agreements. Licensed under the Elastic License; |
| 4 | + * you may not use this file except in compliance with the Elastic License. |
| 5 | + */ |
| 6 | + |
| 7 | +package org.elasticsearch.xpack.security.operator; |
| 8 | + |
| 9 | +import org.elasticsearch.ElasticsearchSecurityException; |
| 10 | +import org.elasticsearch.action.admin.cluster.configuration.ClearVotingConfigExclusionsAction; |
| 11 | +import org.elasticsearch.action.admin.cluster.configuration.ClearVotingConfigExclusionsRequest; |
| 12 | +import org.elasticsearch.action.support.TransportAction; |
| 13 | +import org.elasticsearch.client.Client; |
| 14 | +import org.elasticsearch.common.inject.Binding; |
| 15 | +import org.elasticsearch.common.inject.Injector; |
| 16 | +import org.elasticsearch.common.inject.TypeLiteral; |
| 17 | +import org.elasticsearch.common.settings.SecureString; |
| 18 | +import org.elasticsearch.common.settings.Settings; |
| 19 | +import org.elasticsearch.common.util.set.Sets; |
| 20 | +import org.elasticsearch.test.SecuritySingleNodeTestCase; |
| 21 | + |
| 22 | +import java.util.ArrayList; |
| 23 | +import java.util.HashSet; |
| 24 | +import java.util.List; |
| 25 | +import java.util.Map; |
| 26 | +import java.util.Set; |
| 27 | + |
| 28 | +import static org.elasticsearch.test.SecuritySettingsSource.TEST_PASSWORD_HASHED; |
| 29 | +import static org.elasticsearch.test.SecuritySettingsSourceField.TEST_PASSWORD; |
| 30 | +import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue; |
| 31 | +import static org.hamcrest.Matchers.containsString; |
| 32 | + |
| 33 | +public class OperatorPrivilegesSingleNodeTests extends SecuritySingleNodeTestCase { |
| 34 | + |
| 35 | + private static final String OPERATOR_USER_NAME = "test_operator"; |
| 36 | + |
| 37 | + @Override |
| 38 | + protected String configUsers() { |
| 39 | + return super.configUsers() |
| 40 | + + OPERATOR_USER_NAME + ":" + TEST_PASSWORD_HASHED + "\n"; |
| 41 | + } |
| 42 | + |
| 43 | + @Override |
| 44 | + protected String configRoles() { |
| 45 | + return super.configRoles() |
| 46 | + + "limited_operator:\n" |
| 47 | + + " cluster:\n" |
| 48 | + + " - 'cluster:admin/voting_config/clear_exclusions'\n" |
| 49 | + + " - 'monitor'\n"; |
| 50 | + } |
| 51 | + |
| 52 | + @Override |
| 53 | + protected String configUsersRoles() { |
| 54 | + return super.configUsersRoles() |
| 55 | + + "limited_operator:" + OPERATOR_USER_NAME + "\n"; |
| 56 | + } |
| 57 | + |
| 58 | + @Override |
| 59 | + protected String configOperatorUsers() { |
| 60 | + return super.configOperatorUsers() |
| 61 | + + "operator:\n" |
| 62 | + + " - usernames: ['" + OPERATOR_USER_NAME + "']\n"; |
| 63 | + } |
| 64 | + |
| 65 | + @Override |
| 66 | + protected Settings nodeSettings() { |
| 67 | + Settings.Builder builder = Settings.builder().put(super.nodeSettings()); |
| 68 | + // Ensure the new settings can be configured |
| 69 | + builder.put("xpack.security.operator_privileges.enabled", "true"); |
| 70 | + return builder.build(); |
| 71 | + } |
| 72 | + |
| 73 | + // TODO: Not all plugins are available in internal cluster tests. Hence not all action names can be checked. |
| 74 | + public void testActionsAreEitherOperatorOnlyOrNot() { |
| 75 | + final Injector injector = node().injector(); |
| 76 | + final List<Binding<TransportAction>> bindings = injector.findBindingsByType(TypeLiteral.get(TransportAction.class)); |
| 77 | + |
| 78 | + final List<String> allActionNames = new ArrayList<>(bindings.size()); |
| 79 | + for (final Binding<TransportAction> binding : bindings) { |
| 80 | + allActionNames.add(binding.getProvider().get().actionName); |
| 81 | + } |
| 82 | + |
| 83 | + final Set<String> nonOperatorActions = Set.of(NON_OPERATOR_ACTIONS); |
| 84 | + final Set<String> expectedOperatorOnlyActions = Sets.difference(Set.copyOf(allActionNames), nonOperatorActions); |
| 85 | + final Set<String> actualOperatorOnlyActions = new HashSet<>(CompositeOperatorOnly.ActionOperatorOnly.SIMPLE_ACTIONS); |
| 86 | + assertTrue(actualOperatorOnlyActions.containsAll(expectedOperatorOnlyActions)); |
| 87 | + assertFalse(actualOperatorOnlyActions.removeAll(nonOperatorActions)); |
| 88 | + } |
| 89 | + |
| 90 | + public void testSuperuserWillFailToCallOperatorOnlyAction() { |
| 91 | + final ClearVotingConfigExclusionsRequest clearVotingConfigExclusionsRequest = new ClearVotingConfigExclusionsRequest(); |
| 92 | + final ElasticsearchSecurityException e = expectThrows(ElasticsearchSecurityException.class, |
| 93 | + () -> client().execute(ClearVotingConfigExclusionsAction.INSTANCE, clearVotingConfigExclusionsRequest).actionGet()); |
| 94 | + assertThat(e.getCause().getMessage(), containsString("Operator privileges are required for action")); |
| 95 | + } |
| 96 | + |
| 97 | + public void testOperatorUserWillSucceedToCallOperatorOnlyAction() { |
| 98 | + final Client client = client().filterWithHeader(Map.of( |
| 99 | + "Authorization", |
| 100 | + basicAuthHeaderValue(OPERATOR_USER_NAME, new SecureString(TEST_PASSWORD.toCharArray())))); |
| 101 | + final ClearVotingConfigExclusionsRequest clearVotingConfigExclusionsRequest = new ClearVotingConfigExclusionsRequest(); |
| 102 | + client.execute(ClearVotingConfigExclusionsAction.INSTANCE, clearVotingConfigExclusionsRequest).actionGet(); |
| 103 | + } |
| 104 | + |
| 105 | + public static final String[] NON_OPERATOR_ACTIONS = new String[] { |
| 106 | + "cluster:admin/component_template/delete", |
| 107 | + "cluster:admin/component_template/get", |
| 108 | + "cluster:admin/component_template/put", |
| 109 | + "cluster:admin/indices/dangling/delete", |
| 110 | + "cluster:admin/indices/dangling/find", |
| 111 | + "cluster:admin/indices/dangling/import", |
| 112 | + "cluster:admin/indices/dangling/list", |
| 113 | + "cluster:admin/ingest/pipeline/delete", |
| 114 | + "cluster:admin/ingest/pipeline/get", |
| 115 | + "cluster:admin/ingest/pipeline/put", |
| 116 | + "cluster:admin/ingest/pipeline/simulate", |
| 117 | + "cluster:admin/nodes/reload_secure_settings", |
| 118 | + "cluster:admin/persistent/completion", |
| 119 | + "cluster:admin/persistent/remove", |
| 120 | + "cluster:admin/persistent/start", |
| 121 | + "cluster:admin/persistent/update_status", |
| 122 | + "cluster:admin/reindex/rethrottle", |
| 123 | + "cluster:admin/repository/_cleanup", |
| 124 | + "cluster:admin/repository/delete", |
| 125 | + "cluster:admin/repository/get", |
| 126 | + "cluster:admin/repository/put", |
| 127 | + "cluster:admin/repository/verify", |
| 128 | + "cluster:admin/reroute", |
| 129 | + "cluster:admin/script/delete", |
| 130 | + "cluster:admin/script/get", |
| 131 | + "cluster:admin/script/put", |
| 132 | + "cluster:admin/script_context/get", |
| 133 | + "cluster:admin/script_language/get", |
| 134 | + "cluster:admin/settings/update", |
| 135 | + "cluster:admin/snapshot/clone", |
| 136 | + "cluster:admin/snapshot/create", |
| 137 | + "cluster:admin/snapshot/delete", |
| 138 | + "cluster:admin/snapshot/get", |
| 139 | + "cluster:admin/snapshot/restore", |
| 140 | + "cluster:admin/snapshot/status", |
| 141 | + "cluster:admin/snapshot/status[nodes]", |
| 142 | + "cluster:admin/tasks/cancel", |
| 143 | + "cluster:admin/xpack/license/basic_status", |
| 144 | + "cluster:admin/xpack/license/feature_usage", |
| 145 | + "cluster:admin/xpack/license/start_basic", |
| 146 | + "cluster:admin/xpack/license/start_trial", |
| 147 | + "cluster:admin/xpack/license/trial_status", |
| 148 | + "cluster:admin/xpack/monitoring/bulk", |
| 149 | + "cluster:admin/xpack/security/api_key/create", |
| 150 | + "cluster:admin/xpack/security/api_key/get", |
| 151 | + "cluster:admin/xpack/security/api_key/grant", |
| 152 | + "cluster:admin/xpack/security/api_key/invalidate", |
| 153 | + "cluster:admin/xpack/security/cache/clear", |
| 154 | + "cluster:admin/xpack/security/delegate_pki", |
| 155 | + "cluster:admin/xpack/security/oidc/authenticate", |
| 156 | + "cluster:admin/xpack/security/oidc/logout", |
| 157 | + "cluster:admin/xpack/security/oidc/prepare", |
| 158 | + "cluster:admin/xpack/security/privilege/builtin/get", |
| 159 | + "cluster:admin/xpack/security/privilege/cache/clear", |
| 160 | + "cluster:admin/xpack/security/privilege/delete", |
| 161 | + "cluster:admin/xpack/security/privilege/get", |
| 162 | + "cluster:admin/xpack/security/privilege/put", |
| 163 | + "cluster:admin/xpack/security/realm/cache/clear", |
| 164 | + "cluster:admin/xpack/security/role/delete", |
| 165 | + "cluster:admin/xpack/security/role/get", |
| 166 | + "cluster:admin/xpack/security/role/put", |
| 167 | + "cluster:admin/xpack/security/role_mapping/delete", |
| 168 | + "cluster:admin/xpack/security/role_mapping/get", |
| 169 | + "cluster:admin/xpack/security/role_mapping/put", |
| 170 | + "cluster:admin/xpack/security/roles/cache/clear", |
| 171 | + "cluster:admin/xpack/security/saml/authenticate", |
| 172 | + "cluster:admin/xpack/security/saml/complete_logout", |
| 173 | + "cluster:admin/xpack/security/saml/invalidate", |
| 174 | + "cluster:admin/xpack/security/saml/logout", |
| 175 | + "cluster:admin/xpack/security/saml/prepare", |
| 176 | + "cluster:admin/xpack/security/token/create", |
| 177 | + "cluster:admin/xpack/security/token/invalidate", |
| 178 | + "cluster:admin/xpack/security/token/refresh", |
| 179 | + "cluster:admin/xpack/security/user/authenticate", |
| 180 | + "cluster:admin/xpack/security/user/change_password", |
| 181 | + "cluster:admin/xpack/security/user/delete", |
| 182 | + "cluster:admin/xpack/security/user/get", |
| 183 | + "cluster:admin/xpack/security/user/has_privileges", |
| 184 | + "cluster:admin/xpack/security/user/list_privileges", |
| 185 | + "cluster:admin/xpack/security/user/put", |
| 186 | + "cluster:admin/xpack/security/user/set_enabled", |
| 187 | + "cluster:monitor/allocation/explain", |
| 188 | + "cluster:monitor/health", |
| 189 | + "cluster:monitor/main", |
| 190 | + "cluster:monitor/nodes/hot_threads", |
| 191 | + "cluster:monitor/nodes/info", |
| 192 | + "cluster:monitor/nodes/stats", |
| 193 | + "cluster:monitor/nodes/usage", |
| 194 | + "cluster:monitor/remote/info", |
| 195 | + "cluster:monitor/state", |
| 196 | + "cluster:monitor/stats", |
| 197 | + "cluster:monitor/task", |
| 198 | + "cluster:monitor/task/get", |
| 199 | + "cluster:monitor/tasks/lists", |
| 200 | + "cluster:monitor/xpack/info", |
| 201 | + "cluster:monitor/xpack/info/data_tiers", |
| 202 | + "cluster:monitor/xpack/info/monitoring", |
| 203 | + "cluster:monitor/xpack/info/security", |
| 204 | + "cluster:monitor/xpack/license/get", |
| 205 | + "cluster:monitor/xpack/security/saml/metadata", |
| 206 | + "cluster:monitor/xpack/ssl/certificates/get", |
| 207 | + "cluster:monitor/xpack/usage", |
| 208 | + "cluster:monitor/xpack/usage/data_tiers", |
| 209 | + "cluster:monitor/xpack/usage/monitoring", |
| 210 | + "cluster:monitor/xpack/usage/security", |
| 211 | + "indices:admin/aliases", |
| 212 | + "indices:admin/aliases/get", |
| 213 | + "indices:admin/analyze", |
| 214 | + "indices:admin/auto_create", |
| 215 | + "indices:admin/block/add", |
| 216 | + "indices:admin/block/add[s]", |
| 217 | + "indices:admin/cache/clear", |
| 218 | + "indices:admin/close", |
| 219 | + "indices:admin/close[s]", |
| 220 | + "indices:admin/create", |
| 221 | + "indices:admin/delete", |
| 222 | + "indices:admin/flush", |
| 223 | + "indices:admin/flush[s]", |
| 224 | + "indices:admin/forcemerge", |
| 225 | + "indices:admin/get", |
| 226 | + "indices:admin/index_template/delete", |
| 227 | + "indices:admin/index_template/get", |
| 228 | + "indices:admin/index_template/put", |
| 229 | + "indices:admin/index_template/simulate", |
| 230 | + "indices:admin/index_template/simulate_index", |
| 231 | + "indices:admin/mapping/auto_put", |
| 232 | + "indices:admin/mapping/put", |
| 233 | + "indices:admin/mappings/fields/get", |
| 234 | + "indices:admin/mappings/fields/get[index]", |
| 235 | + "indices:admin/mappings/get", |
| 236 | + "indices:admin/open", |
| 237 | + "indices:admin/refresh", |
| 238 | + "indices:admin/refresh[s]", |
| 239 | + "indices:admin/reload_analyzers", |
| 240 | + "indices:admin/resize", |
| 241 | + "indices:admin/resolve/index", |
| 242 | + "indices:admin/rollover", |
| 243 | + "indices:admin/seq_no/add_retention_lease", |
| 244 | + "indices:admin/seq_no/global_checkpoint_sync", |
| 245 | + "indices:admin/seq_no/remove_retention_lease", |
| 246 | + "indices:admin/seq_no/renew_retention_lease", |
| 247 | + "indices:admin/settings/update", |
| 248 | + "indices:admin/shards/search_shards", |
| 249 | + "indices:admin/template/delete", |
| 250 | + "indices:admin/template/get", |
| 251 | + "indices:admin/template/put", |
| 252 | + "indices:admin/validate/query", |
| 253 | + "indices:data/read/async_search/delete", |
| 254 | + "indices:data/read/close_point_in_time", |
| 255 | + "indices:data/read/explain", |
| 256 | + "indices:data/read/field_caps", |
| 257 | + "indices:data/read/field_caps[index]", |
| 258 | + "indices:data/read/get", |
| 259 | + "indices:data/read/mget", |
| 260 | + "indices:data/read/mget[shard]", |
| 261 | + "indices:data/read/msearch", |
| 262 | + "indices:data/read/mtv", |
| 263 | + "indices:data/read/mtv[shard]", |
| 264 | + "indices:data/read/open_point_in_time", |
| 265 | + "indices:data/read/scroll", |
| 266 | + "indices:data/read/scroll/clear", |
| 267 | + "indices:data/read/search", |
| 268 | + "indices:data/read/tv", |
| 269 | + "indices:data/write/bulk", |
| 270 | + "indices:data/write/bulk[s]", |
| 271 | + "indices:data/write/delete", |
| 272 | + "indices:data/write/delete/byquery", |
| 273 | + "indices:data/write/index", |
| 274 | + "indices:data/write/reindex", |
| 275 | + "indices:data/write/update", |
| 276 | + "indices:data/write/update/byquery", |
| 277 | + "indices:monitor/recovery", |
| 278 | + "indices:monitor/segments", |
| 279 | + "indices:monitor/settings/get", |
| 280 | + "indices:monitor/shard_stores", |
| 281 | + "indices:monitor/stats", |
| 282 | + "internal:cluster/nodes/indices/shard/store", |
| 283 | + "internal:gateway/local/meta_state", |
| 284 | + "internal:gateway/local/started_shards" |
| 285 | + }; |
| 286 | +} |
0 commit comments