Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id C78FB200C41 for ; Fri, 24 Mar 2017 22:07:21 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id C60EE160B93; Fri, 24 Mar 2017 21:07:21 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 2C4FE160B9A for ; Fri, 24 Mar 2017 22:07:19 +0100 (CET) Received: (qmail 27533 invoked by uid 500); 24 Mar 2017 21:07:18 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 27404 invoked by uid 99); 24 Mar 2017 21:07:17 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 24 Mar 2017 21:07:17 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 10D39E00EE; Fri, 24 Mar 2017 21:07:17 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: oleewere@apache.org To: commits@ambari.apache.org Date: Fri, 24 Mar 2017 21:07:20 -0000 Message-Id: <4e097d3f3cbb44b7931aaf523c6b7d18@git.apache.org> In-Reply-To: <45f552fc459d46919d5251ba3be1c79e@git.apache.org> References: <45f552fc459d46919d5251ba3be1c79e@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [5/7] ambari git commit: AMBARI-20566. Create ambari-infra module in Ambari (move solr modules from ambari-logsearch) (oleewere) archived-at: Fri, 24 Mar 2017 21:07:21 -0000 http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetSolrHostsCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetSolrHostsCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetSolrHostsCommand.java new file mode 100644 index 0000000..5a14a44 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetSolrHostsCommand.java @@ -0,0 +1,53 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.zookeeper.ZooKeeper; + +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +public class GetSolrHostsCommand extends AbstractRetryCommand> { + + public GetSolrHostsCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + public Collection createAndProcessRequest(AmbariSolrCloudClient solrCloudClient) throws Exception { + List solrHosts = new ArrayList<>(); + + ZooKeeper zk = new ZooKeeper(solrCloudClient.getZkConnectString(), 10000, null); + List ids = zk.getChildren("/live_nodes", false); + for (String id : ids) { + if (id.endsWith("_solr")) { + String hostAndPort = id.substring(0, id.length() - 5); + String[] tokens = hostAndPort.split(":"); + String host = InetAddress.getByName(tokens[0]).getHostName(); + + solrHosts.add(host); + } + } + + return solrHosts; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetStateFileZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetStateFileZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetStateFileZkCommand.java new file mode 100644 index 0000000..10a8daa --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/GetStateFileZkCommand.java @@ -0,0 +1,43 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.ambari.infra.solr.domain.AmbariSolrState; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.SolrZooKeeper; + +public class GetStateFileZkCommand extends AbstractStateFileZkCommand { + private String unsecureZnode; + + public GetStateFileZkCommand(int maxRetries, int interval, String unsecureZnode) { + super(maxRetries, interval); + this.unsecureZnode = unsecureZnode; + } + + @Override + protected AmbariSolrState executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception { + AmbariSolrState result = AmbariSolrState.UNSECURE; + String stateFile = String.format("%s/%s", unsecureZnode, AbstractStateFileZkCommand.STATE_FILE); + if (zkClient.exists(stateFile, true)) { + result = getStateFromJson(client, stateFile); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/ListCollectionCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/ListCollectionCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/ListCollectionCommand.java new file mode 100644 index 0000000..41094c7 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/ListCollectionCommand.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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.response.CollectionAdminResponse; + +import java.util.List; + +public class ListCollectionCommand extends AbstractSolrRetryCommand> { + + public ListCollectionCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + public List handleResponse(CollectionAdminResponse response, AmbariSolrCloudClient client) throws Exception { + List allCollectionList = (List) response + .getResponse().get("collections"); + return allCollectionList; + } + + @Override + public CollectionAdminRequest.List createRequest(AmbariSolrCloudClient client) { + return new CollectionAdminRequest.List(); + } + + @Override + public String errorMessage(AmbariSolrCloudClient client) { + return "Cannot get collections."; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureSolrZNodeZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureSolrZNodeZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureSolrZNodeZkCommand.java new file mode 100644 index 0000000..6958623 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureSolrZNodeZkCommand.java @@ -0,0 +1,86 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.ambari.infra.solr.util.AclUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.SolrZooKeeper; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SecureSolrZNodeZkCommand extends AbstractZookeeperRetryCommand { + + private static final Logger LOG = LoggerFactory.getLogger(SecureSolrZNodeZkCommand.class); + + public SecureSolrZNodeZkCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + protected Boolean executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception { + String zNode = client.getZnode(); + List newAclList = new ArrayList<>(); + List saslUserList = AclUtils.createAclListFromSaslUsers(client.getSaslUsers().split(",")); + newAclList.addAll(saslUserList); + newAclList.add(new ACL(ZooDefs.Perms.READ, new Id("world", "anyone"))); + + String configsPath = String.format("%s/%s", zNode, "configs"); + String collectionsPath = String.format("%s/%s", zNode, "collections"); + String aliasesPath = String.format("%s/%s", zNode, "aliases.json"); // TODO: protect this later somehow + List excludePaths = Arrays.asList(configsPath, collectionsPath, aliasesPath); + + createZnodeIfNeeded(configsPath, client.getSolrZkClient()); + createZnodeIfNeeded(collectionsPath, client.getSolrZkClient()); + + AclUtils.setRecursivelyOn(client.getSolrZkClient().getSolrZooKeeper(), zNode, newAclList, excludePaths); + + List commonConfigAcls = new ArrayList<>(); + commonConfigAcls.addAll(saslUserList); + commonConfigAcls.add(new ACL(ZooDefs.Perms.READ | ZooDefs.Perms.CREATE, new Id("world", "anyone"))); + + LOG.info("Set sasl users for znode '{}' : {}", client.getZnode(), StringUtils.join(saslUserList, ",")); + LOG.info("Skip {}/configs and {}/collections", client.getZnode(), client.getZnode()); + solrZooKeeper.setACL(configsPath, AclUtils.mergeAcls(solrZooKeeper.getACL(configsPath, new Stat()), commonConfigAcls), -1); + solrZooKeeper.setACL(collectionsPath, AclUtils.mergeAcls(solrZooKeeper.getACL(collectionsPath, new Stat()), commonConfigAcls), -1); + + LOG.info("Set world:anyone to 'cr' on {}/configs and {}/collections", client.getZnode(), client.getZnode()); + AclUtils.setRecursivelyOn(solrZooKeeper, configsPath, saslUserList); + AclUtils.setRecursivelyOn(solrZooKeeper, collectionsPath, saslUserList); + + return true; + } + + private void createZnodeIfNeeded(String configsPath, SolrZkClient zkClient) throws KeeperException, InterruptedException { + if (!zkClient.exists(configsPath, true)) { + LOG.info("'{}' does not exist. Creating it ...", configsPath); + zkClient.makePath(configsPath, true); + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureZNodeZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureZNodeZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureZNodeZkCommand.java new file mode 100644 index 0000000..a96dc5d --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SecureZNodeZkCommand.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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.ambari.infra.solr.util.AclUtils; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.SolrZooKeeper; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; + +import java.util.ArrayList; +import java.util.List; + +public class SecureZNodeZkCommand extends AbstractZookeeperRetryCommand { + + public SecureZNodeZkCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + protected Boolean executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception { + String zNode = client.getZnode(); + List newAclList = new ArrayList<>(); + List saslUserList = AclUtils.createAclListFromSaslUsers(client.getSaslUsers().split(",")); + newAclList.addAll(saslUserList); + newAclList.add(new ACL(ZooDefs.Perms.READ, new Id("world", "anyone"))); + AclUtils.setRecursivelyOn(client.getSolrZkClient().getSolrZooKeeper(), zNode, newAclList); + return true; + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetClusterPropertyZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetClusterPropertyZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetClusterPropertyZkCommand.java new file mode 100644 index 0000000..34597c6 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetClusterPropertyZkCommand.java @@ -0,0 +1,40 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.SolrZooKeeper; +import org.apache.solr.common.cloud.ZkStateReader; + +public class SetClusterPropertyZkCommand extends AbstractZookeeperRetryCommand{ + + public SetClusterPropertyZkCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + protected String executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception { + String propertyName = client.getPropName(); + String propertyValue = client.getPropValue(); + ZkStateReader reader = new ZkStateReader(zkClient); + reader.setClusterProperty(propertyName, propertyValue); + return propertyValue; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UpdateStateFileZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UpdateStateFileZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UpdateStateFileZkCommand.java new file mode 100644 index 0000000..2b360fb --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UpdateStateFileZkCommand.java @@ -0,0 +1,84 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.ambari.infra.solr.domain.AmbariSolrState; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.cloud.SolrZooKeeper; +import org.apache.zookeeper.CreateMode; +import org.codehaus.jackson.map.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class UpdateStateFileZkCommand extends AbstractStateFileZkCommand { + + private static final Logger LOG = LoggerFactory.getLogger(UpdateStateFileZkCommand.class); + + private String unsecureZnode; + + public UpdateStateFileZkCommand(int maxRetries, int interval, String unsecureZnode) { + super(maxRetries, interval); + this.unsecureZnode = unsecureZnode; + } + + @Override + protected AmbariSolrState executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception { + boolean secure = client.isSecure(); + String stateFile = String.format("%s/%s", unsecureZnode, AbstractStateFileZkCommand.STATE_FILE); + AmbariSolrState result = null; + if (secure) { + LOG.info("Update state file in secure mode."); + updateStateFile(client, zkClient, AmbariSolrState.SECURE, stateFile); + result = AmbariSolrState.SECURE; + } else { + LOG.info("Update state file in unsecure mode."); + updateStateFile(client, zkClient, AmbariSolrState.UNSECURE, stateFile); + result = AmbariSolrState.UNSECURE; + } + return result; + } + + private void updateStateFile(AmbariSolrCloudClient client, SolrZkClient zkClient, AmbariSolrState stateToUpdate, + String stateFile) throws Exception { + if (!zkClient.exists(stateFile, true)) { + LOG.info("State file does not exits. Initializing it as '{}'", stateToUpdate); + zkClient.create(stateFile, createStateJson(stateToUpdate).getBytes(StandardCharsets.UTF_8), + CreateMode.PERSISTENT, true); + } else { + AmbariSolrState stateOnSecure = getStateFromJson(client, stateFile); + if (stateToUpdate.equals(stateOnSecure)) { + LOG.info("State file is in '{}' mode. No update.", stateOnSecure); + } else { + LOG.info("State file is in '{}' mode. Updating it to '{}'", stateOnSecure, stateToUpdate); + zkClient.setData(stateFile, createStateJson(stateToUpdate).getBytes(StandardCharsets.UTF_8), true); + } + } + } + + private String createStateJson(AmbariSolrState state) throws Exception { + Map secStateMap = new HashMap<>(); + secStateMap.put(AbstractStateFileZkCommand.STATE_FIELD, state.toString()); + return new ObjectMapper().writeValueAsString(secStateMap); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UploadConfigZkCommand.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UploadConfigZkCommand.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UploadConfigZkCommand.java new file mode 100644 index 0000000..fc7482d --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/UploadConfigZkCommand.java @@ -0,0 +1,41 @@ +/* + * 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.apache.ambari.infra.solr.commands; + +import org.apache.ambari.infra.solr.AmbariSolrCloudClient; +import org.apache.solr.common.cloud.ZkConfigManager; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class UploadConfigZkCommand extends AbstractZookeeperConfigCommand { + + public UploadConfigZkCommand(int maxRetries, int interval) { + super(maxRetries, interval); + } + + @Override + protected String executeZkConfigCommand(ZkConfigManager zkConfigManager, AmbariSolrCloudClient client) throws Exception { + Path configDir = Paths.get(client.getConfigDir()); + String configSet = client.getConfigSet(); + zkConfigManager.uploadConfigDir(configDir, configSet); + return configSet; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/AmbariSolrState.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/AmbariSolrState.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/AmbariSolrState.java new file mode 100644 index 0000000..489d3f1 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/AmbariSolrState.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.apache.ambari.infra.solr.domain; + +/** + * Enum state values for storing security status in unsecure znode + */ +public enum AmbariSolrState { + SECURE, UNSECURE +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/AclUtils.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/AclUtils.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/AclUtils.java new file mode 100644 index 0000000..dd5d6c8 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/AclUtils.java @@ -0,0 +1,85 @@ +/* + * 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.apache.ambari.infra.solr.util; + +import org.apache.solr.common.cloud.SolrZooKeeper; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Id; +import org.apache.zookeeper.data.Stat; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AclUtils { + + public static List mergeAcls(List originalAcls, List updateAcls) { + Map aclMap = new HashMap<>(); + List acls = new ArrayList<>(); + if (originalAcls != null) { + for (ACL acl : originalAcls) { + aclMap.put(acl.getId().getId(), acl); + } + } + + if (updateAcls != null) { + for (ACL acl : updateAcls) { + aclMap.put(acl.getId().getId(), acl); + } + } + + for (Map.Entry aclEntry : aclMap.entrySet()) { + acls.add(aclEntry.getValue()); + } + return acls; + } + + public static List createAclListFromSaslUsers(String[] saslUsers) { + List saslUserList = new ArrayList<>(); + for (String saslUser : saslUsers) { + ACL acl = new ACL(); + acl.setId(new Id("sasl", saslUser)); + acl.setPerms(ZooDefs.Perms.ALL); + saslUserList.add(acl); + } + return saslUserList; + } + + public static void setRecursivelyOn(SolrZooKeeper solrZooKeeper, String node, List acls) throws KeeperException, InterruptedException { + setRecursivelyOn(solrZooKeeper, node, acls, new ArrayList()); + } + + public static void setRecursivelyOn(SolrZooKeeper solrZooKeeper, String node, List acls, List excludePaths) + throws KeeperException, InterruptedException { + if (!excludePaths.contains(node)) { + List newAcls = AclUtils.mergeAcls(solrZooKeeper.getACL(node, new Stat()), acls); + solrZooKeeper.setACL(node, newAcls, -1); + for (String child : solrZooKeeper.getChildren(node, null)) { + setRecursivelyOn(solrZooKeeper, path(node, child), acls, excludePaths); + } + } + } + + private static String path(String node, String child) { + return node.endsWith("/") ? node + child : node + "/" + child; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/ShardUtils.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/ShardUtils.java b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/ShardUtils.java new file mode 100644 index 0000000..f46565b --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/util/ShardUtils.java @@ -0,0 +1,71 @@ +/* + * 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.apache.ambari.infra.solr.util; + +import org.apache.solr.common.cloud.Replica; +import org.apache.solr.common.cloud.Slice; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +public class ShardUtils { + + private static final Logger LOG = LoggerFactory.getLogger(ShardUtils.class); + + public static String generateShardListStr(int maxShardsPerNode) { + String shardsListStr = ""; + for (int i = 0; i < maxShardsPerNode; i++) { + if (i != 0) { + shardsListStr += ","; + } + String shard = "shard" + i; + shardsListStr += shard; + } + return shardsListStr; + } + + public static List generateShardList(int maxShardsPerNode) { + List shardsList = new ArrayList<>(); + for (int i = 0; i < maxShardsPerNode; i++) { + shardsList.add("shard" + i); + } + return shardsList; + } + + public static Collection getShardNamesFromSlices(Collection slices, String collection) { + Collection result = new HashSet(); + Iterator iter = slices.iterator(); + while (iter.hasNext()) { + Slice slice = iter.next(); + for (Replica replica : slice.getReplicas()) { + LOG.info("collectionName=" + collection + ", slice.name=" + + slice.getName() + ", slice.state=" + slice.getState() + + ", replica.core=" + replica.getStr("core") + + ", replica.state=" + replica.getStr("state")); + result.add(slice.getName()); + } + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/resources/log4j.properties b/ambari-infra/ambari-infra-solr-client/src/main/resources/log4j.properties new file mode 100644 index 0000000..e8dca12 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/resources/log4j.properties @@ -0,0 +1,31 @@ +# Copyright 2011 The Apache Software Foundation +# +# 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. + +log4j.rootLogger=INFO,stdout,stderr + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Threshold=INFO +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%m%n + +log4j.appender.stderr=org.apache.log4j.ConsoleAppender +log4j.appender.stderr.Threshold=ERROR +log4j.appender.stderr.Target=System.err +log4j.appender.stderr.layout=org.apache.log4j.PatternLayout +log4j.appender.stderr.layout.ConversionPattern=%m%n \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/main/resources/solrCloudCli.sh ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/main/resources/solrCloudCli.sh b/ambari-infra/ambari-infra-solr-client/src/main/resources/solrCloudCli.sh new file mode 100644 index 0000000..cd47f06 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/main/resources/solrCloudCli.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# 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. + +JVM="java" +sdir="`dirname \"$0\"`" + +PATH=$JAVA_HOME/bin:$PATH $JVM -classpath "$sdir:$sdir/libs/*" org.apache.ambari.logsearch.solr.AmbariSolrCloudCLI ${1+"$@"} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-client/src/test/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientTest.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-client/src/test/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientTest.java b/ambari-infra/ambari-infra-solr-client/src/test/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientTest.java new file mode 100644 index 0000000..44f3ec5 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-client/src/test/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientTest.java @@ -0,0 +1,134 @@ +/* + * 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.apache.ambari.infra.solr; + +import static org.easymock.EasyMock.anyString; +import static org.easymock.EasyMock.anyObject; +import static org.easymock.EasyMock.createMock; +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.verify; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.apache.solr.client.solrj.impl.CloudSolrClient; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.response.CollectionAdminResponse; +import org.apache.solr.common.cloud.SolrZkClient; +import org.apache.solr.common.util.NamedList; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +public class AmbariSolrCloudClientTest { + + private AmbariSolrCloudClient underTest; + + private CloudSolrClient mockedSolrClient; + + private SolrZkClient mockedSolrZkClient; + + private CollectionAdminResponse mockedResponse; + + @Before + public void setUp() { + AmbariSolrCloudClientBuilder builder = new AmbariSolrCloudClientBuilder(); + + mockedSolrClient = createMock(CloudSolrClient.class); + mockedSolrZkClient = createMock(SolrZkClient.class); + mockedResponse = createMock(CollectionAdminResponse.class); + + builder.solrCloudClient = mockedSolrClient; + builder.solrZkClient = mockedSolrZkClient; + + underTest = builder + .withZkConnectString("localhost1:2181,localhost2:2182") + .withCollection("collection1") + .withConfigSet("configSet") + .withShards(1) + .withReplication(1) + .withMaxShardsPerNode(2) + .withInterval(1) + .withRetry(2) + .withRouterName("routerName") + .withRouterField("routerField") + .build(); + } + + @Test + public void testCreateCollectionWhenCollectionDoesNotExist() throws Exception { + // GIVEN + NamedList namedList = new NamedList<>(); + namedList.add("collections", Arrays.asList("collection1", "collection2")); + + expect(mockedSolrClient.request(anyObject(CollectionAdminRequest.class), anyString())).andReturn(namedList).times(1); + replay(mockedSolrClient); + + // WHEN + String result = underTest.createCollection(); + // THEN + assertEquals("collection1", result); + verify(mockedSolrClient); + } + + @Test + public void testCreateCollectionWhenCollectionExists() throws Exception { + // GIVEN + NamedList namedList = new NamedList<>(); + namedList.add("collections", Arrays.asList("collection2", "collection3")); + + expect(mockedSolrClient.request(anyObject(CollectionAdminRequest.class), anyString())).andReturn(namedList).times(2); + replay(mockedSolrClient); + + // WHEN + String result = underTest.createCollection(); + // THEN + assertEquals("collection1", result); + verify(mockedSolrClient); + } + + @Test + public void testListCollections() throws Exception { + // GIVEN + NamedList namedList = new NamedList<>(); + namedList.add("collections", Arrays.asList("collection1", "collection2")); + + expect(mockedSolrClient.request(anyObject(CollectionAdminRequest.class), anyString())).andReturn(namedList); + + replay(mockedSolrClient); + // WHEN + List result = underTest.listCollections(); + + // THEN + assertTrue(result.contains("collection1")); + assertTrue(result.contains("collection2")); + assertEquals(2, result.size()); + } + + @Test(expected = AmbariSolrCloudClientException.class) + public void testRetries() throws Exception { + // GIVEN + expect(mockedSolrClient.request(anyObject(CollectionAdminRequest.class), anyString())).andThrow(new RuntimeException("ex")).times(2); + replay(mockedSolrClient); + // WHEN + underTest.listCollections(); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/pom.xml b/ambari-infra/ambari-infra-solr-plugin/pom.xml new file mode 100644 index 0000000..dbd4bff --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/pom.xml @@ -0,0 +1,56 @@ + + + + + ambari-logsearch + org.apache.ambari + 2.0.0.0-SNAPSHOT + + Ambari Infra Solr Plugin + http://maven.apache.org + 4.0.0 + ambari-infra-solr-plugin + + + org.apache.solr + solr-core + ${solr.version} + + + org.apache.solr + solr-test-framework + ${solr.version} + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.7 + 1.7 + + + + + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraKerberosHostValidator.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraKerberosHostValidator.java b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraKerberosHostValidator.java new file mode 100644 index 0000000..4a47a89 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraKerberosHostValidator.java @@ -0,0 +1,54 @@ +/* + * 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.apache.ambari.infra.security; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.hadoop.security.authentication.server.AuthenticationToken; +import org.apache.hadoop.security.authentication.util.KerberosName; + +import java.security.Principal; +import java.util.Map; +import java.util.Set; + +/** + * Validate that the user has the right access based on the hostname in the kerberos principal + */ +public class InfraKerberosHostValidator { + + public boolean validate(Principal principal, Map> userVsHosts, Map userVsHostRegex) { + if (principal instanceof AuthenticationToken) { + AuthenticationToken authenticationToken = (AuthenticationToken) principal; + KerberosName kerberosName = new KerberosName(authenticationToken.getName()); + String hostname = kerberosName.getHostName(); + String serviceUserName = kerberosName.getServiceName(); + if (MapUtils.isNotEmpty(userVsHostRegex)) { + String regex = userVsHostRegex.get(serviceUserName); + return hostname.matches(regex); + } + if (MapUtils.isNotEmpty(userVsHosts)) { + Set hosts = userVsHosts.get(serviceUserName); + if (CollectionUtils.isNotEmpty(hosts)) { + return hosts.contains(hostname); + } + } + } + return true; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraRuleBasedAuthorizationPlugin.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraRuleBasedAuthorizationPlugin.java b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraRuleBasedAuthorizationPlugin.java new file mode 100644 index 0000000..2f1a558 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraRuleBasedAuthorizationPlugin.java @@ -0,0 +1,542 @@ +/* + * 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.apache.ambari.infra.security; + +import com.google.common.collect.ImmutableSet; +import java.io.IOException; +import java.lang.invoke.MethodHandles; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.CollectionParams; +import org.apache.solr.common.util.Utils; +import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationPlugin; +import org.apache.solr.security.AuthorizationResponse; +import org.apache.solr.security.ConfigEditablePlugin; +import org.apache.solr.util.CommandOperation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static java.util.Collections.singleton; +import static org.apache.solr.common.params.CommonParams.NAME; +import static org.apache.solr.common.util.Utils.getDeepCopy; +import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue; +import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue; + +/** + * Modified copy of solr.RuleBasedAuthorizationPlugin to handle role - permission mappings with KereberosPlugin + * Added 2 new JSON map: (precedence: user-host-regex > user-host) + * 1. "user-host": user host mappings (array) for hostname validation + * 2. "user-host-regex": user host regex mapping (string) for hostname validation + */ +public class InfraRuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin { + + private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private final Map> usersVsRoles = new HashMap<>(); + private final Map mapping = new HashMap<>(); + private final List permissions = new ArrayList<>(); + private final Map> userVsHosts = new HashMap<>(); + private final Map userVsHostRegex = new HashMap<>(); + + private final InfraUserRolesLookupStrategy infraUserRolesLookupStrategy = new InfraUserRolesLookupStrategy(); + private final InfraKerberosHostValidator infraKerberosDomainValidator = new InfraKerberosHostValidator(); + + private static class WildCardSupportMap extends HashMap> { + final Set wildcardPrefixes = new HashSet<>(); + + @Override + public List put(String key, List value) { + if (key != null && key.endsWith("/*")) { + key = key.substring(0, key.length() - 2); + wildcardPrefixes.add(key); + } + return super.put(key, value); + } + + @Override + public List get(Object key) { + List result = super.get(key); + if (key == null || result != null) return result; + if (!wildcardPrefixes.isEmpty()) { + for (String s : wildcardPrefixes) { + if (key.toString().startsWith(s)) { + List l = super.get(s); + if (l != null) { + result = result == null ? new ArrayList() : new ArrayList(result); + result.addAll(l); + } + } + } + } + return result; + } + } + + @Override + public AuthorizationResponse authorize(AuthorizationContext context) { + List collectionRequests = context.getCollectionRequests(); + if (context.getRequestType() == AuthorizationContext.RequestType.ADMIN) { + MatchStatus flag = checkCollPerm(mapping.get(null), context); + return flag.rsp; + } + + for (AuthorizationContext.CollectionRequest collreq : collectionRequests) { + //check permissions for each collection + MatchStatus flag = checkCollPerm(mapping.get(collreq.collectionName), context); + if (flag != MatchStatus.NO_PERMISSIONS_FOUND) return flag.rsp; + } + //check wildcard (all=*) permissions. + MatchStatus flag = checkCollPerm(mapping.get("*"), context); + return flag.rsp; + } + + private MatchStatus checkCollPerm(Map> pathVsPerms, + AuthorizationContext context) { + if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND; + + String path = context.getResource(); + MatchStatus flag = checkPathPerm(pathVsPerms.get(path), context); + if (flag != MatchStatus.NO_PERMISSIONS_FOUND) return flag; + return checkPathPerm(pathVsPerms.get(null), context); + } + + private MatchStatus checkPathPerm(List permissions, AuthorizationContext context) { + if (permissions == null || permissions.isEmpty()) return MatchStatus.NO_PERMISSIONS_FOUND; + Principal principal = context.getUserPrincipal(); + loopPermissions: + for (int i = 0; i < permissions.size(); i++) { + Permission permission = permissions.get(i); + if (permission.method != null && !permission.method.contains(context.getHttpMethod())) { + //this permissions HTTP method does not match this rule. try other rules + continue; + } + if(permission.predicate != null){ + if(!permission.predicate.test(context)) continue ; + } + + if (permission.params != null) { + for (Map.Entry e : permission.params.entrySet()) { + String paramVal = context.getParams().get(e.getKey()); + Object val = e.getValue(); + if (val instanceof List) { + if (!((List) val).contains(paramVal)) continue loopPermissions; + } else if (!Objects.equals(val, paramVal)) continue loopPermissions; + } + } + + if (permission.role == null) { + //no role is assigned permission.That means everybody is allowed to access + return MatchStatus.PERMITTED; + } + if (principal == null) { + log.info("request has come without principal. failed permission {} ",permission); + //this resource needs a principal but the request has come without + //any credential. + return MatchStatus.USER_REQUIRED; + } else if (permission.role.contains("*")) { + return MatchStatus.PERMITTED; + } + + for (String role : permission.role) { + Set userRoles = infraUserRolesLookupStrategy.getUserRolesFromPrincipal(usersVsRoles, principal); + boolean validHostname = infraKerberosDomainValidator.validate(principal, userVsHosts, userVsHostRegex); + if (!validHostname) { + log.warn("Hostname is not valid for principal {}", principal); + return MatchStatus.FORBIDDEN; + } + if (userRoles != null && userRoles.contains(role)) return MatchStatus.PERMITTED; + } + log.info("This resource is configured to have a permission {}, The principal {} does not have the right role ", permission, principal); + return MatchStatus.FORBIDDEN; + } + log.debug("No permissions configured for the resource {} . So allowed to access", context.getResource()); + return MatchStatus.NO_PERMISSIONS_FOUND; + } + + @Override + public void init(Map initInfo) { + mapping.put(null, new WildCardSupportMap()); + Map map = getMapValue(initInfo, "user-role"); + for (Object o : map.entrySet()) { + Map.Entry e = (Map.Entry) o; + String roleName = (String) e.getKey(); + usersVsRoles.put(roleName, readValueAsSet(map, roleName)); + } + List perms = getListValue(initInfo, "permissions"); + for (Map o : perms) { + Permission p; + try { + p = Permission.load(o); + } catch (Exception exp) { + log.error("Invalid permission ", exp); + continue; + } + permissions.add(p); + add2Mapping(p); + } + // adding user-host + Map userHostsMap = getMapValue(initInfo, "user-host"); + for (Object userHost : userHostsMap.entrySet()) { + Map.Entry e = (Map.Entry) userHost; + String roleName = (String) e.getKey(); + userVsHosts.put(roleName, readValueAsSet(userHostsMap, roleName)); + } + // adding user-host-regex + Map userHostRegexMap = getMapValue(initInfo, "user-host-regex"); + for (Map.Entry entry : userHostRegexMap.entrySet()) { + userVsHostRegex.put(entry.getKey(), entry.getValue().toString()); + } + + } + + //this is to do optimized lookup of permissions for a given collection/path + private void add2Mapping(Permission permission) { + for (String c : permission.collections) { + WildCardSupportMap m = mapping.get(c); + if (m == null) mapping.put(c, m = new WildCardSupportMap()); + for (String path : permission.path) { + List perms = m.get(path); + if (perms == null) m.put(path, perms = new ArrayList<>()); + perms.add(permission); + } + } + } + + /** + * read a key value as a set. if the value is a single string , + * return a singleton set + * + * @param m the map from which to lookup + * @param key the key with which to do lookup + */ + static Set readValueAsSet(Map m, String key) { + Set result = new HashSet<>(); + Object val = m.get(key); + if (val == null) { + if("collection".equals(key)){ + //for collection collection: null means a core admin/ collection admin request + // otherwise it means a request where collection name is ignored + return m.containsKey(key) ? singleton((String) null) : singleton("*"); + } + return null; + } + if (val instanceof Collection) { + Collection list = (Collection) val; + for (Object o : list) result.add(String.valueOf(o)); + } else if (val instanceof String) { + result.add((String) val); + } else { + throw new RuntimeException("Bad value for : " + key); + } + return result.isEmpty() ? null : Collections.unmodifiableSet(result); + } + + @Override + public void close() throws IOException { } + + static class Permission { + String name; + Set path, role, collections, method; + Map params; + Predicate predicate; + Map originalConfig; + + private Permission() { + } + + static Permission load(Map m) { + Permission p = new Permission(); + p.originalConfig = new LinkedHashMap<>(m); + String name = (String) m.get(NAME); + if (!m.containsKey("role")) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "role not specified"); + p.role = readValueAsSet(m, "role"); + if (well_known_permissions.containsKey(name)) { + HashSet disAllowed = new HashSet<>(knownKeys); + disAllowed.remove("role");//these are the only + disAllowed.remove(NAME);//allowed keys for well-known permissions + for (String s : disAllowed) { + if (m.containsKey(s)) + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, s + " is not a valid key for the permission : " + name); + } + p.predicate = (Predicate) ((Map) well_known_permissions.get(name)).get(Predicate.class.getName()); + m = well_known_permissions.get(name); + } + p.name = name; + p.path = readSetSmart(name, m, "path"); + p.collections = readSetSmart(name, m, "collection"); + p.method = readSetSmart(name, m, "method"); + p.params = (Map) m.get("params"); + return p; + } + + @Override + public String toString() { + return Utils.toJSONString(originalConfig); + } + + static final Set knownKeys = ImmutableSet.of("collection", "role", "params", "path", "method", NAME); + } + + enum MatchStatus { + USER_REQUIRED(AuthorizationResponse.PROMPT), + NO_PERMISSIONS_FOUND(AuthorizationResponse.OK), + PERMITTED(AuthorizationResponse.OK), + FORBIDDEN(AuthorizationResponse.FORBIDDEN); + + final AuthorizationResponse rsp; + + MatchStatus(AuthorizationResponse rsp) { + this.rsp = rsp; + } + } + + /** + * This checks for the defaults available other rules for the keys + */ + private static Set readSetSmart(String permissionName, Map m, String key) { + Set set = readValueAsSet(m, key); + if (set == null && well_known_permissions.containsKey(permissionName)) { + set = readValueAsSet((Map) well_known_permissions.get(permissionName), key); + } + if ("method".equals(key)) { + if (set != null) { + for (String s : set) if (!HTTP_METHODS.contains(s)) return null; + } + return set; + } + return set == null ? singleton((String)null) : set; + } + + @Override + public Map edit(Map latestConf, List commands) { + for (CommandOperation op : commands) { + OPERATION operation = null; + for (OPERATION o : OPERATION.values()) { + if (o.name.equals(op.name)) { + operation = o; + break; + } + } + if (operation == null) { + op.unknownOperation(); + return null; + } + latestConf = operation.edit(latestConf, op); + if (latestConf == null) return null; + + } + return latestConf; + } + + enum OPERATION { + SET_USER_ROLE("set-user-role") { + @Override + public Map edit(Map latestConf, CommandOperation op) { + Map roleMap = getMapValue(latestConf, "user-role"); + Map map = op.getDataMap(); + if (op.hasError()) return null; + for (Map.Entry e : map.entrySet()) { + if (e.getValue() == null) { + roleMap.remove(e.getKey()); + continue; + } + if (e.getValue() instanceof String || e.getValue() instanceof List) { + roleMap.put(e.getKey(), e.getValue()); + } else { + op.addError("Unexpected value "); + return null; + } + } + return latestConf; + } + }, + SET_PERMISSION("set-permission") { + @Override + public Map edit(Map latestConf, CommandOperation op) { + String name = op.getStr(NAME); + Map dataMap = op.getDataMap(); + if (op.hasError()) return null; + dataMap = getDeepCopy(dataMap, 3); + String before = (String) dataMap.remove("before"); + for (String key : dataMap.keySet()) { + if (!Permission.knownKeys.contains(key)) op.addError("Unknown key, " + key); + } + try { + Permission.load(dataMap); + } catch (Exception e) { + op.addError(e.getMessage()); + return null; + } + List permissions = getListValue(latestConf, "permissions"); + List permissionsCopy = new ArrayList<>(); + boolean added = false; + for (Map e : permissions) { + Object n = e.get(NAME); + if (n.equals(before) || n.equals(name)) { + added = true; + permissionsCopy.add(dataMap); + } + if (!n.equals(name)) permissionsCopy.add(e); + } + if (!added && before != null) { + op.addError("Invalid 'before' :" + before); + return null; + } + if (!added) permissionsCopy.add(dataMap); + latestConf.put("permissions", permissionsCopy); + return latestConf; + } + }, + UPDATE_PERMISSION("update-permission") { + @Override + public Map edit(Map latestConf, CommandOperation op) { + String name = op.getStr(NAME); + if (op.hasError()) return null; + for (Map permission : (List) getListValue(latestConf, "permissions")) { + if (name.equals(permission.get(NAME))) { + LinkedHashMap copy = new LinkedHashMap<>(permission); + copy.putAll(op.getDataMap()); + op.setCommandData(copy); + return SET_PERMISSION.edit(latestConf, op); + } + } + op.addError("No such permission " + name); + return null; + } + }, + DELETE_PERMISSION("delete-permission") { + @Override + public Map edit(Map latestConf, CommandOperation op) { + List names = op.getStrs(""); + if (names == null || names.isEmpty()) { + op.addError("Invalid command"); + return null; + } + names = new ArrayList<>(names); + List copy = new ArrayList<>(); + List p = getListValue(latestConf, "permissions"); + for (Map map : p) { + Object n = map.get(NAME); + if (names.contains(n)) { + names.remove(n); + continue; + } else { + copy.add(map); + } + } + if (!names.isEmpty()) { + op.addError("Unknown permission name(s) " + names); + return null; + } + latestConf.put("permissions", copy); + return latestConf; + } + }; + + public abstract Map edit(Map latestConf, CommandOperation op); + + public final String name; + + OPERATION(String s) { + this.name = s; + } + + public static OPERATION get(String name) { + for (OPERATION o : values()) if (o.name.equals(name)) return o; + return null; + } + } + + public static final Set HTTP_METHODS = ImmutableSet.of("GET", "POST", "DELETE", "PUT", "HEAD"); + + private static final Map> well_known_permissions = (Map) Utils.fromJSONString( + " { " + + " security-edit :{" + + " path:['/admin/authentication','/admin/authorization']," + + " collection:null," + + " method:POST }," + + " security-read :{" + + " path:['/admin/authentication','/admin/authorization']," + + " collection:null," + + " method:GET}," + + " schema-edit :{" + + " method:POST," + + " path:'/schema/*'}," + + " collection-admin-edit :{" + + " collection:null," + + " path:'/admin/collections'}," + + " collection-admin-read :{" + + " collection:null," + + " path:'/admin/collections'}," + + " schema-read :{" + + " method:GET," + + " path:'/schema/*'}," + + " config-read :{" + + " method:GET," + + " path:'/config/*'}," + + " update :{" + + " path:'/update/*'}," + + " read :{" + + " path:['/select', '/get','/browse','/tvrh','/terms','/clustering','/elevate', '/export','/spell','/clustering']}," + + " config-edit:{" + + " method:POST," + + " path:'/config/*'}," + + " all:{collection:['*', null]}" + + "}"); + + static { + ((Map) well_known_permissions.get("collection-admin-edit")).put(Predicate.class.getName(), getCollectionActionPredicate(true)); + ((Map) well_known_permissions.get("collection-admin-read")).put(Predicate.class.getName(), getCollectionActionPredicate(false)); + } + + private static Predicate getCollectionActionPredicate(final boolean isEdit) { + return new Predicate() { + @Override + public boolean test(AuthorizationContext context) { + String action = context.getParams().get("action"); + if (action == null) return false; + CollectionParams.CollectionAction collectionAction = CollectionParams.CollectionAction.get(action); + if (collectionAction == null) return false; + return isEdit ? collectionAction.isWrite : !collectionAction.isWrite; + } + }; + } + + + public static void main(String[] args) { + System.out.println(Utils.toJSONString(well_known_permissions)); + + } + + public interface Predicate { + + boolean test(T t); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraUserRolesLookupStrategy.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraUserRolesLookupStrategy.java b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraUserRolesLookupStrategy.java new file mode 100644 index 0000000..a54e4ad --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/main/java/org.apache.ambari.infra.security/InfraUserRolesLookupStrategy.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.apache.ambari.infra.security; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.hadoop.security.authentication.server.AuthenticationToken; +import org.apache.hadoop.security.authentication.util.KerberosName; + +import java.security.Principal; +import java.util.Map; +import java.util.Set; + + +/** + * Strategy class to get roles with the principal name (in a specific format e.g.: 'name@DOMAIN') + * in case of KerberosPlugin is used for authentication + */ +public class InfraUserRolesLookupStrategy { + + public Set getUserRolesFromPrincipal(Map> usersVsRoles, Principal principal) { + if (principal instanceof AuthenticationToken) { + AuthenticationToken authenticationToken = (AuthenticationToken) principal; + KerberosName kerberosName = new KerberosName(authenticationToken.getName()); + Set rolesResult = usersVsRoles.get(String.format("%s@%s", kerberosName.getServiceName(), kerberosName.getRealm())); + if (CollectionUtils.isEmpty(rolesResult)) { + rolesResult = usersVsRoles.get(principal.getName()); + } + return rolesResult; + } else { + return usersVsRoles.get(principal.getName()); + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraKerberosHostValidatorTest.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraKerberosHostValidatorTest.java b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraKerberosHostValidatorTest.java new file mode 100644 index 0000000..828f09a --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraKerberosHostValidatorTest.java @@ -0,0 +1,114 @@ +/* + * 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.apache.ambari.infra.security; + +import org.apache.hadoop.security.authentication.server.AuthenticationToken; +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class InfraKerberosHostValidatorTest { + + private static final String DEFAULT_SERVICE_USER = "logsearch"; + + private InfraKerberosHostValidator underTest = new InfraKerberosHostValidator(); + private AuthenticationToken principal; + + + @Before + public void setUp() { + principal = new AuthenticationToken(DEFAULT_SERVICE_USER, DEFAULT_SERVICE_USER + "/c6401.ambari.apache.org@EXAMPLE.COM", "kerberos"); + } + + @Test + public void testValidateHosts() { + // GIVEN + Map> userHostsMap = generateUserHostMap("c6401.ambari.apache.org"); + // WHEN + boolean result = underTest.validate(principal, userHostsMap, new HashMap()); + // THEN + assertTrue(result); + } + + @Test + public void testValidateHostsValid() { + // GIVEN + Map> userHostsMap = generateUserHostMap("c6402.ambari.apache.org"); + // WHEN + boolean result = underTest.validate(principal, userHostsMap, new HashMap()); + // THEN + assertFalse(result); + + } + + @Test + public void testValidateHostRegex() { + // GIVEN + Map userHostRegex = generateRegexMap("c\\d+.*.apache.org"); + // WHEN + boolean result = underTest.validate(principal, new HashMap>(), userHostRegex); + // THEN + assertTrue(result); + + } + + @Test + public void testValidateHostRegexInvalid() { + // GIVEN + Map userHostRegex = generateRegexMap("c\\d+.*.org.apache"); + // WHEN + boolean result = underTest.validate(principal, new HashMap>(), userHostRegex); + // THEN + assertFalse(result); + } + + @Test + public void testPrecedence() { + // GIVEN + Map> userHostsMap = generateUserHostMap("c6402.ambari.apache.org"); + Map userHostRegex = generateRegexMap("c\\d+.*.apache.org"); + // WHEN + boolean result = underTest.validate(principal, userHostsMap, userHostRegex); + // THEN + assertTrue(result); + } + + private Map> generateUserHostMap(String... hosts) { + Map> map = new HashMap<>(); + Set hostSet = new HashSet<>(); + for (String host : hosts) { + hostSet.add(host); + } + map.put(DEFAULT_SERVICE_USER, hostSet); + return map; + } + + private Map generateRegexMap(String regex) { + Map map = new HashMap<>(); + map.put(DEFAULT_SERVICE_USER, regex); + return map; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraRuleBasedAuthorizationPluginTest.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraRuleBasedAuthorizationPluginTest.java b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraRuleBasedAuthorizationPluginTest.java new file mode 100644 index 0000000..ee84969 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraRuleBasedAuthorizationPluginTest.java @@ -0,0 +1,247 @@ +/* + * 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.apache.ambari.infra.security; + +import java.security.Principal; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.hadoop.security.authentication.server.AuthenticationToken; +import org.apache.solr.common.params.MapSolrParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.Utils; +import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationContext.RequestType; +import org.apache.solr.security.AuthorizationResponse; +import org.junit.Test; + +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.apache.solr.common.util.Utils.makeMap; +import static org.junit.Assert.assertEquals; + +public class InfraRuleBasedAuthorizationPluginTest { + + private static final String PERMISSIONS = "{" + + " user-host : {" + + " 'infra-solr@EXAMPLE.COM': [hostname, hostname2]" + + " }," + + " user-role : {" + + " 'infra-solr@EXAMPLE.COM': [admin]," + + " 'logsearch@EXAMPLE.COM': [logsearch_role,dev]," + + " 'logfeeder@EXAMPLE.COM': [logsearch_role,dev]," + + " 'atlas@EXAMPLE.COM': [atlas_role, audit_role, dev]," + + " 'knox@EXAMPLE.COM': [audit_role,dev]," + + " 'hdfs@EXAMPLE.COM': [audit_role,dev]," + + " 'hbase@EXAMPLE.COM': [audit_role,dev]," + + " 'yarn@EXAMPLE.COM': [audit_role,dev]," + + " 'knox@EXAMPLE.COM': [audit_role,dev]," + + " 'kafka@EXAMPLE.COM': [audit_role,dev]," + + " 'kms@EXAMPLE.COM': [audit_role,dev]," + + " 'storm@EXAMPLE.COM': [audit_role,dev]," + + " 'rangeradmin@EXAMPLE.COM':[ranger_role, audit_role, dev]" + + " }," + + " permissions : [" + + " {name:'collection-admin-read'," + + " role:null}," + + " {name:collection-admin-edit ," + + " role:[logsearch_role, atlas_role, ranger_role, admin]}," + + " {name:mycoll_update," + + " collection:mycoll," + + " path:'/*'," + + " role:[logsearch_role,admin]" + + " }," + + " {name:mycoll2_update," + + " collection:mycoll2," + + " path:'/*'," + + " role:[ranger_role, audit_role, admin]" + + " }," + + "{name:read , role:dev }]}"; + + @Test + public void testPermissions() { + int STATUS_OK = 200; + int FORBIDDEN = 403; + int PROMPT_FOR_CREDENTIALS = 401; + + checkRules(makeMap("resource", "/#", + "httpMethod", "POST", + "userPrincipal", "unknownuser", + "collectionRequests", "freeforall" ) + , STATUS_OK); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "POST", + "userPrincipal", "tim", + "collectionRequests", "mycoll") + , FORBIDDEN); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "POST", + "userPrincipal", "logsearch", + "collectionRequests", "mycoll") + , STATUS_OK); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "GET", + "userPrincipal", "rangeradmin", + "collectionRequests", "mycoll") + , FORBIDDEN); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "GET", + "userPrincipal", "rangeradmin", + "collectionRequests", "mycoll2") + , STATUS_OK); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "GET", + "userPrincipal", "logsearch", + "collectionRequests", "mycoll2") + , FORBIDDEN); + + checkRules(makeMap("resource", "/update/json/docs", + "httpMethod", "POST", + "userPrincipal", "kms", + "collectionRequests", "mycoll2") + , STATUS_OK); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", "tim", + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "CREATE"))) + , FORBIDDEN); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", null, + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "CREATE"))) + , PROMPT_FOR_CREDENTIALS); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", "rangeradmin", + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "CREATE"))) + , STATUS_OK); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", "kms", + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "CREATE"))) + , FORBIDDEN); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", "kms", + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "LIST"))) + , STATUS_OK); + + checkRules(makeMap("resource", "/admin/collections", + "userPrincipal", "rangeradmin", + "requestType", RequestType.ADMIN, + "collectionRequests", null, + "params", new MapSolrParams(singletonMap("action", "LIST"))) + , STATUS_OK); + } + + private void checkRules(Map values, int expected) { + checkRules(values,expected,(Map) Utils.fromJSONString(PERMISSIONS)); + } + + private void checkRules(Map values, int expected, Map permissions) { + AuthorizationContext context = new MockAuthorizationContext(values); + InfraRuleBasedAuthorizationPlugin plugin = new InfraRuleBasedAuthorizationPlugin(); + plugin.init(permissions); + AuthorizationResponse authResp = plugin.authorize(context); + assertEquals(expected, authResp.statusCode); + } + + private static class MockAuthorizationContext extends AuthorizationContext { + private final Map values; + + private MockAuthorizationContext(Map values) { + this.values = values; + } + + @Override + public SolrParams getParams() { + SolrParams params = (SolrParams) values.get("params"); + return params == null ? new MapSolrParams(new HashMap()) : params; + } + + @Override + public Principal getUserPrincipal() { + Object userPrincipal = values.get("userPrincipal"); + return userPrincipal == null ? null : + new AuthenticationToken(String.valueOf(userPrincipal), String.format("%s%s", String.valueOf(userPrincipal), "/hostname@EXAMPLE.COM"), "kerberos"); + } + + @Override + public String getHttpHeader(String header) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public List getCollectionRequests() { + Object collectionRequests = values.get("collectionRequests"); + if (collectionRequests instanceof String) { + return singletonList(new CollectionRequest((String)collectionRequests)); + } + return (List) collectionRequests; + } + + @Override + public RequestType getRequestType() { + return (RequestType) values.get("requestType"); + } + + @Override + public String getHttpMethod() { + return (String) values.get("httpMethod"); + } + + @Override + public String getResource() { + return (String) values.get("resource"); + } + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d3f1aed5/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraUserRolesLookupStrategyTest.java ---------------------------------------------------------------------- diff --git a/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraUserRolesLookupStrategyTest.java b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraUserRolesLookupStrategyTest.java new file mode 100644 index 0000000..c1a47d1 --- /dev/null +++ b/ambari-infra/ambari-infra-solr-plugin/src/test/java/org/apache/ambari/infra/security/InfraUserRolesLookupStrategyTest.java @@ -0,0 +1,83 @@ +/* + * 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.apache.ambari.infra.security; + +import com.google.common.collect.Sets; +import org.apache.hadoop.security.authentication.server.AuthenticationToken; +import org.apache.http.auth.BasicUserPrincipal; +import org.junit.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class InfraUserRolesLookupStrategyTest { + + private InfraUserRolesLookupStrategy underTest = new InfraUserRolesLookupStrategy(); + + @Test + public void testLookupRolesForPrincipalName() { + // GIVEN + Map> usersVsRoles = generateUserRolesMap(); + AuthenticationToken principal = new AuthenticationToken( + "logsearch", "logsearch/c6401.ambari.apache.org@EXAMPLE.COM", "kerberos"); + // WHEN + Set result = underTest.getUserRolesFromPrincipal(usersVsRoles, principal); + // THEN + assertTrue(result.contains("logsearch_user")); + assertTrue(result.contains("ranger_user")); + assertFalse(result.contains("admin")); + } + + @Test + public void testLookupRolesForNonKerberosPrincipalName() { + // GIVEN + Map> usersVsRoles = generateUserRolesMap(); + BasicUserPrincipal principal = new BasicUserPrincipal("infra-solr"); + // WHEN + Set result = underTest.getUserRolesFromPrincipal(usersVsRoles, principal); + // THEN + assertTrue(result.contains("admin")); + assertTrue(result.contains("logsearch_user")); + } + + @Test + public void testLookupRolesWithNonKerberosPrincipalWithoutRoles() { + // GIVEN + Map> usersVsRoles = generateUserRolesMap(); + BasicUserPrincipal principal = new BasicUserPrincipal("unknownuser"); + // WHEN + Set result = underTest.getUserRolesFromPrincipal(usersVsRoles, principal); + // THEN + assertTrue(result.isEmpty()); + } + + private Map> generateUserRolesMap() { + Map> usersVsRoles = new HashMap<>(); + usersVsRoles.put("logsearch@EXAMPLE.COM", Sets.newHashSet("logsearch_user", "ranger_user")); + usersVsRoles.put("infra-solr@EXAMPLE.COM", Sets.newHashSet("admin")); + usersVsRoles.put("infra-solr", Sets.newHashSet("admin", "logsearch_user")); + usersVsRoles.put("unknownuser", new HashSet()); + return usersVsRoles; + } +}