diff --git a/HowToKerberize.md b/HowToKerberize.md index d1176c8cf..b876cab19 100644 --- a/HowToKerberize.md +++ b/HowToKerberize.md @@ -24,75 +24,65 @@ In addition, because Kerberos authentication requires a delegation-token to prox * Zookeeper to store delegation-token (Recommended) ### Configuration - -Waggle Dance does not read Hadoop's `core-site.xml` so a general property providing Kerberos auth should be added to -the Hive configuration file `hive-site.xml`: +Waggle Dance `waggle-dance-server.yml` example: ``` - - hadoop.security.authentication - KERBEROS - +port: 9083 +verbose: true +#database-resolution: MANUAL +database-resolution: PREFIXED +yaml-storage: + overwrite-config-on-shutdown: false +logging: + config: file:/path/to/log4j2.xml +configuration-properties: + hadoop.security.authentication: KERBEROS + hive.metastore.sasl.enabled: true + hive.metastore.kerberos.principal: hive/_HOST@EXAMPLE.COM + hive.metastore.kerberos.keytab.file: /path/to/hive.keytab + hive.cluster.delegation.token.store.class: org.apache.hadoop.hive.thrift.ZooKeeperTokenStore + hive.cluster.delegation.token.store.zookeeper.connectString: zz1:2181,zz2:2181,zz3:2181 + hive.cluster.delegation.token.store.zookeeper.znode: /hive/cluster/wd_delegation + hive.server2.authentication: KERBEROS + hive.server2.authentication.kerberos.principal: hive/_HOST@EXAMPLE.COM + hive.server2.authentication.kerberos.keytab: /path/to/hive.keytab + hive.server2.authentication.client.kerberos.principal: hive/_HOST@EXAMPLE.COM + hadoop.kerberos.keytab.login.autorenewal.enabled : true + hadoop.proxyuser.hive.users: '*' + hadoop.proxyuser.hive.hosts: '*' ``` - -Waggle Dance also needs a keytab file to communicate with the Metastore so the following properties should be present: +Waggle Dance `waggle-dance-federation.yml` example: ``` - - hive.metastore.sasl.enabled - true - - - hive.metastore.kerberos.principal - hive/_HOST@YOUR_REALM.COM - - - hive.metastore.kerberos.keytab.file - /etc/hive.keytab - +primary-meta-store: + database-prefix: '' + name: local + remote-meta-store-uris: thrift://ms1:9083 + access-control-type: READ_AND_WRITE_AND_CREATE + impersonation-enabled: true +federated-meta-stores: +- remote-meta-store-uris: thrift://ms2:9083 + database-prefix: dw_ + name: remote + impersonation-enabled: true + access-control-type: READ_AND_WRITE_ON_DATABASE_WHITELIST + writable-database-white-list: + - .* ``` -In addition, all metastores need to use the Zookeeper shared token: +In start shell , add jvm properties maybe useful. ``` - - hive.cluster.delegation.token.store.class - org.apache.hadoop.hive.thrift.ZooKeeperTokenStore - - - hive.cluster.delegation.token.store.zookeeper.connectString - zk1:2181,zk2:2181,zk3:2181 - - - hive.cluster.delegation.token.store.zookeeper.znode - /hive/token - +-Djavax.security.auth.useSubjectCredsOnly=false ``` -If you are intending to use a Beeline client, the following properties may be valuable: +Connect to Waggle Dance via beeline, change ` hive.metastore.uris` in Hive configuration file `hive-site.xml`: ``` - hive.server2.transport.mode - http - - - hive.server2.authentication - KERBEROS - - - hive.server2.authentication.kerberos.principal - hive/_HOST@YOUR_REALM.COM - - - hive.server2.authentication.kerberos.keytab - /etc/hive.keytab - - - hive.server2.enable.doAs - false + hive.metastore.uris + thrift://wd:9083 ``` - ### Running Waggle Dance should be started by a privileged user with a fresh keytab. diff --git a/README.md b/README.md index 4757b33e2..eadae7943 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,7 @@ The table below describes all the available configuration values for Waggle Danc | `primary-meta-store.name` | Yes | Database name that uniquely identifies this metastore. Used internally. Cannot be empty. | | `primary-meta-store.database-prefix` | No | Prefix used to access the primary metastore and differentiate databases in it from databases in another metastore. The default prefix (i.e. if this value isn't explicitly set) is empty string.| | `primary-meta-store.access-control-type` | No | Sets how the client access controls should be handled. Default is `READ_ONLY` Other options `READ_AND_WRITE_AND_CREATE`, `READ_AND_WRITE_ON_DATABASE_WHITELIST` and `READ_AND_WRITE_AND_CREATE_ON_DATABASE_WHITELIST` see Access Control section below. | +| `primary-meta-store.impersonation-enabled` | No | Enable metastore end-user impersonation.| | `primary-meta-store.writable-database-white-list` | No | White-list of databases used to verify write access used in conjunction with `primary-meta-store.access-control-type`. The list of databases should be listed without any `primary-meta-store.database-prefix`. This property supports both full database names and (case-insensitive) [Java RegEx patterns](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html).| | `primary-meta-store.metastore-tunnel` | No | See metastore tunnel configuration values below. | | `primary-meta-store.latency` | No | Indicates the acceptable slowness of the metastore in **milliseconds** for increasing the default connection timeout. Default latency is `0` and should be changed if the metastore is particularly slow. If you get an error saying that results were omitted because the metastore was slow, consider changing the latency to a higher number.| @@ -168,6 +169,7 @@ The table below describes all the available configuration values for Waggle Danc | `federated-meta-stores` | No | Possible empty list of read only federated metastores. | | `federated-meta-stores[n].remote-meta-store-uris` | Yes | Thrift URIs of the federated read-only metastore. | | `federated-meta-stores[n].name` | Yes | Name that uniquely identifies this metastore. Used internally. Cannot be empty. | +| `federated-meta-stores[n].impersonation-enabled` | No | Enable metastore end-user impersonation.| | `federated-meta-stores[n].database-prefix` | No | Prefix used to access this particular metastore and differentiate databases in it from databases in another metastore. Typically used if databases have the same name across metastores but federated access to them is still needed. The default prefix (i.e. if this value isn't explicitly set) is {federated-meta-stores[n].name} lowercased and postfixed with an underscore. For example if the metastore name was configured as "waggle" and no database prefix was provided but `PREFIXED` database resolution was used then the value of `database-prefix` would be "waggle_". | | `federated-meta-stores[n].metastore-tunnel` | No | See metastore tunnel configuration values below. | | `federated-meta-stores[n].latency` | No | Indicates the acceptable slowness of the metastore in **milliseconds** for increasing the default connection timeout. Default latency is `0` and should be changed if the metastore is particularly slow. If you get an error saying that results were omitted because the metastore was slow, consider changing the latency to a higher number.| diff --git a/kerberos-process.png b/kerberos-process.png index 992c8ee10..d4974533a 100644 Binary files a/kerberos-process.png and b/kerberos-process.png differ diff --git a/waggle-dance-api/src/main/java/com/hotels/bdp/waggledance/api/model/AbstractMetaStore.java b/waggle-dance-api/src/main/java/com/hotels/bdp/waggledance/api/model/AbstractMetaStore.java index fdc91fa88..4e8bc71e0 100644 --- a/waggle-dance-api/src/main/java/com/hotels/bdp/waggledance/api/model/AbstractMetaStore.java +++ b/waggle-dance-api/src/main/java/com/hotels/bdp/waggledance/api/model/AbstractMetaStore.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ public abstract class AbstractMetaStore { private transient @JsonProperty @NotNull MetaStoreStatus status = MetaStoreStatus.UNKNOWN; private long latency = 0; private transient @JsonIgnore HashBiMap databaseNameBiMapping = HashBiMap.create(); - + private boolean impersonationEnabled; public AbstractMetaStore(String name, String remoteMetaStoreUris, AccessControlType accessControlType) { this.name = name; this.remoteMetaStoreUris = remoteMetaStoreUris; @@ -211,6 +211,14 @@ public void setStatus(MetaStoreStatus status) { this.status = status; } + public boolean isImpersonationEnabled() { + return impersonationEnabled; + } + + public void setImpersonationEnabled(boolean impersonationEnabled) { + this.impersonationEnabled = impersonationEnabled; + } + @Override public int hashCode() { return Objects.hashCode(name); @@ -242,5 +250,4 @@ public String toString() { .add("status", status) .toString(); } - } diff --git a/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/FederatedMetaStoreTest.java b/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/FederatedMetaStoreTest.java index 86048bb53..ec4b9cf98 100644 --- a/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/FederatedMetaStoreTest.java +++ b/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/FederatedMetaStoreTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2021 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ public void nullDatabasePrefix() { @Test public void toJson() throws Exception { - String expected = "{\"accessControlType\":\"READ_ONLY\",\"connectionType\":\"DIRECT\",\"databaseNameMapping\":{},\"databasePrefix\":\"name_\",\"federationType\":\"FEDERATED\",\"hiveMetastoreFilterHook\":null,\"latency\":0,\"mappedDatabases\":null,\"mappedTables\":null,\"metastoreTunnel\":null,\"name\":\"name\",\"remoteMetaStoreUris\":\"uri\",\"status\":\"UNKNOWN\",\"writableDatabaseWhiteList\":[]}"; + String expected = "{\"accessControlType\":\"READ_ONLY\",\"connectionType\":\"DIRECT\",\"databaseNameMapping\":{},\"databasePrefix\":\"name_\",\"federationType\":\"FEDERATED\",\"hiveMetastoreFilterHook\":null,\"impersonationEnabled\":false,\"latency\":0,\"mappedDatabases\":null,\"mappedTables\":null,\"metastoreTunnel\":null,\"name\":\"name\",\"remoteMetaStoreUris\":\"uri\",\"status\":\"UNKNOWN\",\"writableDatabaseWhiteList\":[]}"; ObjectMapper mapper = new ObjectMapper(); // Sorting to get deterministic test behaviour mapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); diff --git a/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/PrimaryMetaStoreTest.java b/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/PrimaryMetaStoreTest.java index 8c1634eb6..feb719680 100644 --- a/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/PrimaryMetaStoreTest.java +++ b/waggle-dance-api/src/test/java/com/hotels/bdp/waggledance/api/model/PrimaryMetaStoreTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2021 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,7 +89,7 @@ public void nonEmptyDatabasePrefix() { @Test public void toJson() throws Exception { - String expected = "{\"accessControlType\":\"READ_ONLY\",\"connectionType\":\"DIRECT\",\"databaseNameMapping\":{},\"databasePrefix\":\"\",\"federationType\":\"PRIMARY\",\"hiveMetastoreFilterHook\":null,\"latency\":0,\"mappedDatabases\":null,\"mappedTables\":null,\"metastoreTunnel\":null,\"name\":\"name\",\"remoteMetaStoreUris\":\"uri\",\"status\":\"UNKNOWN\",\"writableDatabaseWhiteList\":[]}"; + String expected = "{\"accessControlType\":\"READ_ONLY\",\"connectionType\":\"DIRECT\",\"databaseNameMapping\":{},\"databasePrefix\":\"\",\"federationType\":\"PRIMARY\",\"hiveMetastoreFilterHook\":null,\"impersonationEnabled\":false,\"latency\":0,\"mappedDatabases\":null,\"mappedTables\":null,\"metastoreTunnel\":null,\"name\":\"name\",\"remoteMetaStoreUris\":\"uri\",\"status\":\"UNKNOWN\",\"writableDatabaseWhiteList\":[]}"; ObjectMapper mapper = new ObjectMapper(); // Sorting to get deterministic test behaviour mapper.enable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY); diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/CloseableThriftHiveMetastoreIfaceClientFactory.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/CloseableThriftHiveMetastoreIfaceClientFactory.java index ca62e4063..81c4f7aba 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/CloseableThriftHiveMetastoreIfaceClientFactory.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/CloseableThriftHiveMetastoreIfaceClientFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import com.hotels.bdp.waggledance.api.model.AbstractMetaStore; import com.hotels.bdp.waggledance.client.tunnelling.TunnelingMetaStoreClientFactory; import com.hotels.bdp.waggledance.conf.WaggleDanceConfiguration; +import com.hotels.bdp.waggledance.context.CommonBeans; import com.hotels.hcommon.hive.metastore.conf.HiveConfFactory; import com.hotels.hcommon.hive.metastore.util.MetaStoreUriNormaliser; @@ -66,6 +67,8 @@ private CloseableThriftHiveMetastoreIface newHiveInstance( connectionTimeout, waggleDanceConfiguration.getConfigurationProperties()); } properties.put(ConfVars.METASTOREURIS.varname, uris); + properties.put(CommonBeans.IMPERSONATION_ENABLED_KEY, + String.valueOf(metaStore.isImpersonationEnabled())); HiveConfFactory confFactory = new HiveConfFactory(Collections.emptyList(), properties); return defaultMetaStoreClientFactory .newInstance(confFactory.newInstance(), "waggledance-" + name, DEFAULT_CLIENT_FACTORY_RECONNECTION_RETRY, diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactory.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactory.java index 4b6986048..a111677a1 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactory.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/DefaultMetaStoreClientFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,13 @@ */ package com.hotels.bdp.waggledance.client; -import java.io.IOException; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; -import java.lang.reflect.UndeclaredThrowableException; import java.util.List; import org.apache.hadoop.hive.conf.HiveConf; -import org.apache.hadoop.hive.metastore.utils.SecurityUtils; -import org.apache.hadoop.security.UserGroupInformation; import org.apache.thrift.transport.TTransportException; import lombok.extern.log4j.Log4j2; @@ -34,7 +30,6 @@ import com.google.common.collect.Lists; import com.hotels.bdp.waggledance.client.compatibility.HiveCompatibleThriftHiveMetastoreIfaceFactory; -import com.hotels.bdp.waggledance.server.TokenWrappingHMSHandler; import com.hotels.hcommon.hive.metastore.exception.MetastoreUnavailableException; @@ -140,76 +135,6 @@ private void reconnectIfDisconnected() { } - @Log4j2 - private static class SaslMetastoreClientHander implements InvocationHandler { - - private final CloseableThriftHiveMetastoreIface baseHandler; - private final ThriftMetastoreClientManager clientManager; - private final String tokenSignature = "WAGGLEDANCETOKEN"; - - private String delegationToken; - - public static CloseableThriftHiveMetastoreIface newProxyInstance( - CloseableThriftHiveMetastoreIface baseHandler, - ThriftMetastoreClientManager clientManager) { - return (CloseableThriftHiveMetastoreIface) Proxy.newProxyInstance(SaslMetastoreClientHander.class.getClassLoader(), - INTERFACES, new SaslMetastoreClientHander(baseHandler, clientManager)); - } - - private SaslMetastoreClientHander( - CloseableThriftHiveMetastoreIface handler, - ThriftMetastoreClientManager clientManager) { - this.baseHandler = handler; - this.clientManager = clientManager; - } - - @SuppressWarnings("unchecked") - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - try { - switch (method.getName()) { - case "get_delegation_token": - try { - clientManager.open(); - Object token = method.invoke(baseHandler, args); - this.delegationToken = (String) token; - clientManager.close(); - setTokenStr2Ugi(UserGroupInformation.getCurrentUser(), (String) token); - clientManager.open(); - return token; - } catch (IOException e) { - throw new MetastoreUnavailableException("Couldn't setup delegation token in the ugi: ", e); - } - default: - genToken(); - return method.invoke(baseHandler, args); - } - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } catch (UndeclaredThrowableException e) { - throw e.getCause(); - } - } - - private void genToken() throws Throwable { - UserGroupInformation currUser = null; - if (delegationToken == null && (currUser = UserGroupInformation.getCurrentUser()) - != UserGroupInformation.getLoginUser()) { - - log.info("set {} delegation token", currUser.getShortUserName()); - String token = TokenWrappingHMSHandler.getToken(); - setTokenStr2Ugi(currUser, token); - delegationToken = token; - clientManager.close(); - } - } - - private void setTokenStr2Ugi(UserGroupInformation currUser, String token) throws IOException { - String newTokenSignature = clientManager.generateNewTokenSignature(tokenSignature); - SecurityUtils.setTokenStr(currUser, token, newTokenSignature); - } - } - /* * (non-Javadoc) * @see com.hotels.bdp.waggledance.client.MetaStoreClientFactoryI#newInstance(org.apache.hadoop.hive.conf.HiveConf, @@ -231,17 +156,9 @@ CloseableThriftHiveMetastoreIface newInstance( int reconnectionRetries, ThriftMetastoreClientManager base) { ReconnectingMetastoreClientInvocationHandler reconnectingHandler = new ReconnectingMetastoreClientInvocationHandler( - name, reconnectionRetries, base); - if (base.isSaslEnabled()) { - CloseableThriftHiveMetastoreIface ifaceReconnectingHandler = (CloseableThriftHiveMetastoreIface) Proxy - .newProxyInstance(getClass().getClassLoader(), INTERFACES, reconnectingHandler); - // wrapping the SaslMetastoreClientHander to handle delegation token if using sasl - return SaslMetastoreClientHander.newProxyInstance(ifaceReconnectingHandler, base); - } else { - return (CloseableThriftHiveMetastoreIface) Proxy - .newProxyInstance(getClass().getClassLoader(), INTERFACES, reconnectingHandler); - } - + name, reconnectionRetries, base); + return (CloseableThriftHiveMetastoreIface) Proxy.newProxyInstance(getClass().getClassLoader(), + INTERFACES, reconnectingHandler); } } \ No newline at end of file diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/ThriftMetastoreClientManager.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/ThriftMetastoreClientManager.java index 6ff94e8d0..171484832 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/ThriftMetastoreClientManager.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/client/ThriftMetastoreClientManager.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,15 @@ */ package com.hotels.bdp.waggledance.client; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import java.io.Closeable; import java.io.IOException; import java.net.URI; +import java.time.Duration; +import java.util.Objects; import java.util.Random; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -27,8 +32,8 @@ import org.apache.hadoop.hive.conf.HiveConfUtil; import org.apache.hadoop.hive.metastore.api.MetaException; import org.apache.hadoop.hive.metastore.api.ThriftHiveMetastore; +import org.apache.hadoop.hive.metastore.api.ThriftHiveMetastore.Iface; import org.apache.hadoop.hive.metastore.utils.MetaStoreUtils; -import org.apache.hadoop.hive.metastore.utils.SecurityUtils; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.StringUtils; import org.apache.hive.service.auth.KerberosSaslHelper; @@ -42,7 +47,12 @@ import lombok.extern.log4j.Log4j2; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + import com.hotels.bdp.waggledance.client.compatibility.HiveCompatibleThriftHiveMetastoreIfaceFactory; +import com.hotels.bdp.waggledance.context.CommonBeans; @Log4j2 class ThriftMetastoreClientManager implements Closeable { @@ -61,6 +71,13 @@ class ThriftMetastoreClientManager implements Closeable { private final int connectionTimeout; private final String msUri; + private final boolean impersonationEnabled; + private static final Duration delegationTokenCacheTtl = Duration.ofHours(1); // The default lifetime in Hive is 7 days (metastore.cluster.delegation.token.max-lifetime) + private static final long delegationTokenCacheMaximumSize = 1000; + private static final LoadingCache delegationTokenCache = CacheBuilder.newBuilder() + .expireAfterWrite(delegationTokenCacheTtl.toMillis(), MILLISECONDS) + .maximumSize(delegationTokenCacheMaximumSize) + .build(CacheLoader.from(ThriftMetastoreClientManager::loadDelegationToken)); ThriftMetastoreClientManager( HiveConf conf, @@ -70,6 +87,7 @@ class ThriftMetastoreClientManager implements Closeable { this.hiveCompatibleThriftHiveMetastoreIfaceFactory = hiveCompatibleThriftHiveMetastoreIfaceFactory; this.connectionTimeout = connectionTimeout; msUri = conf.getVar(ConfVars.METASTOREURIS); + impersonationEnabled = conf.getBoolean(CommonBeans.IMPERSONATION_ENABLED_KEY,false); if (HiveConfUtil.isEmbeddedMetaStore(msUri)) { throw new RuntimeException("You can't waggle an embedded metastore"); @@ -105,6 +123,44 @@ class ThriftMetastoreClientManager implements Closeable { } } + private static String loadDelegationToken(DelegationTokenKey key) { + try { + return key.client.get_delegation_token(key.username, key.username); + } catch (TException e) { + throw new RuntimeException(e); + } + } + + private static class DelegationTokenKey{ + String msUri; + String username; + ThriftHiveMetastore.Iface client; + + public DelegationTokenKey(String msUri, String username, Iface client) { + this.msUri = msUri; + this.username = username; + this.client = client; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + DelegationTokenKey that = (DelegationTokenKey) o; + return Objects.equals(msUri, that.msUri) && Objects.equals(username, + that.username); + } + + @Override + public int hashCode() { + return Objects.hash(msUri, username); + } + } + void open() { open(null); } @@ -113,6 +169,21 @@ void open(HiveUgiArgs ugiArgs) { if (isConnected) { return; } + createMetastoreClientAndOpen(null, ugiArgs); + if (impersonationEnabled) { + try { + String userName = UserGroupInformation.getCurrentUser().getShortUserName(); + DelegationTokenKey key = new DelegationTokenKey(msUri, userName, client); + String delegationToken = delegationTokenCache.get(key); + close(); + createMetastoreClientAndOpen(delegationToken, ugiArgs); + } catch (IOException | ExecutionException e) { + throw new RuntimeException(e); + } + } + } + + void createMetastoreClientAndOpen(String delegationToken, HiveUgiArgs ugiArgs) { TException te = null; boolean useSasl = conf.getBoolVar(ConfVars.METASTORE_USE_THRIFT_SASL); boolean useSsl = conf.getBoolVar(ConfVars.HIVE_METASTORE_USE_SSL); @@ -135,14 +206,14 @@ void open(HiveUgiArgs ugiArgs) { // this should happen on the map/reduce tasks if the client added the // tokens into hadoop's credential store in the front end during job // submission. - String tokenSig = conf.getVar(ConfVars.METASTORE_TOKEN_SIGNATURE); +// String tokenSig = conf.getVar(ConfVars.METASTORE_TOKEN_SIGNATURE); // tokenSig could be null - String tokenStrForm = SecurityUtils.getTokenStrForm(tokenSig); - if (tokenStrForm != null) { + if (impersonationEnabled && delegationToken != null) { // authenticate using delegation tokens via the "DIGEST" mechanism transport = KerberosSaslHelper - .getTokenTransport(tokenStrForm, store.getHost(), transport, - MetaStoreUtils.getMetaStoreSaslProperties(conf, useSsl)); + .getTokenTransport(delegationToken, + store.getHost(), transport, + MetaStoreUtils.getMetaStoreSaslProperties(conf, useSsl)); } else { String principalConfig = conf.getVar(ConfVars.METASTORE_KERBEROS_PRINCIPAL); transport = KerberosSaslHelper diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/context/CommonBeans.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/context/CommonBeans.java index 98be2ca65..83a4e58d0 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/context/CommonBeans.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/context/CommonBeans.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,8 @@ @org.springframework.context.annotation.Configuration public class CommonBeans { + public final static String IMPERSONATION_ENABLED_KEY = "hive.metastore.thrift.impersonation.enabled"; + @Bean public HiveConf hiveConf(WaggleDanceConfiguration waggleDanceConfiguration) { Map confProps = waggleDanceConfiguration.getConfigurationProperties(); diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandler.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandler.java index 842bbd9fb..770d1c855 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandler.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1341,8 +1341,16 @@ public GrantRevokePrivilegeResponse grant_revoke_privileges(GrantRevokePrivilege public GrantRevokePrivilegeResponse refresh_privileges(HiveObjectRef hiveObjectRef, String authorizer, GrantRevokePrivilegeRequest grantRevokePrivilegeRequest) throws MetaException, TException { DatabaseMapping databaseMapping = checkWritePermissions(hiveObjectRef.getDbName()); + PrivilegeBag privilegeBag = grantRevokePrivilegeRequest.getPrivileges(); + if (privilegeBag != null && privilegeBag.getPrivileges() != null) { + for (HiveObjectPrivilege hiveObjectPrivilege : privilegeBag.getPrivileges()) { + if (hiveObjectPrivilege.getHiveObject() != null) { + databaseMapping.transformInboundHiveObjectRef(hiveObjectPrivilege.getHiveObject()); + } + } + } return databaseMapping.getClient().refresh_privileges(databaseMapping.transformInboundHiveObjectRef(hiveObjectRef), - authorizer, grantRevokePrivilegeRequest); + authorizer, databaseMapping.transformInboundGrantRevokePrivilegesRequest(grantRevokePrivilegeRequest)); } private DatabaseMapping checkWritePermissionsForPrivileges(PrivilegeBag privileges) throws NoSuchObjectException { @@ -1369,19 +1377,48 @@ public List set_ugi(String user_name, List group_names) throws M @Loggable(value = Loggable.DEBUG, skipResult = true, name = INVOCATION_LOG_NAME) public String get_delegation_token(String token_owner, String renewer_kerberos_principal_name) throws MetaException, TException { - return getPrimaryClient().get_delegation_token(token_owner, renewer_kerberos_principal_name); + try { + return MetaStoreProxyServer.getSaslServerAndMDT().getDelegationTokenManager() + .getDelegationToken(token_owner, renewer_kerberos_principal_name, + MetaStoreProxyServer.getIPAddress()); + } catch (IOException | InterruptedException e) { + throw new MetaException(e.getMessage()); + } } @Override @Loggable(value = Loggable.DEBUG, skipResult = true, name = INVOCATION_LOG_NAME) public long renew_delegation_token(String token_str_form) throws MetaException, TException { - return getPrimaryClient().renew_delegation_token(token_str_form); + try { + return MetaStoreProxyServer.getSaslServerAndMDT().getDelegationTokenManager() + .renewDelegationToken(token_str_form); + } catch (IOException e) { + throw new MetaException(e.getMessage()); + } catch (Exception e) { + throw newMetaException(e); + } } @Override @Loggable(value = Loggable.DEBUG, skipResult = true, name = INVOCATION_LOG_NAME) public void cancel_delegation_token(String token_str_form) throws MetaException, TException { - getPrimaryClient().cancel_delegation_token(token_str_form); + try { + MetaStoreProxyServer.getSaslServerAndMDT().getDelegationTokenManager() + .cancelDelegationToken(token_str_form); + } catch (IOException e) { + throw new MetaException(e.getMessage()); + } catch (Exception e) { + throw newMetaException(e); + } + } + + private static MetaException newMetaException(Exception e) { + if (e instanceof MetaException) { + return (MetaException)e; + } + MetaException me = new MetaException(e.toString()); + me.initCause(e); + return me; } @Override diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactory.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactory.java index d1d501ace..36df03275 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactory.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/MetaStoreProxyServer.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/MetaStoreProxyServer.java index 05c7797a3..49408f629 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/MetaStoreProxyServer.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/MetaStoreProxyServer.java @@ -25,6 +25,8 @@ package com.hotels.bdp.waggledance.server; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -40,21 +42,19 @@ import org.apache.hadoop.hive.common.auth.HiveAuthUtils; import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.conf.HiveConf.ConfVars; +import org.apache.hadoop.hive.metastore.HiveMetaStore.HMSHandler; import org.apache.hadoop.hive.metastore.TServerSocketKeepAlive; import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge; import org.apache.hadoop.hive.shims.ShimLoader; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authorize.ProxyUsers; import org.apache.hadoop.util.StringUtils; import org.apache.thrift.TProcessorFactory; import org.apache.thrift.protocol.TBinaryProtocol; -import org.apache.thrift.protocol.TProtocol; -import org.apache.thrift.server.ServerContext; import org.apache.thrift.server.TServer; -import org.apache.thrift.server.TServerEventHandler; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TServerSocket; -import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import org.apache.thrift.transport.TTransportFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -66,8 +66,11 @@ import lombok.extern.log4j.Log4j2; +import com.google.common.annotations.VisibleForTesting; + import com.hotels.bdp.waggledance.conf.WaggleDanceConfiguration; import com.hotels.bdp.waggledance.util.SaslHelper; +import com.hotels.bdp.waggledance.util.SaslHelper.SaslServerAndMDT; @Component @Order(Ordered.HIGHEST_PRECEDENCE) @@ -87,6 +90,9 @@ public class MetaStoreProxyServer implements ApplicationRunner { private final Lock startLock; private final Condition startCondition; private TServer tServer; + private static HadoopThriftAuthBridge.Server saslServer; + private static SaslServerAndMDT saslServerAndMDT; + private static boolean useSasl; @Autowired public MetaStoreProxyServer( @@ -162,7 +168,10 @@ private void startWaggleDance( boolean tcpKeepAlive = hiveConf.getBoolVar(ConfVars.METASTORE_TCP_KEEP_ALIVE); boolean useFramedTransport = hiveConf.getBoolVar(ConfVars.METASTORE_USE_THRIFT_FRAMED_TRANSPORT); boolean useSSL = hiveConf.getBoolVar(ConfVars.HIVE_METASTORE_USE_SSL); - boolean useSASL = hiveConf.getBoolVar(ConfVars.METASTORE_USE_THRIFT_SASL); + useSasl = hiveConf.getBoolVar(ConfVars.METASTORE_USE_THRIFT_SASL); + + //load 'hadoop.proxyuser' configs + ProxyUsers.refreshSuperUserGroupsConfiguration(hiveConf); TServerSocket serverSocket = createServerSocket(useSSL, waggleDanceConfiguration.getPort()); @@ -170,15 +179,14 @@ private void startWaggleDance( serverSocket = new TServerSocketKeepAlive(serverSocket); } - HadoopThriftAuthBridge.Server saslServer = null; - - if(useSASL) { + if(useSasl) { UserGroupInformation.setConfiguration(hiveConf); - saslServer = SaslHelper.createSaslServer(hiveConf); + saslServerAndMDT = SaslHelper.createSaslServer(hiveConf); + saslServer = saslServerAndMDT.getSaslServer(); } - TTransportFactory transFactory = createTTransportFactory(useFramedTransport, useSASL, saslServer); - TProcessorFactory tProcessorFactory = getTProcessorFactory(useSASL, saslServer); + TTransportFactory transFactory = createTTransportFactory(useFramedTransport, useSasl, saslServer); + TProcessorFactory tProcessorFactory = getTProcessorFactory(useSasl, saslServer); log.info("Starting WaggleDance Server"); TThreadPoolServer.Args args = new TThreadPoolServer.Args(serverSocket) @@ -192,28 +200,6 @@ private void startWaggleDance( .requestTimeoutUnit(waggleDanceConfiguration.getThriftServerRequestTimeoutUnit()); tServer = new TThreadPoolServer(args); - if (useSASL){ - TServerEventHandler tServerEventHandler = new TServerEventHandler() { - @Override - public void preServe() { - } - - @Override - public ServerContext createContext(TProtocol tProtocol, TProtocol tProtocol1) { - return null; - } - - @Override - public void deleteContext(ServerContext serverContext, TProtocol tProtocol, TProtocol tProtocol1) { - TokenWrappingHMSHandler.removeToken(); - } - - @Override - public void processContext(ServerContext serverContext, TTransport tTransport, TTransport tTransport1) { - } - }; - tServer.setServerEventHandler(tServerEventHandler); - } log.info("Started the new WaggleDance on port [{}]...", waggleDanceConfiguration.getPort()); log.info("Options.minWorkerThreads = {}", minWorkerThreads); log.info("Options.maxWorkerThreads = {}", maxWorkerThreads); @@ -332,4 +318,31 @@ public void waitUntilStarted(int retries, long waitDelay, TimeUnit waitDelayTime } } + static String getIPAddress() { + if (useSasl) { + if (saslServer != null && saslServer.getRemoteAddress() != null) { + return saslServer.getRemoteAddress().getHostAddress(); + } + } else { + // if kerberos is not enabled + try { + Method method = HMSHandler.class.getDeclaredMethod("getThreadLocalIpAddress", null); + method.setAccessible(true); + return (String) method.invoke(null, null); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + return null; + } + + @VisibleForTesting + public static void setSaslServerAndMDT( + SaslServerAndMDT saslServerAndMDT) { + MetaStoreProxyServer.saslServerAndMDT = saslServerAndMDT; + } + + public static SaslServerAndMDT getSaslServerAndMDT() { + return saslServerAndMDT; + } } diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TSetIpAddressProcessorFactory.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TSetIpAddressProcessorFactory.java index 63b1fe2bc..2172795f0 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TSetIpAddressProcessorFactory.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TSetIpAddressProcessorFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,18 +58,10 @@ public TProcessor getProcessor(TTransport transport) { } CloseableIHMSHandler baseHandler = federatedHMSHandlerFactory.create(); - boolean useSASL = hiveConf.getBoolVar(HiveConf.ConfVars.METASTORE_USE_THRIFT_SASL); - if (useSASL) { - IHMSHandler tokenHandler = TokenWrappingHMSHandler.newProxyInstance(baseHandler, useSASL); - IHMSHandler handler = newRetryingHMSHandler(ExceptionWrappingHMSHandler.newProxyInstance(tokenHandler), hiveConf, - false); - return new TSetIpAddressProcessor<>(handler); - } else { - IHMSHandler handler = newRetryingHMSHandler(ExceptionWrappingHMSHandler.newProxyInstance(baseHandler), hiveConf, - false); - transportMonitor.monitor(transport, baseHandler); - return new TSetIpAddressProcessor<>(handler); - } + IHMSHandler handler = newRetryingHMSHandler(ExceptionWrappingHMSHandler.newProxyInstance(baseHandler), hiveConf, + false); + transportMonitor.monitor(transport, baseHandler); + return new TSetIpAddressProcessor<>(handler); } catch (MetaException | ReflectiveOperationException | RuntimeException e) { throw new RuntimeException("Error creating TProcessor", e); } diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TokenWrappingHMSHandler.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TokenWrappingHMSHandler.java deleted file mode 100644 index 4f0f7c06f..000000000 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/server/TokenWrappingHMSHandler.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (C) 2016-2023 Expedia, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hotels.bdp.waggledance.server; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.lang.reflect.UndeclaredThrowableException; - -import org.apache.hadoop.hive.metastore.IHMSHandler; -import org.apache.hadoop.security.UserGroupInformation; - -import lombok.extern.log4j.Log4j2; - -@Log4j2 -public class TokenWrappingHMSHandler implements InvocationHandler { - - private final IHMSHandler baseHandler; - private final Boolean useSasl; - - private static final ThreadLocal tokens = new ThreadLocal() { - @Override - protected String initialValue() { - return ""; - } - }; - - public static String getToken() { - return tokens.get(); - } - - public static void removeToken() { - tokens.remove(); - } - - public static IHMSHandler newProxyInstance(IHMSHandler baseHandler, boolean useSasl) { - return (IHMSHandler) Proxy.newProxyInstance(TokenWrappingHMSHandler.class.getClassLoader(), - new Class[] { IHMSHandler.class }, new TokenWrappingHMSHandler(baseHandler, useSasl)); - } - - public TokenWrappingHMSHandler(IHMSHandler baseHandler, boolean useSasl) { - this.baseHandler = baseHandler; - this.useSasl = useSasl; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - try { - // We will get the token when proxy user call in the first time. - // Login user must open connect in `TProcessorFactorySaslDecorator#getProcessor` - // so we can reuse this connect to get proxy user delegation token - if (useSasl) { - UserGroupInformation currUser = null; - String token = null; - // if call get_delegation_token , will call it directly and set token to threadlocal - - switch (method.getName()) { - case "get_delegation_token": - token = (String) method.invoke(baseHandler, args); - tokens.set(token); - return token; - case "close": - tokens.remove(); - return method.invoke(baseHandler, args); - default: - if (tokens.get().isEmpty() && (currUser = UserGroupInformation.getCurrentUser()) - != UserGroupInformation.getLoginUser()) { - - String shortName = currUser.getShortUserName(); - token = baseHandler.get_delegation_token(shortName, shortName); - log.info("get delegation token by user {}", shortName); - tokens.set(token); - } - return method.invoke(baseHandler, args); - } - } - return method.invoke(baseHandler, args); - } catch (InvocationTargetException e) { - // Need to unwrap this, so callers get the correct exception thrown by the handler. - throw e.getCause(); - } catch (UndeclaredThrowableException e) { - // Need to unwrap this, so callers get the correct exception thrown by the handler. - throw e.getCause(); - } - - } - -} diff --git a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/util/SaslHelper.java b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/util/SaslHelper.java index 87b07cded..163db0a64 100644 --- a/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/util/SaslHelper.java +++ b/waggle-dance-core/src/main/java/com/hotels/bdp/waggledance/util/SaslHelper.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hadoop.hive.metastore.security.DBTokenStore; import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge; +import org.apache.hadoop.hive.metastore.security.HadoopThriftAuthBridge.Server; import org.apache.hadoop.hive.metastore.security.MetastoreDelegationTokenManager; import org.apache.hadoop.hive.ql.metadata.Hive; import org.apache.hive.service.auth.HiveAuthConstants; @@ -42,7 +43,22 @@ @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class SaslHelper { - public static HadoopThriftAuthBridge.Server createSaslServer(HiveConf conf) throws TTransportException { + public static class SaslServerAndMDT { + + HadoopThriftAuthBridge.Server saslServer; + MetastoreDelegationTokenManager delegationTokenManager; + + public Server getSaslServer() { + return saslServer; + } + + public MetastoreDelegationTokenManager getDelegationTokenManager() { + return delegationTokenManager; + } + } + + public static SaslServerAndMDT createSaslServer(HiveConf conf) throws TTransportException { + SaslServerAndMDT saslServerAndMDT = new SaslServerAndMDT(); HadoopThriftAuthBridge.Server saslServer = null; if (SaslHelper.isSASLWithKerberizedHadoop(conf)) { saslServer = @@ -75,8 +91,11 @@ public static HadoopThriftAuthBridge.Server createSaslServer(HiveConf conf) thro catch (IOException e) { throw new TTransportException("Failed to start token manager", e); } + + saslServerAndMDT.saslServer = saslServer; + saslServerAndMDT.delegationTokenManager = delegationTokenManager; } - return saslServer; + return saslServerAndMDT; } public static boolean isSASLWithKerberizedHadoop(HiveConf hiveconf) { diff --git a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/mapping/service/impl/YamlFederatedMetaStoreStorageTest.java b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/mapping/service/impl/YamlFederatedMetaStoreStorageTest.java index bb99938ae..9a1e7e8a0 100644 --- a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/mapping/service/impl/YamlFederatedMetaStoreStorageTest.java +++ b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/mapping/service/impl/YamlFederatedMetaStoreStorageTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2021 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -214,31 +214,36 @@ public void saveFederationWriteFederations() throws Exception { storage.insert(newFederatedInstance); storage.saveFederation(); List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - assertThat(lines.size(), is(24)); - assertThat(lines.get(0), is("primary-meta-store:")); - assertThat(lines.get(1), is(" access-control-type: READ_ONLY")); - assertThat(lines.get(2), is(" database-prefix: ''")); - assertThat(lines.get(3), is(" latency: 0")); - assertThat(lines.get(4), is(" name: hcom_1")); - assertThat(lines.get(5), is(" remote-meta-store-uris: thrift://localhost:19083")); - assertThat(lines.get(6), is("federated-meta-stores:")); - assertThat(lines.get(7), is("- access-control-type: READ_ONLY")); - assertThat(lines.get(8), is(" database-name-mapping: {}")); - assertThat(lines.get(9), is(" database-prefix: hcom_2_")); - assertThat(lines.get(10), is(" hive-metastore-filter-hook: filter.hook.class")); - assertThat(lines.get(11), is(" latency: 0")); - assertThat(lines.get(12), is(" mapped-databases:")); - assertThat(lines.get(13), is(" - db1")); - assertThat(lines.get(14), is(" - db2")); - assertThat(lines.get(15), is(" mapped-tables:")); - assertThat(lines.get(16), is(" - database: db1")); - assertThat(lines.get(17), is(" mapped-tables:")); - assertThat(lines.get(18), is(" - tbl1")); - assertThat(lines.get(19), is(" - database: db2")); - assertThat(lines.get(20), is(" mapped-tables:")); - assertThat(lines.get(21), is(" - tbl2")); - assertThat(lines.get(22), is(" name: hcom_2")); - assertThat(lines.get(23), is(" remote-meta-store-uris: thrift://localhost:29083")); + assertThat(lines.size(), is(26)); + int i = 0; + while (i < 26) { + assertThat(lines.get(i++), is("primary-meta-store:")); + assertThat(lines.get(i++), is(" access-control-type: READ_ONLY")); + assertThat(lines.get(i++), is(" database-prefix: ''")); + assertThat(lines.get(i++), is(" impersonation-enabled: false")); + assertThat(lines.get(i++), is(" latency: 0")); + assertThat(lines.get(i++), is(" name: hcom_1")); + assertThat(lines.get(i++), is(" remote-meta-store-uris: thrift://localhost:19083")); + assertThat(lines.get(i++), is("federated-meta-stores:")); + assertThat(lines.get(i++), is("- access-control-type: READ_ONLY")); + assertThat(lines.get(i++), is(" database-name-mapping: {}")); + assertThat(lines.get(i++), is(" database-prefix: hcom_2_")); + assertThat(lines.get(i++), is(" hive-metastore-filter-hook: filter.hook.class")); + assertThat(lines.get(i++), is(" impersonation-enabled: false")); + assertThat(lines.get(i++), is(" latency: 0")); + assertThat(lines.get(i++), is(" mapped-databases:")); + assertThat(lines.get(i++), is(" - db1")); + assertThat(lines.get(i++), is(" - db2")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - database: db1")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - tbl1")); + assertThat(lines.get(i++), is(" - database: db2")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - tbl2")); + assertThat(lines.get(i++), is(" name: hcom_2")); + assertThat(lines.get(i++), is(" remote-meta-store-uris: thrift://localhost:29083")); + } } @Test @@ -296,30 +301,35 @@ public void savePrimaryWriteFederations() throws Exception { storage.insert(newFederatedInstance("hcom_2", "thrift://localhost:29083")); storage.saveFederation(); List lines = Files.readAllLines(file.toPath(), StandardCharsets.UTF_8); - assertThat(lines.size(), is(23)); - assertThat(lines.get(0), is("primary-meta-store:")); - assertThat(lines.get(1), is(" access-control-type: READ_ONLY")); - assertThat(lines.get(2), is(" database-prefix: ''")); - assertThat(lines.get(3), is(" latency: 0")); - assertThat(lines.get(4), is(" mapped-databases:")); - assertThat(lines.get(5), is(" - db1")); - assertThat(lines.get(6), is(" - db2")); - assertThat(lines.get(7), is(" mapped-tables:")); - assertThat(lines.get(8), is(" - database: db1")); - assertThat(lines.get(9), is(" mapped-tables:")); - assertThat(lines.get(10), is(" - tbl1")); - assertThat(lines.get(11), is(" - database: db2")); - assertThat(lines.get(12), is(" mapped-tables:")); - assertThat(lines.get(13), is(" - tbl2")); - assertThat(lines.get(14), is(" name: hcom_1")); - assertThat(lines.get(15), is(" remote-meta-store-uris: thrift://localhost:19083")); - assertThat(lines.get(16), is("federated-meta-stores:")); - assertThat(lines.get(17), is("- access-control-type: READ_ONLY")); - assertThat(lines.get(18), is(" database-name-mapping: {}")); - assertThat(lines.get(19), is(" database-prefix: hcom_2_")); - assertThat(lines.get(20), is(" latency: 0")); - assertThat(lines.get(21), is(" name: hcom_2")); - assertThat(lines.get(22), is(" remote-meta-store-uris: thrift://localhost:29083")); + assertThat(lines.size(), is(25)); + int i = 0; + while (i < 25) { + assertThat(lines.get(i++), is("primary-meta-store:")); + assertThat(lines.get(i++), is(" access-control-type: READ_ONLY")); + assertThat(lines.get(i++), is(" database-prefix: ''")); + assertThat(lines.get(i++), is(" impersonation-enabled: false")); + assertThat(lines.get(i++), is(" latency: 0")); + assertThat(lines.get(i++), is(" mapped-databases:")); + assertThat(lines.get(i++), is(" - db1")); + assertThat(lines.get(i++), is(" - db2")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - database: db1")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - tbl1")); + assertThat(lines.get(i++), is(" - database: db2")); + assertThat(lines.get(i++), is(" mapped-tables:")); + assertThat(lines.get(i++), is(" - tbl2")); + assertThat(lines.get(i++), is(" name: hcom_1")); + assertThat(lines.get(i++), is(" remote-meta-store-uris: thrift://localhost:19083")); + assertThat(lines.get(i++), is("federated-meta-stores:")); + assertThat(lines.get(i++), is("- access-control-type: READ_ONLY")); + assertThat(lines.get(i++), is(" database-name-mapping: {}")); + assertThat(lines.get(i++), is(" database-prefix: hcom_2_")); + assertThat(lines.get(i++), is(" impersonation-enabled: false")); + assertThat(lines.get(i++), is(" latency: 0")); + assertThat(lines.get(i++), is(" name: hcom_2")); + assertThat(lines.get(i++), is(" remote-meta-store-uris: thrift://localhost:29083")); + } } private PrimaryMetaStore newPrimaryInstance(String name, String remoteMetaStoreUris) { diff --git a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactoryTest.java b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactoryTest.java index b1e1984ee..23c5633f7 100644 --- a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactoryTest.java +++ b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerFactoryTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2021 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerTest.java b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerTest.java index c7b2029d0..6a5ca70d4 100644 --- a/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerTest.java +++ b/waggle-dance-core/src/test/java/com/hotels/bdp/waggledance/server/FederatedHMSHandlerTest.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2016-2023 Expedia, Inc. + * Copyright (C) 2016-2024 Expedia, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -119,6 +120,7 @@ import org.apache.hadoop.hive.metastore.api.HeartbeatTxnRangeResponse; import org.apache.hadoop.hive.metastore.api.HiveObjectPrivilege; import org.apache.hadoop.hive.metastore.api.HiveObjectRef; +import org.apache.hadoop.hive.metastore.api.HiveObjectType; import org.apache.hadoop.hive.metastore.api.ISchema; import org.apache.hadoop.hive.metastore.api.ISchemaName; import org.apache.hadoop.hive.metastore.api.LockComponent; @@ -211,6 +213,7 @@ import org.apache.hadoop.hive.metastore.api.WMGetTriggersForResourePlanResponse; import org.apache.hadoop.hive.metastore.api.WMValidateResourcePlanRequest; import org.apache.hadoop.hive.metastore.api.WMValidateResourcePlanResponse; +import org.apache.hadoop.hive.metastore.security.MetastoreDelegationTokenManager; import org.apache.thrift.TException; import org.junit.Before; import org.junit.Test; @@ -227,6 +230,7 @@ import com.hotels.bdp.waggledance.mapping.service.MappingEventListener; import com.hotels.bdp.waggledance.mapping.service.PanopticOperationHandler; import com.hotels.bdp.waggledance.mapping.service.impl.NotifyingFederationService; +import com.hotels.bdp.waggledance.util.SaslHelper.SaslServerAndMDT; @RunWith(MockitoJUnitRunner.class) public class FederatedHMSHandlerTest { @@ -244,6 +248,9 @@ public class FederatedHMSHandlerTest { private @Mock DatabaseMapping primaryMapping; private @Mock Iface primaryClient; private @Mock WaggleDanceConfiguration waggleDanceConfiguration; + private @Mock SaslServerAndMDT saslServerAndMDT; + private @Mock MetastoreDelegationTokenManager metastoreDelegationTokenManager; + private @Mock MetaStoreProxyServer metaStoreProxyServer; private FederatedHMSHandler handler; @@ -1518,25 +1525,32 @@ public void grant_revoke_privileges() throws TException { } @Test - public void get_delegation_token() throws TException { + public void get_delegation_token() throws TException, IOException, InterruptedException { String expected = "expected"; - when(primaryClient.get_delegation_token("owner", "kerberos_principal")).thenReturn(expected); + MetaStoreProxyServer.setSaslServerAndMDT(saslServerAndMDT); + when(saslServerAndMDT.getDelegationTokenManager()).thenReturn(metastoreDelegationTokenManager); + when(metastoreDelegationTokenManager.getDelegationToken("owner", "kerberos_principal", + null)).thenReturn(expected); String result = handler.get_delegation_token("owner", "kerberos_principal"); assertThat(result, is(expected)); } @Test - public void renew_delegation_token() throws TException { + public void renew_delegation_token() throws TException, IOException { long expected = 10L; - when(primaryClient.renew_delegation_token("token")).thenReturn(expected); + MetaStoreProxyServer.setSaslServerAndMDT(saslServerAndMDT); + when(saslServerAndMDT.getDelegationTokenManager()).thenReturn(metastoreDelegationTokenManager); + when(metastoreDelegationTokenManager.renewDelegationToken("token")).thenReturn(expected); long result = handler.renew_delegation_token("token"); assertThat(result, is(expected)); } @Test - public void cancel_delegation_token() throws TException { + public void cancel_delegation_token() throws TException, IOException { + MetaStoreProxyServer.setSaslServerAndMDT(saslServerAndMDT); + when(saslServerAndMDT.getDelegationTokenManager()).thenReturn(metastoreDelegationTokenManager); handler.cancel_delegation_token("token"); - verify(primaryClient).cancel_delegation_token("token"); + verify(metastoreDelegationTokenManager).cancelDelegationToken("token"); } @Test @@ -1990,9 +2004,21 @@ public void refresh_privileges() throws TException { GrantRevokePrivilegeRequest grantRevokePrivilegeRequest = new GrantRevokePrivilegeRequest(); HiveObjectRef hiveObjectRef = new HiveObjectRef(); hiveObjectRef.setDbName(DB_P); + PrivilegeBag privileges = new PrivilegeBag(); + List privilegesList = new ArrayList<>(); + HiveObjectPrivilege hiveObjectPrivilege = new HiveObjectPrivilege(); + HiveObjectRef hor = new HiveObjectRef(); + hor.setDbName(DB_P); + hor.setObjectType(HiveObjectType.DATABASE); + hor.setObjectName(DB_P); + hiveObjectPrivilege.setHiveObject(hor); + privilegesList.add(hiveObjectPrivilege); + privileges.setPrivileges(privilegesList); + grantRevokePrivilegeRequest.setPrivileges(privileges); GrantRevokePrivilegeResponse grantRevokePrivilegeResponse = new GrantRevokePrivilegeResponse(); when(primaryMapping.transformInboundHiveObjectRef(hiveObjectRef)).thenReturn(hiveObjectRef); + when(primaryMapping.transformInboundGrantRevokePrivilegesRequest(grantRevokePrivilegeRequest)).thenReturn(grantRevokePrivilegeRequest); when(primaryClient.refresh_privileges(hiveObjectRef, "dummy", grantRevokePrivilegeRequest)).thenReturn(grantRevokePrivilegeResponse); GrantRevokePrivilegeResponse result = handler.refresh_privileges(hiveObjectRef, "dummy", grantRevokePrivilegeRequest); assertThat(result, is(grantRevokePrivilegeResponse));