| /* |
| * 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.hadoop.registry.client.impl.zk; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.Lists; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.curator.framework.CuratorFrameworkFactory; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authentication.util.KerberosUtil; |
| import org.apache.hadoop.service.AbstractService; |
| import org.apache.hadoop.service.ServiceStateException; |
| import org.apache.hadoop.util.ZKUtil; |
| import org.apache.zookeeper.Environment; |
| import org.apache.zookeeper.ZooDefs; |
| import org.apache.zookeeper.data.ACL; |
| import org.apache.zookeeper.data.Id; |
| import org.apache.zookeeper.server.auth.DigestAuthenticationProvider; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.security.auth.login.AppConfigurationEntry; |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Locale; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| |
| import static org.apache.hadoop.registry.client.impl.zk.ZookeeperConfigOptions.*; |
| import static org.apache.hadoop.registry.client.api.RegistryConstants.*; |
| |
| /** |
| * Implement the registry security ... a self contained service for |
| * testability. |
| * <p> |
| * This class contains: |
| * <ol> |
| * <li> |
| * The registry security policy implementation, configuration reading, ACL |
| * setup and management |
| * </li> |
| * <li>Lots of static helper methods to aid security setup and debugging</li> |
| * </ol> |
| */ |
| |
| public class RegistrySecurity extends AbstractService { |
| |
| private static final Logger LOG = |
| LoggerFactory.getLogger(RegistrySecurity.class); |
| |
| public static final String E_UNKNOWN_AUTHENTICATION_MECHANISM = |
| "Unknown/unsupported authentication mechanism; "; |
| |
| /** |
| * there's no default user to add with permissions, so it would be |
| * impossible to create nodes with unrestricted user access |
| */ |
| public static final String E_NO_USER_DETERMINED_FOR_ACLS = |
| "No user for ACLs determinable from current user or registry option " |
| + KEY_REGISTRY_USER_ACCOUNTS; |
| |
| /** |
| * Error raised when the registry is tagged as secure but this |
| * process doesn't have hadoop security enabled. |
| */ |
| public static final String E_NO_KERBEROS = |
| "Registry security is enabled -but Hadoop security is not enabled"; |
| |
| /** |
| * Access policy options |
| */ |
| private enum AccessPolicy { |
| anon, sasl, digest |
| } |
| |
| /** |
| * Access mechanism |
| */ |
| private AccessPolicy access; |
| |
| /** |
| * User used for digest auth |
| */ |
| |
| private String digestAuthUser; |
| |
| /** |
| * Password used for digest auth |
| */ |
| |
| private String digestAuthPassword; |
| |
| /** |
| * Auth data used for digest auth |
| */ |
| private byte[] digestAuthData; |
| |
| /** |
| * flag set to true if the registry has security enabled. |
| */ |
| private boolean secureRegistry; |
| |
| /** |
| * An ACL with read-write access for anyone |
| */ |
| public static final ACL ALL_READWRITE_ACCESS = |
| new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE); |
| |
| /** |
| * An ACL with read access for anyone |
| */ |
| public static final ACL ALL_READ_ACCESS = |
| new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE); |
| |
| /** |
| * An ACL list containing the {@link #ALL_READWRITE_ACCESS} entry. |
| * It is copy on write so can be shared without worry |
| */ |
| public static final List<ACL> WorldReadWriteACL; |
| |
| static { |
| List<ACL> acls = new ArrayList<ACL>(); |
| acls.add(ALL_READWRITE_ACCESS); |
| WorldReadWriteACL = new CopyOnWriteArrayList<ACL>(acls); |
| } |
| |
| /** |
| * the list of system ACLs |
| */ |
| private final List<ACL> systemACLs = new ArrayList<ACL>(); |
| |
| private boolean usesRealm = true; |
| |
| /** |
| * A list of digest ACLs which can be added to permissions |
| * —and cleared later. |
| */ |
| private final List<ACL> digestACLs = new ArrayList<ACL>(); |
| |
| /** |
| * the default kerberos realm |
| */ |
| private String kerberosRealm; |
| |
| /** |
| * Client context |
| */ |
| private String jaasClientContext; |
| |
| /** |
| * Client identity |
| */ |
| private String jaasClientIdentity; |
| |
| /** |
| * Create an instance |
| * @param name service name |
| */ |
| public RegistrySecurity(String name) { |
| super(name); |
| } |
| |
| /** |
| * Init the service: this sets up security based on the configuration |
| * @param conf configuration |
| * @throws Exception |
| */ |
| @Override |
| protected void serviceInit(Configuration conf) throws Exception { |
| super.serviceInit(conf); |
| String auth = conf.getTrimmed(KEY_REGISTRY_CLIENT_AUTH, |
| REGISTRY_CLIENT_AUTH_ANONYMOUS); |
| |
| switch (auth) { |
| case REGISTRY_CLIENT_AUTH_KERBEROS: |
| access = AccessPolicy.sasl; |
| break; |
| case REGISTRY_CLIENT_AUTH_DIGEST: |
| access = AccessPolicy.digest; |
| break; |
| case REGISTRY_CLIENT_AUTH_ANONYMOUS: |
| access = AccessPolicy.anon; |
| break; |
| default: |
| throw new ServiceStateException(E_UNKNOWN_AUTHENTICATION_MECHANISM |
| + "\"" + auth + "\""); |
| } |
| initSecurity(); |
| } |
| |
| /** |
| * Init security. |
| * |
| * After this operation, the {@link #systemACLs} list is valid. |
| * @throws IOException |
| */ |
| private void initSecurity() throws IOException { |
| |
| secureRegistry = |
| getConfig().getBoolean(KEY_REGISTRY_SECURE, DEFAULT_REGISTRY_SECURE); |
| systemACLs.clear(); |
| if (secureRegistry) { |
| addSystemACL(ALL_READ_ACCESS); |
| |
| // determine the kerberos realm from JVM and settings |
| kerberosRealm = getConfig().get(KEY_REGISTRY_KERBEROS_REALM, |
| getDefaultRealmInJVM()); |
| |
| // System Accounts |
| String system = getOrFail(KEY_REGISTRY_SYSTEM_ACCOUNTS, |
| DEFAULT_REGISTRY_SYSTEM_ACCOUNTS); |
| usesRealm = system.contains("@"); |
| |
| systemACLs.addAll(buildACLs(system, kerberosRealm, ZooDefs.Perms.ALL)); |
| |
| // user accounts (may be empty, but for digest one user AC must |
| // be built up |
| String user = getConfig().get(KEY_REGISTRY_USER_ACCOUNTS, |
| DEFAULT_REGISTRY_USER_ACCOUNTS); |
| List<ACL> userACLs = buildACLs(user, kerberosRealm, ZooDefs.Perms.ALL); |
| |
| // add self if the current user can be determined |
| ACL self; |
| if (UserGroupInformation.isSecurityEnabled()) { |
| self = createSaslACLFromCurrentUser(ZooDefs.Perms.ALL); |
| if (self != null) { |
| userACLs.add(self); |
| } |
| } |
| |
| // here check for UGI having secure on or digest + ID |
| switch (access) { |
| case sasl: |
| // secure + SASL => has to be authenticated |
| if (!UserGroupInformation.isSecurityEnabled()) { |
| throw new IOException("Kerberos required for secure registry access"); |
| } |
| UserGroupInformation currentUser = |
| UserGroupInformation.getCurrentUser(); |
| jaasClientContext = getOrFail(KEY_REGISTRY_CLIENT_JAAS_CONTEXT, |
| DEFAULT_REGISTRY_CLIENT_JAAS_CONTEXT); |
| jaasClientIdentity = currentUser.getShortUserName(); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Auth is SASL user=\"{}\" JAAS context=\"{}\"", |
| jaasClientIdentity, |
| jaasClientContext); |
| } |
| break; |
| |
| case digest: |
| String id = getOrFail(KEY_REGISTRY_CLIENT_AUTHENTICATION_ID, ""); |
| String pass = getOrFail(KEY_REGISTRY_CLIENT_AUTHENTICATION_PASSWORD, ""); |
| if (userACLs.isEmpty()) { |
| // |
| throw new ServiceStateException(E_NO_USER_DETERMINED_FOR_ACLS); |
| } |
| digest(id, pass); |
| ACL acl = new ACL(ZooDefs.Perms.ALL, toDigestId(id, pass)); |
| userACLs.add(acl); |
| digestAuthUser = id; |
| digestAuthPassword = pass; |
| String authPair = id + ":" + pass; |
| digestAuthData = authPair.getBytes("UTF-8"); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Auth is Digest ACL: {}", aclToString(acl)); |
| } |
| break; |
| |
| case anon: |
| // nothing is needed; account is read only. |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Auth is anonymous"); |
| } |
| userACLs = new ArrayList<ACL>(0); |
| break; |
| } |
| systemACLs.addAll(userACLs); |
| |
| } else { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Registry has no security"); |
| } |
| // wide open cluster, adding system acls |
| systemACLs.addAll(WorldReadWriteACL); |
| } |
| } |
| |
| /** |
| * Add another system ACL |
| * @param acl add ACL |
| */ |
| public void addSystemACL(ACL acl) { |
| systemACLs.add(acl); |
| } |
| |
| /** |
| * Add a digest ACL |
| * @param acl add ACL |
| */ |
| public boolean addDigestACL(ACL acl) { |
| if (secureRegistry) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Added ACL {}", aclToString(acl)); |
| } |
| digestACLs.add(acl); |
| return true; |
| } else { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Ignoring added ACL - registry is insecure{}", |
| aclToString(acl)); |
| } |
| return false; |
| } |
| } |
| |
| /** |
| * Reset the digest ACL list |
| */ |
| public void resetDigestACLs() { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Cleared digest ACLs"); |
| } |
| digestACLs.clear(); |
| } |
| |
| /** |
| * Flag to indicate the cluster is secure |
| * @return true if the config enabled security |
| */ |
| public boolean isSecureRegistry() { |
| return secureRegistry; |
| } |
| |
| /** |
| * Get the system principals |
| * @return the system principals |
| */ |
| public List<ACL> getSystemACLs() { |
| Preconditions.checkNotNull(systemACLs, "registry security is uninitialized"); |
| return Collections.unmodifiableList(systemACLs); |
| } |
| |
| /** |
| * Get all ACLs needed for a client to use when writing to the repo. |
| * That is: system ACLs, its own ACL, any digest ACLs |
| * @return the client ACLs |
| */ |
| public List<ACL> getClientACLs() { |
| List<ACL> clientACLs = new ArrayList<ACL>(systemACLs); |
| clientACLs.addAll(digestACLs); |
| return clientACLs; |
| } |
| |
| /** |
| * Create a SASL ACL for the user |
| * @param perms permissions |
| * @return an ACL for the current user or null if they aren't a kerberos user |
| * @throws IOException |
| */ |
| public ACL createSaslACLFromCurrentUser(int perms) throws IOException { |
| UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); |
| if (currentUser.hasKerberosCredentials()) { |
| return createSaslACL(currentUser, perms); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Given a UGI, create a SASL ACL from it |
| * @param ugi UGI |
| * @param perms permissions |
| * @return a new ACL |
| */ |
| public ACL createSaslACL(UserGroupInformation ugi, int perms) { |
| String userName = null; |
| if (usesRealm) { |
| userName = ugi.getUserName(); |
| } else { |
| userName = ugi.getShortUserName(); |
| } |
| return new ACL(perms, new Id(SCHEME_SASL, userName)); |
| } |
| |
| /** |
| * Get a conf option, throw an exception if it is null/empty |
| * @param key key |
| * @param defval default value |
| * @return the value |
| * @throws IOException if missing |
| */ |
| private String getOrFail(String key, String defval) throws IOException { |
| String val = getConfig().get(key, defval); |
| if (StringUtils.isEmpty(val)) { |
| throw new IOException("Missing value for configuration option " + key); |
| } |
| return val; |
| } |
| |
| /** |
| * Check for an id:password tuple being valid. |
| * This test is stricter than that in {@link DigestAuthenticationProvider}, |
| * which splits the string, but doesn't check the contents of each |
| * half for being non-"". |
| * @param idPasswordPair id:pass pair |
| * @return true if the pass is considered valid. |
| */ |
| public boolean isValid(String idPasswordPair) { |
| String[] parts = idPasswordPair.split(":"); |
| return parts.length == 2 |
| && !StringUtils.isEmpty(parts[0]) |
| && !StringUtils.isEmpty(parts[1]); |
| } |
| |
| /** |
| * Get the derived kerberos realm. |
| * @return this is built from the JVM realm, or the configuration if it |
| * overrides it. If "", it means "don't know". |
| */ |
| public String getKerberosRealm() { |
| return kerberosRealm; |
| } |
| |
| /** |
| * Generate a base-64 encoded digest of the idPasswordPair pair |
| * @param idPasswordPair id:password |
| * @return a string that can be used for authentication |
| */ |
| public String digest(String idPasswordPair) throws IOException { |
| if (StringUtils.isEmpty(idPasswordPair) || !isValid(idPasswordPair)) { |
| throw new IOException("Invalid id:password"); |
| } |
| try { |
| return DigestAuthenticationProvider.generateDigest(idPasswordPair); |
| } catch (NoSuchAlgorithmException e) { |
| // unlikely since it is standard to the JVM, but maybe JCE restrictions |
| // could trigger it |
| throw new IOException(e.toString(), e); |
| } |
| } |
| |
| /** |
| * Generate a base-64 encoded digest of the idPasswordPair pair |
| * @param id ID |
| * @param password pass |
| * @return a string that can be used for authentication |
| * @throws IOException |
| */ |
| public String digest(String id, String password) throws IOException { |
| return digest(id + ":" + password); |
| } |
| |
| /** |
| * Given a digest, create an ID from it |
| * @param digest digest |
| * @return ID |
| */ |
| public Id toDigestId(String digest) { |
| return new Id(SCHEME_DIGEST, digest); |
| } |
| |
| /** |
| * Create a Digest ID from an id:pass pair |
| * @param id ID |
| * @param password password |
| * @return an ID |
| * @throws IOException |
| */ |
| public Id toDigestId(String id, String password) throws IOException { |
| return toDigestId(digest(id, password)); |
| } |
| |
| /** |
| * Split up a list of the form |
| * <code>sasl:mapred@,digest:5f55d66, sasl@yarn@EXAMPLE.COM</code> |
| * into a list of possible ACL values, trimming as needed |
| * |
| * The supplied realm is added to entries where |
| * <ol> |
| * <li>the string begins "sasl:"</li> |
| * <li>the string ends with "@"</li> |
| * </ol> |
| * No attempt is made to validate any of the acl patterns. |
| * |
| * @param aclString list of 0 or more ACLs |
| * @param realm realm to add |
| * @return a list of split and potentially patched ACL pairs. |
| * |
| */ |
| public List<String> splitAclPairs(String aclString, String realm) { |
| List<String> list = Lists.newArrayList( |
| Splitter.on(',').omitEmptyStrings().trimResults() |
| .split(aclString)); |
| ListIterator<String> listIterator = list.listIterator(); |
| while (listIterator.hasNext()) { |
| String next = listIterator.next(); |
| if (next.startsWith(SCHEME_SASL +":") && next.endsWith("@")) { |
| listIterator.set(next + realm); |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Parse a string down to an ID, adding a realm if needed |
| * @param idPair id:data tuple |
| * @param realm realm to add |
| * @return the ID. |
| * @throws IllegalArgumentException if the idPair is invalid |
| */ |
| public Id parse(String idPair, String realm) { |
| int firstColon = idPair.indexOf(':'); |
| int lastColon = idPair.lastIndexOf(':'); |
| if (firstColon == -1 || lastColon == -1 || firstColon != lastColon) { |
| throw new IllegalArgumentException( |
| "ACL '" + idPair + "' not of expected form scheme:id"); |
| } |
| String scheme = idPair.substring(0, firstColon); |
| String id = idPair.substring(firstColon + 1); |
| if (id.endsWith("@")) { |
| Preconditions.checkArgument( |
| StringUtils.isNotEmpty(realm), |
| "@ suffixed account but no realm %s", id); |
| id = id + realm; |
| } |
| return new Id(scheme, id); |
| } |
| |
| /** |
| * Parse the IDs, adding a realm if needed, setting the permissions |
| * @param principalList id string |
| * @param realm realm to add |
| * @param perms permissions |
| * @return the relevant ACLs |
| * @throws IOException |
| */ |
| public List<ACL> buildACLs(String principalList, String realm, int perms) |
| throws IOException { |
| List<String> aclPairs = splitAclPairs(principalList, realm); |
| List<ACL> ids = new ArrayList<ACL>(aclPairs.size()); |
| for (String aclPair : aclPairs) { |
| ACL newAcl = new ACL(); |
| newAcl.setId(parse(aclPair, realm)); |
| newAcl.setPerms(perms); |
| ids.add(newAcl); |
| } |
| return ids; |
| } |
| |
| /** |
| * Parse an ACL list. This includes configuration indirection |
| * {@link ZKUtil#resolveConfIndirection(String)} |
| * @param zkAclConf configuration string |
| * @return an ACL list |
| * @throws IOException on a bad ACL parse |
| */ |
| public List<ACL> parseACLs(String zkAclConf) throws IOException { |
| try { |
| return ZKUtil.parseACLs(ZKUtil.resolveConfIndirection(zkAclConf)); |
| } catch (ZKUtil.BadAclFormatException e) { |
| throw new IOException("Parsing " + zkAclConf + " :" + e, e); |
| } |
| } |
| |
| /** |
| * Get the appropriate Kerberos Auth module for JAAS entries |
| * for this JVM. |
| * @return a JVM-specific kerberos login module classname. |
| */ |
| public static String getKerberosAuthModuleForJVM() { |
| if (System.getProperty("java.vendor").contains("IBM")) { |
| return "com.ibm.security.auth.module.Krb5LoginModule"; |
| } else { |
| return "com.sun.security.auth.module.Krb5LoginModule"; |
| } |
| } |
| |
| /** |
| * JAAS template: {@value} |
| * Note the semicolon on the last entry |
| */ |
| private static final String JAAS_ENTRY = |
| "%s { %n" |
| + " %s required%n" |
| // kerberos module |
| + " keyTab=\"%s\"%n" |
| + " debug=true%n" |
| + " principal=\"%s\"%n" |
| + " useKeyTab=true%n" |
| + " useTicketCache=false%n" |
| + " doNotPrompt=true%n" |
| + " storeKey=true;%n" |
| + "}; %n" |
| ; |
| |
| /** |
| * Create a JAAS entry for insertion |
| * @param context context of the entry |
| * @param principal kerberos principal |
| * @param keytab keytab |
| * @return a context |
| */ |
| public String createJAASEntry( |
| String context, |
| String principal, |
| File keytab) { |
| Preconditions.checkArgument(StringUtils.isNotEmpty(principal), |
| "invalid principal"); |
| Preconditions.checkArgument(StringUtils.isNotEmpty(context), |
| "invalid context"); |
| Preconditions.checkArgument(keytab != null && keytab.isFile(), |
| "Keytab null or missing: "); |
| String keytabpath = keytab.getAbsolutePath(); |
| // fix up for windows; no-op on unix |
| keytabpath = keytabpath.replace('\\', '/'); |
| return String.format( |
| Locale.ENGLISH, |
| JAAS_ENTRY, |
| context, |
| getKerberosAuthModuleForJVM(), |
| keytabpath, |
| principal); |
| } |
| |
| /** |
| * Bind the JVM JAS setting to the specified JAAS file. |
| * |
| * <b>Important:</b> once a file has been loaded the JVM doesn't pick up |
| * changes |
| * @param jaasFile the JAAS file |
| */ |
| public static void bindJVMtoJAASFile(File jaasFile) { |
| String path = jaasFile.getAbsolutePath(); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Binding {} to {}", Environment.JAAS_CONF_KEY, path); |
| } |
| System.setProperty(Environment.JAAS_CONF_KEY, path); |
| } |
| |
| /** |
| * Set the Zookeeper server property |
| * {@link ZookeeperConfigOptions#PROP_ZK_SERVER_SASL_CONTEXT} |
| * to the SASL context. When the ZK server starts, this is the context |
| * which it will read in |
| * @param contextName the name of the context |
| */ |
| public static void bindZKToServerJAASContext(String contextName) { |
| System.setProperty(PROP_ZK_SERVER_SASL_CONTEXT, contextName); |
| } |
| |
| /** |
| * Reset any system properties related to JAAS |
| */ |
| public static void clearJaasSystemProperties() { |
| System.clearProperty(Environment.JAAS_CONF_KEY); |
| } |
| |
| /** |
| * Resolve the context of an entry. This is an effective test of |
| * JAAS setup, because it will relay detected problems up |
| * @param context context name |
| * @return the entry |
| * @throws RuntimeException if there is no context entry found |
| */ |
| public static AppConfigurationEntry[] validateContext(String context) { |
| if (context == null) { |
| throw new RuntimeException("Null context argument"); |
| } |
| if (context.isEmpty()) { |
| throw new RuntimeException("Empty context argument"); |
| } |
| javax.security.auth.login.Configuration configuration = |
| javax.security.auth.login.Configuration.getConfiguration(); |
| AppConfigurationEntry[] entries = |
| configuration.getAppConfigurationEntry(context); |
| if (entries == null) { |
| throw new RuntimeException( |
| String.format("Entry \"%s\" not found; " + |
| "JAAS config = %s", |
| context, |
| describeProperty(Environment.JAAS_CONF_KEY) )); |
| } |
| return entries; |
| } |
| |
| /** |
| * Apply the security environment to this curator instance. This |
| * may include setting up the ZK system properties for SASL |
| * @param builder curator builder |
| */ |
| public void applySecurityEnvironment(CuratorFrameworkFactory.Builder builder) { |
| |
| if (isSecureRegistry()) { |
| switch (access) { |
| case anon: |
| clearZKSaslClientProperties(); |
| break; |
| |
| case digest: |
| // no SASL |
| clearZKSaslClientProperties(); |
| builder.authorization(SCHEME_DIGEST, digestAuthData); |
| break; |
| |
| case sasl: |
| // bind to the current identity and context within the JAAS file |
| setZKSaslClientProperties(jaasClientIdentity, jaasClientContext); |
| } |
| } |
| } |
| |
| /** |
| * Set the client properties. This forces the ZK client into |
| * failing if it can't auth. |
| * <b>Important:</b>This is JVM-wide. |
| * @param username username |
| * @param context login context |
| * @throws RuntimeException if the context cannot be found in the current |
| * JAAS context |
| */ |
| public static void setZKSaslClientProperties(String username, |
| String context) { |
| RegistrySecurity.validateContext(context); |
| enableZookeeperClientSASL(); |
| setSystemPropertyIfUnset(PROP_ZK_SASL_CLIENT_USERNAME, username); |
| setSystemPropertyIfUnset(PROP_ZK_SASL_CLIENT_CONTEXT, context); |
| } |
| |
| private static void setSystemPropertyIfUnset(String name, String value) { |
| String existingValue = System.getProperty(name); |
| if (existingValue == null || existingValue.isEmpty()) { |
| System.setProperty(name, value); |
| } |
| } |
| |
| /** |
| * Clear all the ZK SASL Client properties |
| * <b>Important:</b>This is JVM-wide |
| */ |
| public static void clearZKSaslClientProperties() { |
| disableZookeeperClientSASL(); |
| System.clearProperty(PROP_ZK_SASL_CLIENT_CONTEXT); |
| System.clearProperty(PROP_ZK_SASL_CLIENT_USERNAME); |
| } |
| |
| /** |
| * Turn ZK SASL on |
| * <b>Important:</b>This is JVM-wide |
| */ |
| protected static void enableZookeeperClientSASL() { |
| System.setProperty(PROP_ZK_ENABLE_SASL_CLIENT, "true"); |
| } |
| |
| /** |
| * Force disable ZK SASL bindings. |
| * <b>Important:</b>This is JVM-wide |
| */ |
| public static void disableZookeeperClientSASL() { |
| System.setProperty(ZookeeperConfigOptions.PROP_ZK_ENABLE_SASL_CLIENT, "false"); |
| } |
| |
| /** |
| * Is the system property enabling the SASL client set? |
| * @return true if the SASL client system property is set. |
| */ |
| public static boolean isClientSASLEnabled() { |
| return Boolean.parseBoolean(System.getProperty( |
| ZookeeperConfigOptions.PROP_ZK_ENABLE_SASL_CLIENT, "true")); |
| } |
| |
| /** |
| * Log details about the current Hadoop user at INFO. |
| * Robust against IOEs when trying to get the current user |
| */ |
| public void logCurrentHadoopUser() { |
| try { |
| UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); |
| LOG.info("Current user = {}",currentUser); |
| UserGroupInformation realUser = currentUser.getRealUser(); |
| LOG.info("Real User = {}" , realUser); |
| } catch (IOException e) { |
| LOG.warn("Failed to get current user {}, {}", e); |
| } |
| } |
| |
| /** |
| * Stringify a list of ACLs for logging. Digest ACLs have their |
| * digest values stripped for security. |
| * @param acls ACL list |
| * @return a string for logs, exceptions, ... |
| */ |
| public static String aclsToString(List<ACL> acls) { |
| StringBuilder builder = new StringBuilder(); |
| if (acls == null) { |
| builder.append("null ACL"); |
| } else { |
| builder.append('\n'); |
| for (ACL acl : acls) { |
| builder.append(aclToString(acl)) |
| .append(" "); |
| } |
| } |
| return builder.toString(); |
| } |
| |
| /** |
| * Convert an ACL to a string, with any obfuscation needed |
| * @param acl ACL |
| * @return ACL string value |
| */ |
| public static String aclToString(ACL acl) { |
| return String.format(Locale.ENGLISH, |
| "0x%02x: %s", |
| acl.getPerms(), |
| idToString(acl.getId()) |
| ); |
| } |
| |
| /** |
| * Convert an ID to a string, stripping out all but the first few characters |
| * of any digest auth hash for security reasons |
| * @param id ID |
| * @return a string description of a Zookeeper ID |
| */ |
| public static String idToString(Id id) { |
| String s; |
| if (id.getScheme().equals(SCHEME_DIGEST)) { |
| String ids = id.getId(); |
| int colon = ids.indexOf(':'); |
| if (colon > 0) { |
| ids = ids.substring(colon + 3); |
| } |
| s = SCHEME_DIGEST + ": " + ids; |
| } else { |
| s = id.toString(); |
| } |
| return s; |
| } |
| |
| /** |
| * Build up low-level security diagnostics to aid debugging |
| * @return a string to use in diagnostics |
| */ |
| public String buildSecurityDiagnostics() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append(secureRegistry ? "secure registry; " |
| : "insecure registry; "); |
| builder.append("Curator service access policy: ").append(access); |
| |
| builder.append("; System ACLs: ").append(aclsToString(systemACLs)); |
| builder.append("User: ").append(UgiInfo.fromCurrentUser()); |
| builder.append("; Kerberos Realm: ").append(kerberosRealm); |
| builder.append(describeProperty(Environment.JAAS_CONF_KEY)); |
| String sasl = |
| System.getProperty(PROP_ZK_ENABLE_SASL_CLIENT, |
| DEFAULT_ZK_ENABLE_SASL_CLIENT); |
| boolean saslEnabled = Boolean.parseBoolean(sasl); |
| builder.append(describeProperty(PROP_ZK_ENABLE_SASL_CLIENT, |
| DEFAULT_ZK_ENABLE_SASL_CLIENT)); |
| if (saslEnabled) { |
| builder.append("; JAAS Client Identity") |
| .append("=") |
| .append(jaasClientIdentity) |
| .append("; "); |
| builder.append(KEY_REGISTRY_CLIENT_JAAS_CONTEXT) |
| .append("=") |
| .append(jaasClientContext) |
| .append("; "); |
| builder.append(describeProperty(PROP_ZK_SASL_CLIENT_USERNAME)); |
| builder.append(describeProperty(PROP_ZK_SASL_CLIENT_CONTEXT)); |
| } |
| builder.append(describeProperty(PROP_ZK_ALLOW_FAILED_SASL_CLIENTS, |
| "(undefined but defaults to true)")); |
| builder.append(describeProperty( |
| PROP_ZK_SERVER_MAINTAIN_CONNECTION_DESPITE_SASL_FAILURE)); |
| return builder.toString(); |
| } |
| |
| private static String describeProperty(String name) { |
| return describeProperty(name, "(undefined)"); |
| } |
| |
| private static String describeProperty(String name, String def) { |
| return "; " + name + "=" + System.getProperty(name, def); |
| } |
| |
| /** |
| * Get the default kerberos realm —returning "" if there |
| * is no realm or other problem |
| * @return the default realm of the system if it |
| * could be determined |
| */ |
| public static String getDefaultRealmInJVM() { |
| try { |
| return KerberosUtil.getDefaultRealm(); |
| // JDK7 |
| } catch (ClassNotFoundException ignored) { |
| // ignored |
| } catch (NoSuchMethodException ignored) { |
| // ignored |
| } catch (IllegalAccessException ignored) { |
| // ignored |
| } catch (InvocationTargetException ignored) { |
| // ignored |
| } |
| return ""; |
| } |
| |
| /** |
| * Create an ACL For a user. |
| * @param ugi User identity |
| * @return the ACL For the specified user. Ifthe username doesn't end |
| * in "@" then the realm is added |
| */ |
| public ACL createACLForUser(UserGroupInformation ugi, int perms) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Creating ACL For ", new UgiInfo(ugi)); |
| } |
| if (!secureRegistry) { |
| return ALL_READWRITE_ACCESS; |
| } else { |
| return createACLfromUsername(ugi.getUserName(), perms); |
| } |
| } |
| |
| /** |
| * Given a user name (short or long), create a SASL ACL |
| * @param username user name; if it doesn't contain an "@" symbol, the |
| * service's kerberos realm is added |
| * @param perms permissions |
| * @return an ACL for the user |
| */ |
| public ACL createACLfromUsername(String username, int perms) { |
| if (usesRealm && !username.contains("@")) { |
| username = username + "@" + kerberosRealm; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("Appending kerberos realm to make {}", username); |
| } |
| } |
| return new ACL(perms, new Id(SCHEME_SASL, username)); |
| } |
| |
| /** |
| * On demand string-ifier for UGI with extra details |
| */ |
| public static class UgiInfo { |
| |
| public static UgiInfo fromCurrentUser() { |
| try { |
| return new UgiInfo(UserGroupInformation.getCurrentUser()); |
| } catch (IOException e) { |
| LOG.info("Failed to get current user {}", e, e); |
| return new UgiInfo(null); |
| } |
| } |
| |
| private final UserGroupInformation ugi; |
| |
| public UgiInfo(UserGroupInformation ugi) { |
| this.ugi = ugi; |
| } |
| |
| @Override |
| public String toString() { |
| if (ugi==null) { |
| return "(null ugi)"; |
| } |
| StringBuilder builder = new StringBuilder(); |
| builder.append(ugi.getUserName()).append(": "); |
| builder.append(ugi.toString()); |
| builder.append(" hasKerberosCredentials=").append( |
| ugi.hasKerberosCredentials()); |
| builder.append(" isFromKeytab=").append(ugi.isFromKeytab()); |
| builder.append(" kerberos is enabled in Hadoop =").append(UserGroupInformation.isSecurityEnabled()); |
| return builder.toString(); |
| } |
| |
| } |
| |
| /** |
| * on-demand stringifier for a list of ACLs |
| */ |
| public static class AclListInfo { |
| public final List<ACL> acls; |
| |
| public AclListInfo(List<ACL> acls) { |
| this.acls = acls; |
| } |
| |
| @Override |
| public String toString() { |
| return aclsToString(acls); |
| } |
| } |
| } |