|
| 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 | + * 2.0 and the Server Side Public License, v 1; you may not use this file except |
| 5 | + * in compliance with, at your election, the Elastic License 2.0 or the Server |
| 6 | + * Side Public License, v 1. |
| 7 | + */ |
| 8 | + |
| 9 | +package org.elasticsearch.dlm; |
| 10 | + |
| 11 | +import org.apache.http.HttpHost; |
| 12 | +import org.elasticsearch.client.Request; |
| 13 | +import org.elasticsearch.client.Response; |
| 14 | +import org.elasticsearch.client.ResponseException; |
| 15 | +import org.elasticsearch.client.RestClient; |
| 16 | +import org.elasticsearch.common.Strings; |
| 17 | +import org.elasticsearch.common.settings.SecureString; |
| 18 | +import org.elasticsearch.common.settings.Settings; |
| 19 | +import org.elasticsearch.common.util.concurrent.ThreadContext; |
| 20 | +import org.elasticsearch.rest.RestStatus; |
| 21 | +import org.elasticsearch.test.rest.ESRestTestCase; |
| 22 | +import org.elasticsearch.test.rest.ObjectPath; |
| 23 | + |
| 24 | +import java.io.IOException; |
| 25 | +import java.util.List; |
| 26 | +import java.util.Map; |
| 27 | + |
| 28 | +import static org.hamcrest.Matchers.equalTo; |
| 29 | + |
| 30 | +public class PermissionsIT extends ESRestTestCase { |
| 31 | + |
| 32 | + @Override |
| 33 | + protected Settings restClientSettings() { |
| 34 | + // Note: This user is defined in build.gradle, and assigned the role "manage_dlm". That role is defined in roles.yml. |
| 35 | + String token = basicAuthHeaderValue("test_dlm", new SecureString("x-pack-test-password".toCharArray())); |
| 36 | + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); |
| 37 | + } |
| 38 | + |
| 39 | + @Override |
| 40 | + protected Settings restAdminSettings() { |
| 41 | + String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray())); |
| 42 | + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); |
| 43 | + } |
| 44 | + |
| 45 | + private Settings restUnprivilegedClientSettings() { |
| 46 | + // Note: This user is defined in build.gradle, and assigned the role "not_privileged". That role is defined in roles.yml. |
| 47 | + String token = basicAuthHeaderValue("test_non_privileged", new SecureString("x-pack-test-password".toCharArray())); |
| 48 | + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build(); |
| 49 | + } |
| 50 | + |
| 51 | + @SuppressWarnings("unchecked") |
| 52 | + public void testManageDLM() throws Exception { |
| 53 | + { |
| 54 | + /* |
| 55 | + * This test checks that a user with the "manage_dlm" index privilege on "dlm-*" data streams can delete and put a lifecycle |
| 56 | + * on the "dlm-test" data stream, while a user with who does not have that privilege (but does have all of the other same |
| 57 | + * "dlm-*" privileges) cannot delete or put a lifecycle on that datastream. |
| 58 | + */ |
| 59 | + String dataStreamName = "dlm-test"; // Needs to match the pattern of the names in roles.yml |
| 60 | + createDataStreamAsAdmin(dataStreamName); |
| 61 | + Response getDatastreamRepsonse = adminClient().performRequest(new Request("GET", "/_data_stream/" + dataStreamName)); |
| 62 | + final List<Map<String, Object>> nodes = ObjectPath.createFromResponse(getDatastreamRepsonse).evaluate("data_streams"); |
| 63 | + String index = (String) ((List<Map<String, Object>>) nodes.get(0).get("indices")).get(0).get("index_name"); |
| 64 | + |
| 65 | + Request explainLifecycleRequest = new Request("GET", "/" + randomFrom("_all", "*", index) + "/_lifecycle/explain"); |
| 66 | + Request getLifecycleRequest = new Request("GET", "_data_stream/" + randomFrom("_all", "*", dataStreamName) + "/_lifecycle"); |
| 67 | + Request deleteLifecycleRequest = new Request( |
| 68 | + "DELETE", |
| 69 | + "_data_stream/" + randomFrom("_all", "*", dataStreamName) + "/_lifecycle" |
| 70 | + ); |
| 71 | + Request putLifecycleRequest = new Request("PUT", "_data_stream/" + randomFrom("_all", "*", dataStreamName) + "/_lifecycle"); |
| 72 | + putLifecycleRequest.setJsonEntity("{}"); |
| 73 | + |
| 74 | + makeRequest(client(), explainLifecycleRequest, true); |
| 75 | + makeRequest(client(), getLifecycleRequest, true); |
| 76 | + makeRequest(client(), deleteLifecycleRequest, true); |
| 77 | + makeRequest(client(), putLifecycleRequest, true); |
| 78 | + |
| 79 | + try ( |
| 80 | + RestClient nonDlmManagerClient = buildClient(restUnprivilegedClientSettings(), getClusterHosts().toArray(new HttpHost[0])) |
| 81 | + ) { |
| 82 | + makeRequest(nonDlmManagerClient, explainLifecycleRequest, true); |
| 83 | + makeRequest(nonDlmManagerClient, getLifecycleRequest, true); |
| 84 | + makeRequest(nonDlmManagerClient, deleteLifecycleRequest, false); |
| 85 | + makeRequest(nonDlmManagerClient, putLifecycleRequest, false); |
| 86 | + } |
| 87 | + } |
| 88 | + { |
| 89 | + // Now test that the user who has the manage_dlm privilege on dlm-* data streams cannot manage other data streams: |
| 90 | + String otherDataStreamName = "other-dlm-test"; |
| 91 | + createDataStreamAsAdmin(otherDataStreamName); |
| 92 | + Response getOtherDataStreamResponse = adminClient().performRequest(new Request("GET", "/_data_stream/" + otherDataStreamName)); |
| 93 | + final List<Map<String, Object>> otherNodes = ObjectPath.createFromResponse(getOtherDataStreamResponse).evaluate("data_streams"); |
| 94 | + String otherIndex = (String) ((List<Map<String, Object>>) otherNodes.get(0).get("indices")).get(0).get("index_name"); |
| 95 | + Request putOtherLifecycleRequest = new Request("PUT", "_data_stream/" + otherDataStreamName + "/_lifecycle"); |
| 96 | + putOtherLifecycleRequest.setJsonEntity("{}"); |
| 97 | + makeRequest(client(), new Request("GET", "/" + otherIndex + "/_lifecycle/explain"), false); |
| 98 | + makeRequest(client(), new Request("GET", "_data_stream/" + otherDataStreamName + "/_lifecycle"), false); |
| 99 | + makeRequest(client(), new Request("DELETE", "_data_stream/" + otherDataStreamName + "/_lifecycle"), false); |
| 100 | + makeRequest(client(), putOtherLifecycleRequest, false); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + /* |
| 105 | + * This makes the given request with the given client. It asserts a 200 response if expectSuccess is true, and asserts an exception |
| 106 | + * with a 403 response if expectStatus is false. |
| 107 | + */ |
| 108 | + private void makeRequest(RestClient client, Request request, boolean expectSuccess) throws IOException { |
| 109 | + if (expectSuccess) { |
| 110 | + Response response = client.performRequest(request); |
| 111 | + assertThat(response.getStatusLine().getStatusCode(), equalTo(RestStatus.OK.getStatus())); |
| 112 | + } else { |
| 113 | + ResponseException exception = expectThrows(ResponseException.class, () -> client.performRequest(request)); |
| 114 | + assertThat(exception.getResponse().getStatusLine().getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + private void createDataStreamAsAdmin(String name) throws IOException { |
| 119 | + String mappingsTemplateName = name + "_mappings"; |
| 120 | + Request mappingsRequest = new Request("PUT", "/_component_template/" + mappingsTemplateName); |
| 121 | + mappingsRequest.setJsonEntity(""" |
| 122 | + { |
| 123 | + "template": { |
| 124 | + "mappings": { |
| 125 | + "properties": { |
| 126 | + "@timestamp": { |
| 127 | + "type": "date", |
| 128 | + "format": "date_optional_time||epoch_millis" |
| 129 | + }, |
| 130 | + "message": { |
| 131 | + "type": "wildcard" |
| 132 | + } |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + }"""); |
| 137 | + assertOK(adminClient().performRequest(mappingsRequest)); |
| 138 | + |
| 139 | + String settingsTemplateName = name + "_settings"; |
| 140 | + Request settingsRequest = new Request("PUT", "/_component_template/" + settingsTemplateName); |
| 141 | + settingsRequest.setJsonEntity(""" |
| 142 | + { |
| 143 | + "template": { |
| 144 | + "settings": { |
| 145 | + "number_of_shards": 1, |
| 146 | + "number_of_replicas": 0 |
| 147 | + } |
| 148 | + } |
| 149 | + }"""); |
| 150 | + assertOK(adminClient().performRequest(settingsRequest)); |
| 151 | + |
| 152 | + Request indexTemplateRequest = new Request("PUT", "/_index_template/" + name + "_template"); |
| 153 | + indexTemplateRequest.setJsonEntity(Strings.format(""" |
| 154 | + { |
| 155 | + "index_patterns": ["%s*"], |
| 156 | + "data_stream": { }, |
| 157 | + "composed_of": [ "%s", "%s" ] |
| 158 | + }""", name, mappingsTemplateName, settingsTemplateName)); |
| 159 | + assertOK(adminClient().performRequest(indexTemplateRequest)); |
| 160 | + |
| 161 | + Request request = new Request("PUT", "/_data_stream/" + name); |
| 162 | + assertOK(adminClient().performRequest(request)); |
| 163 | + } |
| 164 | + |
| 165 | +} |
0 commit comments