Repository: sentry Updated Branches: refs/heads/SENTRY-1205 aef769858 -> 4767ec38e
SENTRY-1348: Move HA related class from sentry-provider-db to sentry-service-common(Colin Ma, reviewed by Dapeng Sun) Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/4767ec38 Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/4767ec38 Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/4767ec38 Branch: refs/heads/SENTRY-1205 Commit: 4767ec38ef2a5d967d37539c64c787c986afe5cb Parents: aef7698 Author: Colin Ma <[email protected]> Authored: Wed Jun 22 14:11:57 2016 +0800 Committer: Colin Ma <[email protected]> Committed: Wed Jun 22 14:11:57 2016 +0800 ---------------------------------------------------------------------- sentry-provider/sentry-provider-db/pom.xml | 17 -- .../persistent/FixedJsonInstanceSerializer.java | 163 ------------ .../db/service/persistent/HAContext.java | 262 ------------------- .../service/thrift/JaasConfiguration.java | 133 ---------- sentry-service/sentry-service-common/pom.xml | 17 ++ .../persistent/FixedJsonInstanceSerializer.java | 163 ++++++++++++ .../db/service/persistent/HAContext.java | 262 +++++++++++++++++++ .../service/thrift/JaasConfiguration.java | 133 ++++++++++ 8 files changed, 575 insertions(+), 575 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-provider/sentry-provider-db/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/pom.xml b/sentry-provider/sentry-provider-db/pom.xml index f3029fa..3d76198 100644 --- a/sentry-provider/sentry-provider-db/pom.xml +++ b/sentry-provider/sentry-provider-db/pom.xml @@ -38,11 +38,6 @@ limitations under the License. </dependency> <dependency> <groupId>org.apache.hadoop</groupId> - <artifactId>hadoop-common</artifactId> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-jobclient</artifactId> </dependency> <dependency> @@ -166,18 +161,6 @@ limitations under the License. <scope>test</scope> </dependency> <dependency> - <groupId>org.apache.curator</groupId> - <artifactId>curator-recipes</artifactId> - </dependency> - <dependency> - <groupId>org.apache.curator</groupId> - <artifactId>curator-x-discovery</artifactId> - </dependency> - <dependency> - <groupId>org.apache.curator</groupId> - <artifactId>curator-test</artifactId> - </dependency> - <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java deleted file mode 100644 index 476bf6a..0000000 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * 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.sentry.provider.db.service.persistent; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.JsonParseException; -import org.codehaus.jackson.map.DeserializationConfig; -import org.codehaus.jackson.map.JsonMappingException; -import org.codehaus.jackson.map.ObjectMapper; - -import com.google.common.base.Preconditions; -import org.apache.curator.x.discovery.ServiceInstance; -import org.apache.curator.x.discovery.ServiceInstanceBuilder; -import org.apache.curator.x.discovery.ServiceType; -import org.apache.curator.x.discovery.UriSpec; -import org.apache.curator.x.discovery.details.InstanceSerializer; - -// TODO: Workaround for CURATOR-5 (https://issues.apache.org/jira/browse/CURATOR-5) -// Remove this class (code from pull request listed on JIRA) and use regular JsonInstanceSerializer once fixed -// (Otherwise we can't properly serialize objects for the ZK Service Discovery) -public class FixedJsonInstanceSerializer<T> implements InstanceSerializer<T> -{ - - private final ObjectMapper mMapper; - private final Class<T> mPayloadClass; - - /** - * @param payloadClass - * used to validate payloads when deserializing - */ - public FixedJsonInstanceSerializer(final Class<T> payloadClass) { - this(payloadClass, new ObjectMapper()); - } - - public FixedJsonInstanceSerializer(final Class<T> pPayloadClass, final ObjectMapper pMapper) { - mPayloadClass = pPayloadClass; - mMapper = pMapper; - mMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); - } - - @Override - public byte[] serialize(final ServiceInstance<T> pInstance) throws Exception { - final ByteArrayOutputStream out = new ByteArrayOutputStream(); - mMapper.writeValue(out, pInstance); - return out.toByteArray(); - - } - - private String getTextField(final JsonNode pNode, final String pFieldName) { - Preconditions.checkNotNull(pNode); - Preconditions.checkNotNull(pFieldName); - return pNode.get(pFieldName) != null ? pNode.get(pFieldName).getTextValue() : null; - } - - private Integer getIntegerField(final JsonNode pNode, final String pFieldName) { - Preconditions.checkNotNull(pNode); - Preconditions.checkNotNull(pFieldName); - return pNode.get(pFieldName) != null && pNode.get(pFieldName).isNumber() ? pNode.get(pFieldName) - .getIntValue() : null; - } - - private Long getLongField(final JsonNode pNode, final String pFieldName) { - Preconditions.checkNotNull(pNode); - Preconditions.checkNotNull(pFieldName); - return pNode.get(pFieldName) != null && pNode.get(pFieldName).isLong() ? pNode.get(pFieldName).getLongValue() - : null; - } - - private <O> O getObject(final JsonNode pNode, final String pFieldName, final Class<O> pObjectClass) - throws JsonParseException, JsonMappingException, IOException { - Preconditions.checkNotNull(pNode); - Preconditions.checkNotNull(pFieldName); - Preconditions.checkNotNull(pObjectClass); - if (pNode.get(pFieldName) != null && pNode.get(pFieldName).isObject()) { - return mMapper.readValue(pNode.get(pFieldName), pObjectClass); - } else { - return null; - } - } - - @Override - public ServiceInstance<T> deserialize(final byte[] pBytes) throws Exception { - final ByteArrayInputStream bais = new ByteArrayInputStream(pBytes); - final JsonNode rootNode = mMapper.readTree(bais); - final ServiceInstanceBuilder<T> builder = ServiceInstance.builder(); - { - final String address = getTextField(rootNode, "address"); - if (address != null) { - builder.address(address); - } - } - { - final String id = getTextField(rootNode, "id"); - if (id != null) { - builder.id(id); - } - } - { - final String name = getTextField(rootNode, "name"); - if (name != null) { - builder.name(name); - } - } - { - final Integer port = getIntegerField(rootNode, "port"); - if (port != null) { - builder.port(port); - } - } - { - final Integer sslPort = getIntegerField(rootNode, "sslPort"); - if (sslPort != null) { - builder.sslPort(sslPort); - } - } - { - final Long registrationTimeUTC = getLongField(rootNode, "registrationTimeUTC"); - if (registrationTimeUTC != null) { - builder.registrationTimeUTC(registrationTimeUTC); - } - } - { - final T payload = getObject(rootNode, "payload", mPayloadClass); - if (payload != null) { - builder.payload(payload); - } - } - { - final ServiceType serviceType = getObject(rootNode, "serviceType", ServiceType.class); - if (serviceType != null) { - builder.serviceType(serviceType); - } - } - { - final UriSpec uriSpec = getObject(rootNode, "uriSpec", UriSpec.class); - if (uriSpec != null) { - builder.uriSpec(uriSpec); - } - } - return builder.build(); - } - -} http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java deleted file mode 100644 index cacc29f..0000000 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java +++ /dev/null @@ -1,262 +0,0 @@ -/** - * 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.sentry.provider.db.service.persistent; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import org.apache.curator.RetryPolicy; -import org.apache.curator.framework.CuratorFramework; -import org.apache.curator.framework.CuratorFrameworkFactory; -import org.apache.curator.framework.api.ACLProvider; -import org.apache.curator.framework.imps.CuratorFrameworkState; -import org.apache.curator.framework.imps.DefaultACLProvider; -import org.apache.curator.retry.RetryNTimes; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.security.SecurityUtil; -import org.apache.sentry.service.thrift.JaasConfiguration; -import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; -import org.apache.zookeeper.ZooDefs.Perms; -import org.apache.zookeeper.client.ZooKeeperSaslClient; -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 com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; - -/** - * Stores the HA related context - */ -public class HAContext { - - private static final Logger LOGGER = LoggerFactory.getLogger(HAContext.class); - private static volatile HAContext serverHAContext = null; - private static boolean aclChecked = false; - - public final static String SENTRY_SERVICE_REGISTER_NAMESPACE = "sentry-service"; - public static final String SENTRY_ZK_JAAS_NAME = "SentryClient"; - private final String zookeeperQuorum; - private final int retriesMaxCount; - private final int sleepMsBetweenRetries; - private final String namespace; - - private final boolean zkSecure; - private List<ACL> saslACL; - - private final CuratorFramework curatorFramework; - private final RetryPolicy retryPolicy; - - protected HAContext(Configuration conf) throws Exception { - this.zookeeperQuorum = conf.get(ServerConfig.SENTRY_HA_ZOOKEEPER_QUORUM, - ServerConfig.SENTRY_HA_ZOOKEEPER_QUORUM_DEFAULT); - this.retriesMaxCount = conf.getInt(ServerConfig.SENTRY_HA_ZOOKEEPER_RETRIES_MAX_COUNT, - ServerConfig.SENTRY_HA_ZOOKEEPER_RETRIES_MAX_COUNT_DEFAULT); - this.sleepMsBetweenRetries = conf.getInt(ServerConfig.SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS, - ServerConfig.SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS_DEFAULT); - this.namespace = conf.get(ServerConfig.SENTRY_HA_ZOOKEEPER_NAMESPACE, - ServerConfig.SENTRY_HA_ZOOKEEPER_NAMESPACE_DEFAULT); - this.zkSecure = conf.getBoolean(ServerConfig.SENTRY_HA_ZOOKEEPER_SECURITY, - ServerConfig.SENTRY_HA_ZOOKEEPER_SECURITY_DEFAULT); - ACLProvider aclProvider; - validateConf(); - if (zkSecure) { - LOGGER.info("Connecting to ZooKeeper with SASL/Kerberos and using 'sasl' ACLs"); - setJaasConfiguration(conf); - System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, - SENTRY_ZK_JAAS_NAME); - saslACL = Lists.newArrayList(); - saslACL.add(new ACL(Perms.ALL, new Id("sasl", getServicePrincipal(conf, - ServerConfig.PRINCIPAL)))); - saslACL.add(new ACL(Perms.ALL, new Id("sasl", getServicePrincipal(conf, - ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_PRINCIPAL)))); - aclProvider = new SASLOwnerACLProvider(); - String allowConnect = conf.get(ServerConfig.ALLOW_CONNECT); - - if (!Strings.isNullOrEmpty(allowConnect)) { - for (String principal : Arrays.asList(allowConnect.split("\\s*,\\s*"))) { - LOGGER.info("Adding acls for " + principal); - saslACL.add(new ACL(Perms.ALL, new Id("sasl", principal))); - } - } - } else { - LOGGER.info("Connecting to ZooKeeper without authentication"); - aclProvider = new DefaultACLProvider(); - } - - retryPolicy = new RetryNTimes(retriesMaxCount, sleepMsBetweenRetries); - this.curatorFramework = CuratorFrameworkFactory.builder() - .namespace(this.namespace) - .connectString(this.zookeeperQuorum) - .retryPolicy(retryPolicy) - .aclProvider(aclProvider) - .build(); - startCuratorFramework(); - } - - /** - * Use common HAContext (ie curator framework connection to ZK) - * - * @param conf - * @throws Exception - */ - public static HAContext getHAContext(Configuration conf) throws Exception { - if (serverHAContext == null) { - serverHAContext = new HAContext(conf); - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - LOGGER.info("ShutdownHook closing curator framework"); - try { - clearServerContext(); - } catch (Throwable t) { - LOGGER.error("Error stopping SentryService", t); - } - } - }); - - } - return serverHAContext; - } - - // HA context for server which verifies the ZK ACLs on namespace - public static HAContext getHAServerContext(Configuration conf) throws Exception { - HAContext serverContext = getHAContext(conf); - serverContext.checkAndSetACLs(); - return serverContext; - } - - @VisibleForTesting - public static synchronized void clearServerContext() { - if (serverHAContext != null) { - serverHAContext.getCuratorFramework().close(); - serverHAContext = null; - } - } - - public void startCuratorFramework() { - if (curatorFramework.getState() != CuratorFrameworkState.STARTED) { - curatorFramework.start(); - } - } - - public CuratorFramework getCuratorFramework() { - return this.curatorFramework; - } - - public String getZookeeperQuorum() { - return zookeeperQuorum; - } - - public static boolean isHaEnabled(Configuration conf) { - return conf.getBoolean(ServerConfig.SENTRY_HA_ENABLED, ServerConfig.SENTRY_HA_ENABLED_DEFAULT); - } - - public String getNamespace() { - return namespace; - } - - public RetryPolicy getRetryPolicy() { - return retryPolicy; - } - - private void validateConf() { - Preconditions.checkNotNull(zookeeperQuorum, "Zookeeper Quorum should not be null."); - Preconditions.checkNotNull(namespace, "Zookeeper namespace should not be null."); - } - - protected String getServicePrincipal(Configuration conf, String confProperty) - throws IOException { - String principal = conf.get(confProperty); - Preconditions.checkNotNull(principal); - Preconditions.checkArgument(principal.length() != 0, "Server principal is not right."); - return principal.split("[/@]")[0]; - } - - private void checkAndSetACLs() throws Exception { - if (zkSecure && !aclChecked) { - // If znodes were previously created without security enabled, and now it is, we need to go through all existing znodes - // and set the ACLs for them. This is done just once at the startup - // We can't get the namespace znode through curator; have to go through zk client - startCuratorFramework(); - String newNamespace = "/" + curatorFramework.getNamespace(); - if (curatorFramework.getZookeeperClient().getZooKeeper().exists(newNamespace, null) != null) { - List<ACL> acls = curatorFramework.getZookeeperClient().getZooKeeper().getACL(newNamespace, new Stat()); - if (acls.isEmpty() || !acls.get(0).getId().getScheme().equals("sasl")) { - LOGGER.info("'sasl' ACLs not set; setting..."); - List<String> children = curatorFramework.getZookeeperClient().getZooKeeper().getChildren(newNamespace, null); - for (String child : children) { - checkAndSetACLs("/" + child); - } - curatorFramework.getZookeeperClient().getZooKeeper().setACL(newNamespace, saslACL, -1); - } - } - aclChecked = true; - - } - } - - private void checkAndSetACLs(String path) throws Exception { - LOGGER.info("Setting acls on " + path); - List<String> children = curatorFramework.getChildren().forPath(path); - for (String child : children) { - checkAndSetACLs(path + "/" + child); - } - curatorFramework.setACL().withACL(saslACL).forPath(path); - } - - // This gets ignored during most tests, see ZKXTestCaseWithSecurity#setupZKServer() - private void setJaasConfiguration(Configuration conf) throws IOException { - if ("false".equalsIgnoreCase(conf.get( - ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_TICKET_CACHE, - ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_TICKET_CACHE_DEFAULT))) { - String keytabFile = conf.get(ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_KEYTAB); - Preconditions.checkArgument(keytabFile.length() != 0, "Keytab File is not right."); - String principal = conf.get(ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_PRINCIPAL); - principal = SecurityUtil.getServerPrincipal(principal, - conf.get(ServerConfig.RPC_ADDRESS, ServerConfig.RPC_ADDRESS_DEFAULT)); - Preconditions.checkArgument(principal.length() != 0, "Kerberos principal is not right."); - - // This is equivalent to writing a jaas.conf file and setting the system property, "java.security.auth.login.config", to - // point to it (but this way we don't have to write a file, and it works better for the tests) - JaasConfiguration.addEntryForKeytab(SENTRY_ZK_JAAS_NAME, principal, keytabFile); - } else { - // Create jaas conf for ticket cache - JaasConfiguration.addEntryForTicketCache(SENTRY_ZK_JAAS_NAME); - } - javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); - } - - public class SASLOwnerACLProvider implements ACLProvider { - @Override - public List<ACL> getDefaultAcl() { - return saslACL; - } - - @Override - public List<ACL> getAclForPath(String path) { - return saslACL; - } - } -} http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java ---------------------------------------------------------------------- diff --git a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java b/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java deleted file mode 100644 index a79ce5f..0000000 --- a/sentry-provider/sentry-provider-db/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java +++ /dev/null @@ -1,133 +0,0 @@ -/** - * 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.sentry.service.thrift; - -import java.util.HashMap; -import java.util.Map; - -import javax.security.auth.login.AppConfigurationEntry; -import javax.security.auth.login.Configuration; - -/** - * Creates a programmatic version of a jaas.conf file. This can be used instead of writing a jaas.conf file and setting - * the system property, "java.security.auth.login.config", to point to that file. It is meant to be used for connecting to - * ZooKeeper. - * <p> - * example usage: - * JaasConfiguration.addEntry("Client", principal, keytabFile); - * javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); - */ -public final class JaasConfiguration extends Configuration { - private static Map<String, AppConfigurationEntry> entries = new HashMap<String, AppConfigurationEntry>(); - private static JaasConfiguration me = null; - private static final String krb5LoginModuleName; - - static { - if (System.getProperty("java.vendor").contains("IBM")) { - krb5LoginModuleName = "com.ibm.security.auth.module.Krb5LoginModule"; - } - else { - krb5LoginModuleName = "com.sun.security.auth.module.Krb5LoginModule"; - } - } - - private JaasConfiguration() { - // don't need to do anything here but we want to make it private - } - - /** - * Return the singleton. You'd typically use it only to do this: - * <p> - * javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); - * - * @return - */ - public static Configuration getInstance() { - if (me == null) { - me = new JaasConfiguration(); - } - return me; - } - - /** - * Add an entry to the jaas configuration with the passed in name, principal, and keytab. The other necessary options will be - * set for you. - * - * @param name The name of the entry (e.g. "Client") - * @param principal The principal of the user - * @param keytab The location of the keytab - */ - public static void addEntryForKeytab(String name, String principal, String keytab) { - Map<String, String> options = new HashMap<String, String>(); - options.put("keyTab", keytab); - options.put("principal", principal); - options.put("useKeyTab", "true"); - options.put("storeKey", "true"); - options.put("useTicketCache", "false"); - AppConfigurationEntry entry = new AppConfigurationEntry(krb5LoginModuleName, - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); - entries.put(name, entry); - } - - /** - * Add an entry to the jaas configuration with the passed in name. The other - * necessary options will be set for you. - * - * @param name The name of the entry (e.g. "Client") - */ - public static void addEntryForTicketCache(String sectionName) { - Map<String, String> options = new HashMap<String, String>(); - options.put("useKeyTab", "false"); - options.put("storeKey", "false"); - options.put("useTicketCache", "true"); - AppConfigurationEntry entry = new AppConfigurationEntry(krb5LoginModuleName, - AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); - entries.put(sectionName, entry); - } - - /** - * Removes the specified entry. - * - * @param name The name of the entry to remove - */ - public static void removeEntry(String name) { - entries.remove(name); - } - - /** - * Clears all entries. - */ - public static void clearEntries() { - entries.clear(); - } - - /** - * Returns the entries map. - * - * @return the entries map - */ - public static Map<String, AppConfigurationEntry> getEntries() { - return entries; - } - - @Override - public AppConfigurationEntry[] getAppConfigurationEntry(String name) { - return new AppConfigurationEntry[]{entries.get(name)}; - } -} - http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-service/sentry-service-common/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-common/pom.xml b/sentry-service/sentry-service-common/pom.xml index d47cafa..512c341 100644 --- a/sentry-service/sentry-service-common/pom.xml +++ b/sentry-service/sentry-service-common/pom.xml @@ -52,6 +52,23 @@ limitations under the License. <groupId>org.apache.sentry</groupId> <artifactId>sentry-core-common</artifactId> </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-recipes</artifactId> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-x-discovery</artifactId> + </dependency> + <dependency> + <groupId>org.apache.curator</groupId> + <artifactId>curator-test</artifactId> + </dependency> + <dependency> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-common</artifactId> + <scope>provided</scope> + </dependency> </dependencies> <build> http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java new file mode 100644 index 0000000..476bf6a --- /dev/null +++ b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/FixedJsonInstanceSerializer.java @@ -0,0 +1,163 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.JsonParseException; +import org.codehaus.jackson.map.DeserializationConfig; +import org.codehaus.jackson.map.JsonMappingException; +import org.codehaus.jackson.map.ObjectMapper; + +import com.google.common.base.Preconditions; +import org.apache.curator.x.discovery.ServiceInstance; +import org.apache.curator.x.discovery.ServiceInstanceBuilder; +import org.apache.curator.x.discovery.ServiceType; +import org.apache.curator.x.discovery.UriSpec; +import org.apache.curator.x.discovery.details.InstanceSerializer; + +// TODO: Workaround for CURATOR-5 (https://issues.apache.org/jira/browse/CURATOR-5) +// Remove this class (code from pull request listed on JIRA) and use regular JsonInstanceSerializer once fixed +// (Otherwise we can't properly serialize objects for the ZK Service Discovery) +public class FixedJsonInstanceSerializer<T> implements InstanceSerializer<T> +{ + + private final ObjectMapper mMapper; + private final Class<T> mPayloadClass; + + /** + * @param payloadClass + * used to validate payloads when deserializing + */ + public FixedJsonInstanceSerializer(final Class<T> payloadClass) { + this(payloadClass, new ObjectMapper()); + } + + public FixedJsonInstanceSerializer(final Class<T> pPayloadClass, final ObjectMapper pMapper) { + mPayloadClass = pPayloadClass; + mMapper = pMapper; + mMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + @Override + public byte[] serialize(final ServiceInstance<T> pInstance) throws Exception { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + mMapper.writeValue(out, pInstance); + return out.toByteArray(); + + } + + private String getTextField(final JsonNode pNode, final String pFieldName) { + Preconditions.checkNotNull(pNode); + Preconditions.checkNotNull(pFieldName); + return pNode.get(pFieldName) != null ? pNode.get(pFieldName).getTextValue() : null; + } + + private Integer getIntegerField(final JsonNode pNode, final String pFieldName) { + Preconditions.checkNotNull(pNode); + Preconditions.checkNotNull(pFieldName); + return pNode.get(pFieldName) != null && pNode.get(pFieldName).isNumber() ? pNode.get(pFieldName) + .getIntValue() : null; + } + + private Long getLongField(final JsonNode pNode, final String pFieldName) { + Preconditions.checkNotNull(pNode); + Preconditions.checkNotNull(pFieldName); + return pNode.get(pFieldName) != null && pNode.get(pFieldName).isLong() ? pNode.get(pFieldName).getLongValue() + : null; + } + + private <O> O getObject(final JsonNode pNode, final String pFieldName, final Class<O> pObjectClass) + throws JsonParseException, JsonMappingException, IOException { + Preconditions.checkNotNull(pNode); + Preconditions.checkNotNull(pFieldName); + Preconditions.checkNotNull(pObjectClass); + if (pNode.get(pFieldName) != null && pNode.get(pFieldName).isObject()) { + return mMapper.readValue(pNode.get(pFieldName), pObjectClass); + } else { + return null; + } + } + + @Override + public ServiceInstance<T> deserialize(final byte[] pBytes) throws Exception { + final ByteArrayInputStream bais = new ByteArrayInputStream(pBytes); + final JsonNode rootNode = mMapper.readTree(bais); + final ServiceInstanceBuilder<T> builder = ServiceInstance.builder(); + { + final String address = getTextField(rootNode, "address"); + if (address != null) { + builder.address(address); + } + } + { + final String id = getTextField(rootNode, "id"); + if (id != null) { + builder.id(id); + } + } + { + final String name = getTextField(rootNode, "name"); + if (name != null) { + builder.name(name); + } + } + { + final Integer port = getIntegerField(rootNode, "port"); + if (port != null) { + builder.port(port); + } + } + { + final Integer sslPort = getIntegerField(rootNode, "sslPort"); + if (sslPort != null) { + builder.sslPort(sslPort); + } + } + { + final Long registrationTimeUTC = getLongField(rootNode, "registrationTimeUTC"); + if (registrationTimeUTC != null) { + builder.registrationTimeUTC(registrationTimeUTC); + } + } + { + final T payload = getObject(rootNode, "payload", mPayloadClass); + if (payload != null) { + builder.payload(payload); + } + } + { + final ServiceType serviceType = getObject(rootNode, "serviceType", ServiceType.class); + if (serviceType != null) { + builder.serviceType(serviceType); + } + } + { + final UriSpec uriSpec = getObject(rootNode, "uriSpec", UriSpec.class); + if (uriSpec != null) { + builder.uriSpec(uriSpec); + } + } + return builder.build(); + } + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java new file mode 100644 index 0000000..cacc29f --- /dev/null +++ b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/provider/db/service/persistent/HAContext.java @@ -0,0 +1,262 @@ +/** + * 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.sentry.provider.db.service.persistent; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.apache.curator.RetryPolicy; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.api.ACLProvider; +import org.apache.curator.framework.imps.CuratorFrameworkState; +import org.apache.curator.framework.imps.DefaultACLProvider; +import org.apache.curator.retry.RetryNTimes; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.SecurityUtil; +import org.apache.sentry.service.thrift.JaasConfiguration; +import org.apache.sentry.service.thrift.ServiceConstants.ServerConfig; +import org.apache.zookeeper.ZooDefs.Perms; +import org.apache.zookeeper.client.ZooKeeperSaslClient; +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 com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; + +/** + * Stores the HA related context + */ +public class HAContext { + + private static final Logger LOGGER = LoggerFactory.getLogger(HAContext.class); + private static volatile HAContext serverHAContext = null; + private static boolean aclChecked = false; + + public final static String SENTRY_SERVICE_REGISTER_NAMESPACE = "sentry-service"; + public static final String SENTRY_ZK_JAAS_NAME = "SentryClient"; + private final String zookeeperQuorum; + private final int retriesMaxCount; + private final int sleepMsBetweenRetries; + private final String namespace; + + private final boolean zkSecure; + private List<ACL> saslACL; + + private final CuratorFramework curatorFramework; + private final RetryPolicy retryPolicy; + + protected HAContext(Configuration conf) throws Exception { + this.zookeeperQuorum = conf.get(ServerConfig.SENTRY_HA_ZOOKEEPER_QUORUM, + ServerConfig.SENTRY_HA_ZOOKEEPER_QUORUM_DEFAULT); + this.retriesMaxCount = conf.getInt(ServerConfig.SENTRY_HA_ZOOKEEPER_RETRIES_MAX_COUNT, + ServerConfig.SENTRY_HA_ZOOKEEPER_RETRIES_MAX_COUNT_DEFAULT); + this.sleepMsBetweenRetries = conf.getInt(ServerConfig.SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS, + ServerConfig.SENTRY_HA_ZOOKEEPER_SLEEP_BETWEEN_RETRIES_MS_DEFAULT); + this.namespace = conf.get(ServerConfig.SENTRY_HA_ZOOKEEPER_NAMESPACE, + ServerConfig.SENTRY_HA_ZOOKEEPER_NAMESPACE_DEFAULT); + this.zkSecure = conf.getBoolean(ServerConfig.SENTRY_HA_ZOOKEEPER_SECURITY, + ServerConfig.SENTRY_HA_ZOOKEEPER_SECURITY_DEFAULT); + ACLProvider aclProvider; + validateConf(); + if (zkSecure) { + LOGGER.info("Connecting to ZooKeeper with SASL/Kerberos and using 'sasl' ACLs"); + setJaasConfiguration(conf); + System.setProperty(ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY, + SENTRY_ZK_JAAS_NAME); + saslACL = Lists.newArrayList(); + saslACL.add(new ACL(Perms.ALL, new Id("sasl", getServicePrincipal(conf, + ServerConfig.PRINCIPAL)))); + saslACL.add(new ACL(Perms.ALL, new Id("sasl", getServicePrincipal(conf, + ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_PRINCIPAL)))); + aclProvider = new SASLOwnerACLProvider(); + String allowConnect = conf.get(ServerConfig.ALLOW_CONNECT); + + if (!Strings.isNullOrEmpty(allowConnect)) { + for (String principal : Arrays.asList(allowConnect.split("\\s*,\\s*"))) { + LOGGER.info("Adding acls for " + principal); + saslACL.add(new ACL(Perms.ALL, new Id("sasl", principal))); + } + } + } else { + LOGGER.info("Connecting to ZooKeeper without authentication"); + aclProvider = new DefaultACLProvider(); + } + + retryPolicy = new RetryNTimes(retriesMaxCount, sleepMsBetweenRetries); + this.curatorFramework = CuratorFrameworkFactory.builder() + .namespace(this.namespace) + .connectString(this.zookeeperQuorum) + .retryPolicy(retryPolicy) + .aclProvider(aclProvider) + .build(); + startCuratorFramework(); + } + + /** + * Use common HAContext (ie curator framework connection to ZK) + * + * @param conf + * @throws Exception + */ + public static HAContext getHAContext(Configuration conf) throws Exception { + if (serverHAContext == null) { + serverHAContext = new HAContext(conf); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + LOGGER.info("ShutdownHook closing curator framework"); + try { + clearServerContext(); + } catch (Throwable t) { + LOGGER.error("Error stopping SentryService", t); + } + } + }); + + } + return serverHAContext; + } + + // HA context for server which verifies the ZK ACLs on namespace + public static HAContext getHAServerContext(Configuration conf) throws Exception { + HAContext serverContext = getHAContext(conf); + serverContext.checkAndSetACLs(); + return serverContext; + } + + @VisibleForTesting + public static synchronized void clearServerContext() { + if (serverHAContext != null) { + serverHAContext.getCuratorFramework().close(); + serverHAContext = null; + } + } + + public void startCuratorFramework() { + if (curatorFramework.getState() != CuratorFrameworkState.STARTED) { + curatorFramework.start(); + } + } + + public CuratorFramework getCuratorFramework() { + return this.curatorFramework; + } + + public String getZookeeperQuorum() { + return zookeeperQuorum; + } + + public static boolean isHaEnabled(Configuration conf) { + return conf.getBoolean(ServerConfig.SENTRY_HA_ENABLED, ServerConfig.SENTRY_HA_ENABLED_DEFAULT); + } + + public String getNamespace() { + return namespace; + } + + public RetryPolicy getRetryPolicy() { + return retryPolicy; + } + + private void validateConf() { + Preconditions.checkNotNull(zookeeperQuorum, "Zookeeper Quorum should not be null."); + Preconditions.checkNotNull(namespace, "Zookeeper namespace should not be null."); + } + + protected String getServicePrincipal(Configuration conf, String confProperty) + throws IOException { + String principal = conf.get(confProperty); + Preconditions.checkNotNull(principal); + Preconditions.checkArgument(principal.length() != 0, "Server principal is not right."); + return principal.split("[/@]")[0]; + } + + private void checkAndSetACLs() throws Exception { + if (zkSecure && !aclChecked) { + // If znodes were previously created without security enabled, and now it is, we need to go through all existing znodes + // and set the ACLs for them. This is done just once at the startup + // We can't get the namespace znode through curator; have to go through zk client + startCuratorFramework(); + String newNamespace = "/" + curatorFramework.getNamespace(); + if (curatorFramework.getZookeeperClient().getZooKeeper().exists(newNamespace, null) != null) { + List<ACL> acls = curatorFramework.getZookeeperClient().getZooKeeper().getACL(newNamespace, new Stat()); + if (acls.isEmpty() || !acls.get(0).getId().getScheme().equals("sasl")) { + LOGGER.info("'sasl' ACLs not set; setting..."); + List<String> children = curatorFramework.getZookeeperClient().getZooKeeper().getChildren(newNamespace, null); + for (String child : children) { + checkAndSetACLs("/" + child); + } + curatorFramework.getZookeeperClient().getZooKeeper().setACL(newNamespace, saslACL, -1); + } + } + aclChecked = true; + + } + } + + private void checkAndSetACLs(String path) throws Exception { + LOGGER.info("Setting acls on " + path); + List<String> children = curatorFramework.getChildren().forPath(path); + for (String child : children) { + checkAndSetACLs(path + "/" + child); + } + curatorFramework.setACL().withACL(saslACL).forPath(path); + } + + // This gets ignored during most tests, see ZKXTestCaseWithSecurity#setupZKServer() + private void setJaasConfiguration(Configuration conf) throws IOException { + if ("false".equalsIgnoreCase(conf.get( + ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_TICKET_CACHE, + ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_TICKET_CACHE_DEFAULT))) { + String keytabFile = conf.get(ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_KEYTAB); + Preconditions.checkArgument(keytabFile.length() != 0, "Keytab File is not right."); + String principal = conf.get(ServerConfig.SERVER_HA_ZOOKEEPER_CLIENT_PRINCIPAL); + principal = SecurityUtil.getServerPrincipal(principal, + conf.get(ServerConfig.RPC_ADDRESS, ServerConfig.RPC_ADDRESS_DEFAULT)); + Preconditions.checkArgument(principal.length() != 0, "Kerberos principal is not right."); + + // This is equivalent to writing a jaas.conf file and setting the system property, "java.security.auth.login.config", to + // point to it (but this way we don't have to write a file, and it works better for the tests) + JaasConfiguration.addEntryForKeytab(SENTRY_ZK_JAAS_NAME, principal, keytabFile); + } else { + // Create jaas conf for ticket cache + JaasConfiguration.addEntryForTicketCache(SENTRY_ZK_JAAS_NAME); + } + javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); + } + + public class SASLOwnerACLProvider implements ACLProvider { + @Override + public List<ACL> getDefaultAcl() { + return saslACL; + } + + @Override + public List<ACL> getAclForPath(String path) { + return saslACL; + } + } +} http://git-wip-us.apache.org/repos/asf/sentry/blob/4767ec38/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java ---------------------------------------------------------------------- diff --git a/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java new file mode 100644 index 0000000..a79ce5f --- /dev/null +++ b/sentry-service/sentry-service-common/src/main/java/org/apache/sentry/service/thrift/JaasConfiguration.java @@ -0,0 +1,133 @@ +/** + * 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.sentry.service.thrift; + +import java.util.HashMap; +import java.util.Map; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; + +/** + * Creates a programmatic version of a jaas.conf file. This can be used instead of writing a jaas.conf file and setting + * the system property, "java.security.auth.login.config", to point to that file. It is meant to be used for connecting to + * ZooKeeper. + * <p> + * example usage: + * JaasConfiguration.addEntry("Client", principal, keytabFile); + * javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); + */ +public final class JaasConfiguration extends Configuration { + private static Map<String, AppConfigurationEntry> entries = new HashMap<String, AppConfigurationEntry>(); + private static JaasConfiguration me = null; + private static final String krb5LoginModuleName; + + static { + if (System.getProperty("java.vendor").contains("IBM")) { + krb5LoginModuleName = "com.ibm.security.auth.module.Krb5LoginModule"; + } + else { + krb5LoginModuleName = "com.sun.security.auth.module.Krb5LoginModule"; + } + } + + private JaasConfiguration() { + // don't need to do anything here but we want to make it private + } + + /** + * Return the singleton. You'd typically use it only to do this: + * <p> + * javax.security.auth.login.Configuration.setConfiguration(JaasConfiguration.getInstance()); + * + * @return + */ + public static Configuration getInstance() { + if (me == null) { + me = new JaasConfiguration(); + } + return me; + } + + /** + * Add an entry to the jaas configuration with the passed in name, principal, and keytab. The other necessary options will be + * set for you. + * + * @param name The name of the entry (e.g. "Client") + * @param principal The principal of the user + * @param keytab The location of the keytab + */ + public static void addEntryForKeytab(String name, String principal, String keytab) { + Map<String, String> options = new HashMap<String, String>(); + options.put("keyTab", keytab); + options.put("principal", principal); + options.put("useKeyTab", "true"); + options.put("storeKey", "true"); + options.put("useTicketCache", "false"); + AppConfigurationEntry entry = new AppConfigurationEntry(krb5LoginModuleName, + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); + entries.put(name, entry); + } + + /** + * Add an entry to the jaas configuration with the passed in name. The other + * necessary options will be set for you. + * + * @param name The name of the entry (e.g. "Client") + */ + public static void addEntryForTicketCache(String sectionName) { + Map<String, String> options = new HashMap<String, String>(); + options.put("useKeyTab", "false"); + options.put("storeKey", "false"); + options.put("useTicketCache", "true"); + AppConfigurationEntry entry = new AppConfigurationEntry(krb5LoginModuleName, + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options); + entries.put(sectionName, entry); + } + + /** + * Removes the specified entry. + * + * @param name The name of the entry to remove + */ + public static void removeEntry(String name) { + entries.remove(name); + } + + /** + * Clears all entries. + */ + public static void clearEntries() { + entries.clear(); + } + + /** + * Returns the entries map. + * + * @return the entries map + */ + public static Map<String, AppConfigurationEntry> getEntries() { + return entries; + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + return new AppConfigurationEntry[]{entries.get(name)}; + } +} +
