diff --git a/.gitignore b/.gitignore
index aa7ca99..6fd04f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,5 @@ data/
puppet/.vagrant
test.sh
.vagrant/
+.idea/
+*.iml
diff --git a/pom.xml b/pom.xml
index c7f8a29..b79d708 100755
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
opendistro_security_advanced_modules
- 0.7.0.1
+ 0.7.0.2
jar
Open Distro Security Advanced Modules for Elasticsearch
@@ -34,7 +34,7 @@
2016
- 0.7.0.1
+ 0.7.0.2
6.5.4
@@ -55,7 +55,7 @@
https://github.com/opendistro-for-elasticsearch/security-advanced-modules
scm:git:git@github.com:opendistro-for-elasticsearch/security-advanced-modules.git
scm:git:git@github.com:opendistro-for-elasticsearch/security-advanced-modules.git
- v0.7.0.1
+ v0.7.0.2
diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/OpenDistroSecurityConfigAction.java b/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/OpenDistroSecurityConfigAction.java
index 94e82ae..eb7a7a2 100644
--- a/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/OpenDistroSecurityConfigAction.java
+++ b/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/OpenDistroSecurityConfigAction.java
@@ -15,6 +15,7 @@
package com.amazon.opendistroforelasticsearch.security.dlic.rest.api;
+import java.io.IOException;
import java.nio.file.Path;
import org.elasticsearch.client.Client;
@@ -23,60 +24,91 @@
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.BytesRestResponse;
+import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.RestRequest.Method;
import org.elasticsearch.rest.RestResponse;
+import org.elasticsearch.rest.RestRequest.Method;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.threadpool.ThreadPool;
import com.amazon.opendistroforelasticsearch.security.auditlog.AuditLog;
import com.amazon.opendistroforelasticsearch.security.configuration.AdminDNs;
import com.amazon.opendistroforelasticsearch.security.configuration.IndexBaseConfigurationRepository;
+import com.amazon.opendistroforelasticsearch.security.dlic.rest.support.Utils;
import com.amazon.opendistroforelasticsearch.security.dlic.rest.validation.AbstractConfigurationValidator;
-import com.amazon.opendistroforelasticsearch.security.dlic.rest.validation.NoOpValidator;
+import com.amazon.opendistroforelasticsearch.security.dlic.rest.validation.SecurityConfigValidator;
import com.amazon.opendistroforelasticsearch.security.privileges.PrivilegesEvaluator;
import com.amazon.opendistroforelasticsearch.security.ssl.transport.PrincipalExtractor;
import com.amazon.opendistroforelasticsearch.security.support.ConfigConstants;
-public class OpenDistroSecurityConfigAction extends AbstractApiAction {
+public class OpenDistroSecurityConfigAction extends PatchableResourceApiAction {
+
+ private final boolean allowPutOrPatch;
@Inject
public OpenDistroSecurityConfigAction(final Settings settings, final Path configPath, final RestController controller, final Client client,
- final AdminDNs adminDNs, final IndexBaseConfigurationRepository cl, final ClusterService cs,
- final PrincipalExtractor principalExtractor, final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) {
+ final AdminDNs adminDNs, final IndexBaseConfigurationRepository cl, final ClusterService cs, final PrincipalExtractor principalExtractor,
+ final PrivilegesEvaluator evaluator, ThreadPool threadPool, AuditLog auditLog) {
super(settings, configPath, controller, client, adminDNs, cl, cs, principalExtractor, evaluator, threadPool, auditLog);
- controller.registerHandler(Method.GET, "/_opendistro/_security/api/config/", this);
- }
+ allowPutOrPatch = settings.getAsBoolean(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, false);
+ controller.registerHandler(Method.GET, "/_opendistro/_security/api/securityconfig/", this);
+
+ if (allowPutOrPatch) {
+ controller.registerHandler(Method.PUT, "/_opendistro/_security/api/securityconfig/{name}", this);
+ controller.registerHandler(Method.PATCH, "/_opendistro/_security/api/securityconfig/", this);
+ }
+
+ }
@Override
- protected Tuple handleGet(RestRequest request, Client client,
- final Settings.Builder additionalSettingsBuilder) throws Throwable {
+ protected Tuple handleApiRequest(RestRequest request, Client client) throws Throwable {
+ if (request.method() == Method.PATCH && !allowPutOrPatch) {
+ return notImplemented(Method.PATCH);
+ } else {
+ return super.handleApiRequest(request, client);
+ }
+ }
+ @Override
+ protected Tuple handleGet(RestRequest request, Client client, final Settings.Builder additionalSettingsBuilder) throws IOException {
final Settings configurationSettings = loadAsSettings(getConfigName(), true);
-
return new Tuple(new String[0],
new BytesRestResponse(RestStatus.OK, convertToJson(configurationSettings)));
}
@Override
- protected Tuple handlePut(final RestRequest request, final Client client,
- final Settings.Builder additionalSettings) throws Throwable {
- return notImplemented(Method.PUT);
+ protected Tuple handlePut(final RestRequest request, final Client client, final Settings.Builder additionalSettings)
+ throws Throwable {
+ if (allowPutOrPatch) {
+ if (!"opendistro_security".equals(request.param("name"))) {
+ return badRequestResponse("name must be opendistro_security");
+ }
+ return super.handlePut(request, client, additionalSettings);
+ } else {
+ return notImplemented(Method.PUT);
+ }
}
@Override
- protected Tuple handleDelete(final RestRequest request, final Client client,
- final Settings.Builder additionalSettings) throws Throwable {
+ protected Tuple handleDelete(final RestRequest request, final Client client, final Settings.Builder additionalSettings) {
return notImplemented(Method.DELETE);
}
+ @Override
+ protected Tuple handlePost(final RestRequest request, final Client client, final Settings.Builder additionalSetting)
+ throws IOException {
+ return notImplemented(Method.POST);
+ }
+
@Override
protected AbstractConfigurationValidator getValidator(RestRequest request, BytesReference ref, Object... param) {
- return new NoOpValidator(request, ref, this.settings, param);
+ return new SecurityConfigValidator(request, ref, this.settings, param);
}
@Override
diff --git a/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/validation/SecurityConfigValidator.java b/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/validation/SecurityConfigValidator.java
new file mode 100644
index 0000000..a60ab77
--- /dev/null
+++ b/src/main/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/validation/SecurityConfigValidator.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015-2018 _floragunn_ GmbH
+ * Licensed 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.
+ */
+
+/*
+ * Portions Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.security.dlic.rest.validation;
+
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.rest.RestRequest;
+
+public class SecurityConfigValidator extends AbstractConfigurationValidator {
+
+ public SecurityConfigValidator(final RestRequest request, BytesReference ref, final Settings esSettings, Object... param) {
+ super(request, ref, esSettings, param);
+ this.payloadMandatory = true;
+ allowedKeys.put("dynamic", DataType.OBJECT);
+ }
+
+}
diff --git a/src/test/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/SecurityConfigApiTest.java b/src/test/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/SecurityConfigApiTest.java
new file mode 100644
index 0000000..59d9611
--- /dev/null
+++ b/src/test/java/com/amazon/opendistroforelasticsearch/security/dlic/rest/api/SecurityConfigApiTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015-2018 _floragunn_ GmbH
+ * Licensed 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.
+ */
+
+/*
+ * Portions Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * or in the "license" file accompanying this file. This file 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 com.amazon.opendistroforelasticsearch.security.dlic.rest.api;
+
+import org.apache.http.Header;
+import org.apache.http.HttpStatus;
+import org.elasticsearch.common.settings.Settings;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.amazon.opendistroforelasticsearch.security.support.ConfigConstants;
+import com.amazon.opendistroforelasticsearch.security.test.helper.file.FileHelper;
+import com.amazon.opendistroforelasticsearch.security.test.helper.rest.RestHelper.HttpResponse;
+
+public class SecurityConfigApiTest extends AbstractRestApiUnitTest {
+
+ @Test
+ public void testSecurityConfigApiRead() throws Exception {
+
+ setup();
+
+ rh.keystore = "restapi/kirk-keystore.jks";
+ rh.sendHTTPClientCertificate = true;
+
+ HttpResponse response = rh.executeGetRequest("/_opendistro/_security/api/securityconfig", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+
+ response = rh.executePutRequest("/_opendistro/_security/api/securityconfig", "{\"xxx\": 1}", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ response = rh.executePostRequest("/_opendistro/_security/api/securityconfig", "{\"xxx\": 1}", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ response = rh.executePatchRequest("/_opendistro/_security/api/securityconfig", "{\"xxx\": 1}", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ response = rh.executeDeleteRequest("/_opendistro/_security/api/securityconfig", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ }
+
+ @Test
+ public void testSecurityConfigApiWrite() throws Exception {
+
+ Settings settings = Settings.builder().put(ConfigConstants.OPENDISTRO_SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION, true).build();
+ setup(settings);
+
+ rh.keystore = "restapi/kirk-keystore.jks";
+ rh.sendHTTPClientCertificate = true;
+
+ HttpResponse response = rh.executeGetRequest("/_opendistro/_security/api/securityconfig", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+
+ response = rh.executePutRequest("/_opendistro/_security/api/securityconfig/opendistro_security_xxx", FileHelper.loadFile("restapi/securityconfig.json"), new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_BAD_REQUEST, response.getStatusCode());
+
+ response = rh.executePutRequest("/_opendistro/_security/api/securityconfig/opendistro_security", FileHelper.loadFile("restapi/securityconfig.json"), new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+
+ response = rh.executeGetRequest("/_opendistro/_security/api/securityconfig", new Header[0]);
+ System.out.println(response.getBody());
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+
+ response = rh.executePostRequest("/_opendistro/_security/api/securityconfig", "{\"xxx\": 1}", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ response = rh.executePatchRequest("/_opendistro/_security/api/securityconfig", "[{\"op\": \"replace\",\"path\": \"/opendistro_security/dynamic/hosts_resolver_mode\",\"value\": \"other\"}]", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_OK, response.getStatusCode());
+
+ response = rh.executeDeleteRequest("/_opendistro/_security/api/securityconfig", new Header[0]);
+ Assert.assertEquals(HttpStatus.SC_METHOD_NOT_ALLOWED, response.getStatusCode());
+
+ }
+}
diff --git a/src/test/resources/restapi/securityconfig.json b/src/test/resources/restapi/securityconfig.json
new file mode 100644
index 0000000..883677d
--- /dev/null
+++ b/src/test/resources/restapi/securityconfig.json
@@ -0,0 +1,129 @@
+{
+ "dynamic":{
+ "filtered_alias_mode":"warn",
+ "disable_rest_auth": false,
+ "disable_intertransport_auth":false,
+ "respect_request_indices_options":false,
+ "kibana":{
+ "multitenancy_enabled":true,
+ "server_username":"kibanaserver",
+ "index":".kibana"
+ },
+ "http":{
+ "anonymous_auth_enabled":false,
+ "xff":{
+ "enabled":false,
+ "internalProxies":"192\\.168\\.0\\.10|192\\.168\\.0\\.11",
+ "remoteIpHeader":"x-forwarded-for",
+ "proxiesHeader":"x-forwarded-by",
+ "trustedProxies":"proxy1|proxy2"
+ }
+ },
+ "authc":{
+ "authentication_domain_kerb":{
+ "http_enabled":false,
+ "transport_enabled":false,
+ "order":3,
+ "http_authenticator":{
+ "challenge":true,
+ "type":"kerberos",
+ "config":{
+
+ }
+ },
+ "authentication_backend":{
+ "type":"noop",
+ "config":{
+
+ }
+ }
+ },
+ "authentication_domain_clientcert":{
+ "http_enabled":false,
+ "transport_enabled":false,
+ "order":1,
+ "http_authenticator":{
+ "challenge":true,
+ "type":"clientcert",
+ "config":{
+
+ }
+ },
+ "authentication_backend":{
+ "type":"noop",
+ "config":{
+
+ }
+ }
+ },
+ "authentication_domain_proxy":{
+ "http_enabled":false,
+ "transport_enabled":false,
+ "order":2,
+ "http_authenticator":{
+ "challenge":true,
+ "type":"proxy",
+ "config":{
+ "user_header":"x-proxy-user",
+ "roles_header":"x-proxy-roles"
+ }
+ },
+ "authentication_backend":{
+ "type":"noop",
+ "config":{
+
+ }
+ }
+ },
+ "authentication_domain_basic_internal":{
+ "http_enabled":true,
+ "transport_enabled":true,
+ "order":0,
+ "http_authenticator":{
+ "challenge":true,
+ "type":"basic",
+ "config":{
+
+ }
+ },
+ "authentication_backend":{
+ "type":"intern",
+ "config":{
+
+ }
+ }
+ }
+ },
+ "authz":{
+ "roles_from_xxx":{
+ "http_enabled":false,
+ "transport_enabled":false,
+ "order":0,
+ "authorization_backend":{
+ "type":"xxx",
+ "config":{
+
+ }
+ }
+ },
+ "roles_from_myldap":{
+ "http_enabled":false,
+ "transport_enabled":false,
+ "order":0,
+ "authorization_backend":{
+ "type":"ldap",
+ "config":{
+ "rolesearch":"(uniqueMember={0})",
+ "resolve_nested_roles":true,
+ "rolebase":"ou=groups,o=TEST",
+ "rolename":"cn"
+ }
+ }
+ }
+ },
+ "do_not_fail_on_forbidden":false,
+ "multi_rolespan_enabled":false,
+ "hosts_resolver_mode":"ip-only"
+ }
+
+}