diff --git a/conf/ranger-ssm-audit.xml b/conf/ranger-ssm-audit.xml new file mode 100644 index 0000000000..63cda06ac3 --- /dev/null +++ b/conf/ranger-ssm-audit.xml @@ -0,0 +1,51 @@ + + + + + + xasecure.audit.destination.db + false + + + + xasecure.audit.destination.db.jdbc.driver + com.mysql.jdbc.Driver + + + + xasecure.audit.destination.db.password + rangerlogger + + + + xasecure.audit.destination.db.user + rangerlogger + + + + xasecure.audit.destination.db.batch.filespool.dir + /tmp/audit/db/spool + + + + + xasecure.audit.destination.hdfs + false + + + + xasecure.audit.destination.hdfs.batch.filespool.dir + /tmp/audit/hdfs/spool + + + + + xasecure.audit.destination.log4j + true + + + + xasecure.audit.destination.log4j.logger + ranger_audit_logger + + \ No newline at end of file diff --git a/conf/ranger-ssm-security.xml b/conf/ranger-ssm-security.xml new file mode 100644 index 0000000000..da56253287 --- /dev/null +++ b/conf/ranger-ssm-security.xml @@ -0,0 +1,76 @@ + + + + + + + ranger.plugin.ssm.service.name + ssm + + Name of the Ranger service containing policies for this ssm instance + + + + + ranger.plugin.ssm.policy.source.impl + org.apache.ranger.admin.client.RangerAdminRESTClient + + Class to retrieve policies from the source + + + + + ranger.plugin.ssm.policy.rest.ssl.config.file + ranger-policymgr-ssl.xml + + Path to the file containing SSL details to contact Ranger Admin + + + + + ranger.plugin.ssm.policy.pollIntervalMs + 30000 + + How often to poll for changes in policies? + + + + + ranger.plugin.ssm.policy.cache.dir + /tmp + + Directory where Ranger policies are cached after successful retrieval from the source + + + + + ranger.plugin.ssm.policy.rest.client.connection.timeoutMs + 120000 + + RangerRestClient Connection Timeout in Milli Seconds + + + + + ranger.plugin.ssm.policy.rest.client.read.timeoutMs + 30000 + + RangerRestClient read Timeout in Milli Seconds + + + diff --git a/conf/smart-default.xml b/conf/smart-default.xml index 93bc839671..caed88a021 100644 --- a/conf/smart-default.xml +++ b/conf/smart-default.xml @@ -526,6 +526,14 @@ + + smart.rest.server.auth.ranger.enabled + false + + Whether to enable Apache Ranger authorization for SSM REST server. + + + smart.action.client.cache.ttl 10m diff --git a/conf/ssm-ranger.json b/conf/ssm-ranger.json new file mode 100644 index 0000000000..d7c16eb2d8 --- /dev/null +++ b/conf/ssm-ranger.json @@ -0,0 +1,145 @@ +{ + "name": "ssm", + "label": "SSM Service", + "description": "SSM Ranger Security Plugin", + "guid": "b8290b7f-6f69-44a9-89cc-06b6975ea676", + "implClass": "io.arenadata.ranger.service.ssm.SsmRangerService", + "version": 1, + "isEnabled": 1, + "resources": [ + { + "itemId": 1, + "name": "cluster", + "type": "string", + "level": 10, + "parent": "", + "mandatory": false, + "lookupSupported": true, + "recursiveSupported": true, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Cluster", + "description": "List of SSM cluster nodes", + "accessTypeRestrictions": [ + "VIEW" + ] + }, + { + "itemId": 2, + "name": "rule", + "type": "string", + "level": 10, + "parent": "", + "mandatory": false, + "lookupSupported": true, + "recursiveSupported": true, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Rule", + "description": "Rule", + "accessTypeRestrictions": [ + "VIEW", + "CREATE", + "DELETE", + "EDIT" + ] + }, + { + "itemId": 3, + "name": "action", + "type": "string", + "level": 10, + "parent": "", + "mandatory": false, + "lookupSupported": true, + "recursiveSupported": true, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Action", + "description": "Action", + "accessTypeRestrictions": [ + "VIEW", "SUBMIT" + ] + }, + { + "itemId": 4, + "name": "audit", + "type": "string", + "level": 10, + "parent": "", + "mandatory": false, + "lookupSupported": true, + "recursiveSupported": true, + "excludesSupported": true, + "matcher": "org.apache.ranger.plugin.resourcematcher.RangerDefaultResourceMatcher", + "matcherOptions": { + "wildCard": true, + "ignoreCase": true + }, + "validationRegEx": "", + "validationMessage": "", + "uiHint": "", + "label": "Audit", + "description": "Audit", + "accessTypeRestrictions": [ + "VIEW" + ] + } + ], + "accessTypes": [ + { + "itemId": 1, + "name": "VIEW", + "label": "View" + }, + { + "itemId": 2, + "name": "CREATE", + "label": "Create" + }, + { + "itemId": 3, + "name": "DELETE", + "label": "Delete" + }, + { + "itemId": 4, + "name": "EDIT", + "label": "Edit" + }, + { + "itemId": 5, + "name": "SUBMIT", + "label": "Submit action" + } + ], + "configs": [ + ], + "enums": [ + ], + "contextEnrichers": [ + ], + "policyConditions": [ + ] +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index ba1eea8c2d..f598a35f2d 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,7 @@ smart-dist smart-integration smart-web-server + smart-ranger-plugin smart-hadoop-support @@ -109,9 +110,13 @@ 2.6.1.Final smart-hadoop-3.3 smart-hadoop-client-3 + 2.5.0 + 5.8.0 1.5.3 org.apache.hadoop.thirdparty ${hadoop-thirdparty-shaded.prefix}.protobuf + 3.8.4 + 8.11.3 diff --git a/smart-integration/src/test/resources/ranger-ssm-audit.xml b/smart-integration/src/test/resources/ranger-ssm-audit.xml new file mode 100644 index 0000000000..d21c4fe5a7 --- /dev/null +++ b/smart-integration/src/test/resources/ranger-ssm-audit.xml @@ -0,0 +1,63 @@ + + + + + + xasecure.audit.destination.db + false + + + + xasecure.audit.destination.db.jdbc.driver + com.mysql.jdbc.Driver + + + + xasecure.audit.destination.db.jdbc.url + jdbc:mysql://10.92.3.109/ranger_audit + + + + xasecure.audit.destination.db.password + rangerlogger + + + + xasecure.audit.destination.db.user + rangerlogger + + + + xasecure.audit.destination.db.batch.filespool.dir + /tmp/audit/db/spool + + + + + + xasecure.audit.destination.hdfs + false + + + + xasecure.audit.destination.hdfs.dir + hdfs://10.92.3.109:8020/ranger/audit + + + + xasecure.audit.destination.hdfs.batch.filespool.dir + /tmp/audit/hdfs/spool + + + + + + xasecure.audit.destination.log4j + true + + + + xasecure.audit.destination.log4j.logger + ranger_audit_logger + + \ No newline at end of file diff --git a/smart-integration/src/test/resources/ranger-ssm-security.xml b/smart-integration/src/test/resources/ranger-ssm-security.xml new file mode 100644 index 0000000000..da56253287 --- /dev/null +++ b/smart-integration/src/test/resources/ranger-ssm-security.xml @@ -0,0 +1,76 @@ + + + + + + + ranger.plugin.ssm.service.name + ssm + + Name of the Ranger service containing policies for this ssm instance + + + + + ranger.plugin.ssm.policy.source.impl + org.apache.ranger.admin.client.RangerAdminRESTClient + + Class to retrieve policies from the source + + + + + ranger.plugin.ssm.policy.rest.ssl.config.file + ranger-policymgr-ssl.xml + + Path to the file containing SSL details to contact Ranger Admin + + + + + ranger.plugin.ssm.policy.pollIntervalMs + 30000 + + How often to poll for changes in policies? + + + + + ranger.plugin.ssm.policy.cache.dir + /tmp + + Directory where Ranger policies are cached after successful retrieval from the source + + + + + ranger.plugin.ssm.policy.rest.client.connection.timeoutMs + 120000 + + RangerRestClient Connection Timeout in Milli Seconds + + + + + ranger.plugin.ssm.policy.rest.client.read.timeoutMs + 30000 + + RangerRestClient read Timeout in Milli Seconds + + + diff --git a/smart-integration/src/test/resources/smart-default.xml b/smart-integration/src/test/resources/smart-default.xml index f399dde436..1fb2ee59a9 100644 --- a/smart-integration/src/test/resources/smart-default.xml +++ b/smart-integration/src/test/resources/smart-default.xml @@ -54,4 +54,29 @@ 5 Max number of rules that can be executed in parallel + + + smart.rest.server.security.enabled + false + + Whether to enable SSM REST server security. + + + + + smart.rest.server.auth.predefined.enabled + false + + Whether to enable SSM REST server basic authentication with users, + predefined in the 'smart.rest.server.auth.predefined.users' option. + + + + + smart.rest.server.auth.ranger.enabled + false + + Whether to enable Apache Ranger authorization for SSM REST server. + + diff --git a/smart-ranger-plugin/pom.xml b/smart-ranger-plugin/pom.xml new file mode 100644 index 0000000000..2f63cc93c4 --- /dev/null +++ b/smart-ranger-plugin/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + org.smartdata + smartdata-project + 2.0.0-SNAPSHOT + + + smart-ranger-plugin + smart-ranger-plugin + jar + + + + org.apache.ranger + ranger-plugins-common + ${ranger.version} + + + org.apache.kafka + kafka_2.11 + + + log4j + log4j + + + hadoop-common + org.apache.hadoop + + + + + org.projectlombok + lombok + provided + + + org.apache.zookeeper + zookeeper + ${zookeeper.version} + + + com.google.guava + guava + ${guava.version} + + + org.apache.zookeeper + zookeeper-jute + ${zookeeper.version} + + + org.apache.solr + solr-solrj + ${solrj.version} + + + diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmRangerResource.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmRangerResource.java new file mode 100644 index 0000000000..6696350d59 --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmRangerResource.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger; + +import com.google.common.collect.Sets; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.smartdata.ranger.authorizer.request.RangerOperationDto; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@Getter +@RequiredArgsConstructor +public enum SsmRangerResource { + CLUSTER(Collections.singleton(SsmResourceAccessType.VIEW)), + RULE(Sets.newHashSet(SsmResourceAccessType.CREATE, SsmResourceAccessType.VIEW, + SsmResourceAccessType.EDIT, SsmResourceAccessType.DELETE)), + ACTION(Sets.newHashSet(SsmResourceAccessType.SUBMIT, SsmResourceAccessType.VIEW)), + AUDIT(Collections.singleton(SsmResourceAccessType.VIEW)); + + private final Set accessTypes; + + public RangerOperationDto getRangerOperationDto(SsmResourceAccessType accessType, + String entityId) { + if (!accessTypes.contains(accessType)) { + throw new IllegalArgumentException("Unknown action: " + accessType); + } + Map resources = new HashMap<>(); + Optional.ofNullable(entityId) + .ifPresent(value -> resources.put(this.name().toLowerCase(), value)); + return new RangerOperationDto(accessType.name(), resources); + } +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmResourceAccessType.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmResourceAccessType.java new file mode 100644 index 0000000000..1ffa0f4b0b --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/SsmResourceAccessType.java @@ -0,0 +1,26 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger; + +public enum SsmResourceAccessType { + VIEW, + CREATE, + EDIT, + DELETE, + SUBMIT +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/RangerSsmAuthorizer.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/RangerSsmAuthorizer.java new file mode 100644 index 0000000000..7d960c50dc --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/RangerSsmAuthorizer.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger.authorizer; + +import org.smartdata.ranger.authorizer.request.RangerAuthorizeRequest; + +public interface RangerSsmAuthorizer { + + boolean authorize(RangerAuthorizeRequest request); +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/impl/RangerSsmAuthorizerImpl.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/impl/RangerSsmAuthorizerImpl.java new file mode 100644 index 0000000000..e821bb5cc5 --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/impl/RangerSsmAuthorizerImpl.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger.authorizer.impl; + +import lombok.extern.slf4j.Slf4j; +import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +import org.smartdata.ranger.authorizer.RangerSsmAuthorizer; +import org.smartdata.ranger.authorizer.request.RangerAuthorizeRequest; +import org.smartdata.ranger.plugin.impl.RangerSsmPlugin; + +import java.util.Date; + +@Slf4j +public class RangerSsmAuthorizerImpl implements RangerSsmAuthorizer { + + private final RangerSsmPlugin ssmPlugin; + + public RangerSsmAuthorizerImpl() { + log.debug("Trying to create RangerSsmAuthorizer"); + ssmPlugin = RangerSsmPlugin.getInstance(); + log.debug("RangerSsmAuthorizer created"); + } + + @Override + public boolean authorize(RangerAuthorizeRequest request) { + log.debug("Perform authorization checking [user={}],[resources={}],[accessType={}]", + request.getUser(), request.getOperationDto().getResources(), + request.getOperationDto().getAccessType()); + RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(); + rangerRequest.setUser(request.getUser()); + rangerRequest.setAccessType(request.getOperationDto().getAccessType()); + rangerRequest.setAccessTime(new Date()); + RangerAccessResourceImpl resource = new RangerAccessResourceImpl(); + request.getOperationDto().getResources().forEach(resource::setValue); + rangerRequest.setResource(resource); + RangerAccessResult result = ssmPlugin.isAccessAllowed(rangerRequest); + boolean checkResult = result != null && result.getIsAllowed(); + log.debug("Authorization check [result={}]", checkResult); + return checkResult; + } +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerAuthorizeRequest.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerAuthorizeRequest.java new file mode 100644 index 0000000000..53e744fc51 --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerAuthorizeRequest.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger.authorizer.request; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@EqualsAndHashCode +@Getter +@RequiredArgsConstructor +public class RangerAuthorizeRequest { + private final String user; + private final RangerOperationDto operationDto; +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerOperationDto.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerOperationDto.java new file mode 100644 index 0000000000..2e01150e80 --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/authorizer/request/RangerOperationDto.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger.authorizer.request; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Map; + +@EqualsAndHashCode +@Getter +@RequiredArgsConstructor +public class RangerOperationDto { + private final String accessType; + private final Map resources; +} diff --git a/smart-ranger-plugin/src/main/java/org/smartdata/ranger/plugin/impl/RangerSsmPlugin.java b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/plugin/impl/RangerSsmPlugin.java new file mode 100644 index 0000000000..83d11d0faa --- /dev/null +++ b/smart-ranger-plugin/src/main/java/org/smartdata/ranger/plugin/impl/RangerSsmPlugin.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.ranger.plugin.impl; + +import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; +import org.apache.ranger.plugin.service.RangerBasePlugin; + +public class RangerSsmPlugin extends RangerBasePlugin { + + private static final RangerSsmPlugin instance = new RangerSsmPlugin(); + + private RangerSsmPlugin() { + super("ssm", "ssm"); + init(); + } + + public static RangerSsmPlugin getInstance() { + return instance; + } + + @Override + public void init() { + super.init(); + RangerDefaultAuditHandler auditHandler = new RangerDefaultAuditHandler(); + super.setResultProcessor(auditHandler); + } +} \ No newline at end of file diff --git a/smart-web-server/pom.xml b/smart-web-server/pom.xml index bae3bbed1d..f16063fec7 100644 --- a/smart-web-server/pom.xml +++ b/smart-web-server/pom.xml @@ -57,6 +57,12 @@ 2.0.0-SNAPSHOT + + org.smartdata + smart-ranger-plugin + 2.0.0-SNAPSHOT + + org.springframework.boot spring-boot-starter-web @@ -135,6 +141,12 @@ junit test + + + org.mockito + mockito-core + test + diff --git a/smart-web-server/src/main/java/org/smartdata/server/SmartRestServer.java b/smart-web-server/src/main/java/org/smartdata/server/SmartRestServer.java index 4398d8b478..a70f041b6d 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/SmartRestServer.java +++ b/smart-web-server/src/main/java/org/smartdata/server/SmartRestServer.java @@ -20,6 +20,7 @@ import org.smartdata.conf.SmartConf; import org.smartdata.server.config.SsmContextInitializer; import org.springframework.boot.SpringApplication; +import org.springframework.boot.actuate.autoconfigure.solr.SolrHealthContributorAutoConfiguration; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration; import org.springframework.context.ConfigurableApplicationContext; @@ -69,7 +70,8 @@ private void injectToSpringProperties(SmartConf ssmConfig) { @SpringBootApplication(exclude = { // it's needed to prevent auto-registration of spring hazelcast node // in the SSM hazelcast workers cluster - HazelcastAutoConfiguration.class + HazelcastAutoConfiguration.class, + SolrHealthContributorAutoConfiguration.class }) public static class RestServerApplication { // empty class just to enable auto configs diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/AuthorizationConfiguration.java b/smart-web-server/src/main/java/org/smartdata/server/config/AuthorizationConfiguration.java new file mode 100644 index 0000000000..0f09b4f5ac --- /dev/null +++ b/smart-web-server/src/main/java/org/smartdata/server/config/AuthorizationConfiguration.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.server.config; + +import org.smartdata.ranger.authorizer.impl.RangerSsmAuthorizerImpl; +import org.smartdata.server.security.NoneAuthorizationManager; +import org.smartdata.server.security.RangerAuthorizationManager; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; + +import static org.smartdata.server.config.ConfigKeys.RANGER_AUTHORIZATION_ENABLED; +import static org.smartdata.server.config.ConfigKeys.WEB_SECURITY_ENABLED; + +@Configuration +public class AuthorizationConfiguration { + + @ConditionalOnProperty( + name = {WEB_SECURITY_ENABLED, RANGER_AUTHORIZATION_ENABLED}, + havingValue = "true") + @Bean + public AuthorizationManager rangerAuthorizationManager() { + return new RangerAuthorizationManager(new RangerSsmAuthorizerImpl()); + } + + @ConditionalOnMissingBean(AuthorizationManager.class) + @Bean + public AuthorizationManager noneAuthorizationManager() { + return new NoneAuthorizationManager(); + } +} diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java b/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java index 90c6e8c840..9779654e35 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java +++ b/smart-web-server/src/main/java/org/smartdata/server/config/ConfigKeys.java @@ -33,6 +33,8 @@ public class ConfigKeys { public static final String SMART_REST_SERVER_KEYTAB_FILE_KEY = "smart.rest.server.auth.spnego.keytab"; + public static final String RANGER_AUTHORIZATION_ENABLED = "smart.rest.server.auth.ranger.enabled"; + public static final String SSL_ENABLED = "smart.rest.server.ssl.enabled"; public static final boolean SSL_ENABLED_DEFAULT = false; diff --git a/smart-web-server/src/main/java/org/smartdata/server/config/SecurityConfiguration.java b/smart-web-server/src/main/java/org/smartdata/server/config/SecurityConfiguration.java index a355bfb9a1..2e460554b0 100644 --- a/smart-web-server/src/main/java/org/smartdata/server/config/SecurityConfiguration.java +++ b/smart-web-server/src/main/java/org/smartdata/server/config/SecurityConfiguration.java @@ -25,8 +25,10 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @@ -53,11 +55,11 @@ public AuthenticationManager authenticationManager( public SecurityFilterChain securityFilterChain( HttpSecurity http, SmartPrincipalManager principalManager, - List authHttpConfigurers) throws Exception { + List authHttpConfigurers, + AuthorizationManager authorizationManager) throws Exception { baseHttpSecurity(http) - .authorizeRequests() - .antMatchers(API_ENDPOINTS_PATTERN).authenticated() - .and() + .authorizeHttpRequests( + authorize -> authorize.antMatchers(API_ENDPOINTS_PATTERN).access(authorizationManager)) .anonymous().disable() .addFilterAfter( new SmartPrincipalInitializerFilter(principalManager), diff --git a/smart-web-server/src/main/java/org/smartdata/server/security/NoneAuthorizationManager.java b/smart-web-server/src/main/java/org/smartdata/server/security/NoneAuthorizationManager.java new file mode 100644 index 0000000000..1f0d41851a --- /dev/null +++ b/smart-web-server/src/main/java/org/smartdata/server/security/NoneAuthorizationManager.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.server.security; + +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; + +import java.util.function.Supplier; + +public class NoneAuthorizationManager implements AuthorizationManager { + + @Override + public AuthorizationDecision check(Supplier authentication, + RequestAuthorizationContext object) { + return new AuthorizationDecision(true); + } +} diff --git a/smart-web-server/src/main/java/org/smartdata/server/security/RangerAuthorizationManager.java b/smart-web-server/src/main/java/org/smartdata/server/security/RangerAuthorizationManager.java new file mode 100644 index 0000000000..6284b38ec6 --- /dev/null +++ b/smart-web-server/src/main/java/org/smartdata/server/security/RangerAuthorizationManager.java @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.server.security; + +import com.google.common.collect.ImmutableMap; +import lombok.RequiredArgsConstructor; +import org.smartdata.ranger.SsmResourceAccessType; +import org.smartdata.ranger.authorizer.RangerSsmAuthorizer; +import org.smartdata.ranger.authorizer.request.RangerAuthorizeRequest; +import org.smartdata.ranger.authorizer.request.RangerOperationDto; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; + +import javax.ws.rs.HttpMethod; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Pattern; + +import static org.smartdata.ranger.SsmRangerResource.ACTION; +import static org.smartdata.ranger.SsmRangerResource.AUDIT; +import static org.smartdata.ranger.SsmRangerResource.CLUSTER; +import static org.smartdata.ranger.SsmRangerResource.RULE; + +@RequiredArgsConstructor +public class RangerAuthorizationManager + implements AuthorizationManager { + + private static final String ALL_VALUES_EXPRESSION = "*"; + private static final + Map>> + OPERATION_MAP = + new HashMap>>() {{ + //cluster + put(Pattern.compile("^/api/v2/cluster/nodes$"), ImmutableMap.of(HttpMethod.GET, + request -> CLUSTER.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION))); + //rule + put(Pattern.compile("^/api/v2/rules$"), ImmutableMap.of( + HttpMethod.POST, + request -> RULE.getRangerOperationDto(SsmResourceAccessType.CREATE, + ALL_VALUES_EXPRESSION), + HttpMethod.GET, + request -> RULE.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION))); + put(Pattern.compile("^/api/v2/rules/\\d+$"), ImmutableMap.of( + HttpMethod.GET, + request -> RULE.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION), + HttpMethod.DELETE, + request -> RULE.getRangerOperationDto(SsmResourceAccessType.DELETE, + ALL_VALUES_EXPRESSION))); + //action + put(Pattern.compile("^/api/v2/actions$"), ImmutableMap.of( + HttpMethod.POST, + request -> ACTION.getRangerOperationDto(SsmResourceAccessType.SUBMIT, + ALL_VALUES_EXPRESSION), + HttpMethod.GET, + request -> ACTION.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION))); + put(Pattern.compile("^/api/v2/actions/\\d+$"), ImmutableMap.of( + HttpMethod.GET, + request -> ACTION.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION) + )); + //audit + put(Pattern.compile("^/api/v2/audit/events$"), ImmutableMap.of(HttpMethod.GET, + request -> AUDIT.getRangerOperationDto(SsmResourceAccessType.VIEW, + ALL_VALUES_EXPRESSION))); + }}; + + private final RangerSsmAuthorizer rangerSsmAuthorizer; + + @Override + public AuthorizationDecision check(Supplier authentication, + RequestAuthorizationContext request) { + return new AuthorizationDecision( + OPERATION_MAP.entrySet().stream() + .filter(entry -> entry.getKey().matcher(request.getRequest().getServletPath()).find()) + .map(entry -> Optional.ofNullable(entry.getValue().get( + request.getRequest().getMethod())) + .map(func -> rangerSsmAuthorizer.authorize( + new RangerAuthorizeRequest(authentication.get().getName(), + func.apply(request))))) + .filter(Optional::isPresent) + .map(Optional::get) + .findAny() + .orElse(true)); + } +} diff --git a/smart-web-server/src/test/java/org/smartdata/server/security/RangerAuthorizationManagerTest.java b/smart-web-server/src/test/java/org/smartdata/server/security/RangerAuthorizationManagerTest.java new file mode 100644 index 0000000000..cbc4b65ac0 --- /dev/null +++ b/smart-web-server/src/test/java/org/smartdata/server/security/RangerAuthorizationManagerTest.java @@ -0,0 +1,169 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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.smartdata.server.security; + +import com.google.common.collect.ImmutableMap; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.smartdata.ranger.SsmResourceAccessType; +import org.smartdata.ranger.authorizer.RangerSsmAuthorizer; +import org.smartdata.ranger.authorizer.request.RangerAuthorizeRequest; +import org.smartdata.ranger.authorizer.request.RangerOperationDto; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.AuthorizationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.access.intercept.RequestAuthorizationContext; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.HttpMethod; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RangerAuthorizationManagerTest { + private final RangerSsmAuthorizer rangerSsmAuthorizer = mock(RangerSsmAuthorizer.class); + private AuthorizationManager authorizationManager; + + @Before + public void setUp() { + authorizationManager = new RangerAuthorizationManager(rangerSsmAuthorizer); + } + + @Test + public void testOperationEntriesFound() { + String user = "user"; + ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass( + RangerAuthorizeRequest.class); + when(rangerSsmAuthorizer.authorize(requestArgumentCaptor.capture())).thenReturn(true); + HttpServletRequest httpRequest1 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest2 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest3 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest4 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest5 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest6 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest7 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest8 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest9 = mock(HttpServletRequest.class); + when(httpRequest1.getServletPath()).thenReturn("/api/v2/actions/11"); + when(httpRequest1.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest2.getServletPath()).thenReturn("/api/v2/cluster/nodes"); + when(httpRequest2.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest3.getServletPath()).thenReturn("/api/v2/rules"); + when(httpRequest3.getMethod()).thenReturn(HttpMethod.POST); + + when(httpRequest4.getServletPath()).thenReturn("/api/v2/rules/9"); + when(httpRequest4.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest5.getServletPath()).thenReturn("/api/v2/rules/2"); + when(httpRequest5.getMethod()).thenReturn(HttpMethod.DELETE); + + when(httpRequest6.getServletPath()).thenReturn("/api/v2/actions"); + when(httpRequest6.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest7.getServletPath()).thenReturn("/api/v2/actions/33"); + when(httpRequest7.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest8.getServletPath()).thenReturn("/api/v2/audit/events"); + when(httpRequest8.getMethod()).thenReturn(HttpMethod.GET); + + when(httpRequest9.getServletPath()).thenReturn("/api/v2/actions"); + when(httpRequest9.getMethod()).thenReturn(HttpMethod.POST); + + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(user); + Map requests = + new HashMap() {{ + put(new RequestAuthorizationContext(httpRequest1), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("action", "*"))); + put(new RequestAuthorizationContext(httpRequest2), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("cluster", "*"))); + put(new RequestAuthorizationContext(httpRequest3), + new RangerOperationDto(SsmResourceAccessType.CREATE.name(), + ImmutableMap.of("rule", "*"))); + put(new RequestAuthorizationContext(httpRequest4), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("rule", "*"))); + put(new RequestAuthorizationContext(httpRequest5), + new RangerOperationDto(SsmResourceAccessType.DELETE.name(), + ImmutableMap.of("rule", "*"))); + put(new RequestAuthorizationContext(httpRequest6), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("action", "*"))); + put(new RequestAuthorizationContext(httpRequest7), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("action", "*"))); + put(new RequestAuthorizationContext(httpRequest8), + new RangerOperationDto(SsmResourceAccessType.VIEW.name(), + ImmutableMap.of("audit", "*"))); + put(new RequestAuthorizationContext(httpRequest9), + new RangerOperationDto(SsmResourceAccessType.SUBMIT.name(), + ImmutableMap.of("action", "*"))); + }}; + + requests.forEach((req, value) -> { + AuthorizationDecision result = authorizationManager.check(() -> authentication, req); + assertTrue(result.isGranted()); + RangerAuthorizeRequest rangerRequest = requestArgumentCaptor.getValue(); + assertEquals(user, rangerRequest.getUser()); + assertEquals( + value, + rangerRequest.getOperationDto()); + }); + } + + @Test + public void testOperationNotFound() { + String user = "user"; + HttpServletRequest httpRequest1 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest2 = mock(HttpServletRequest.class); + HttpServletRequest httpRequest3 = mock(HttpServletRequest.class); + when(httpRequest1.getServletPath()).thenReturn("/api/v2/actions/11/test"); + when(httpRequest1.getMethod()).thenReturn(HttpMethod.GET); + when(httpRequest2.getServletPath()).thenReturn("/api/v2/rules/22/test"); + when(httpRequest2.getMethod()).thenReturn(HttpMethod.GET); + when(httpRequest3.getServletPath()).thenReturn("/api/v2/actions/test"); + when(httpRequest3.getMethod()).thenReturn(HttpMethod.GET); + + when(rangerSsmAuthorizer.authorize(any())).thenReturn(false); + List requests = + Arrays.asList(new RequestAuthorizationContext(httpRequest1), + new RequestAuthorizationContext(httpRequest2), + new RequestAuthorizationContext(httpRequest3) + ); + Authentication authentication = mock(Authentication.class); + when(authentication.getName()).thenReturn(user); + + requests.forEach(req -> { + AuthorizationDecision result = authorizationManager.check(() -> authentication, req); + assertTrue(result.isGranted()); + }); + } +}