-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[#2711] feat(filesystem): Support Kerberos client authentication in g…
…vfs (#3314) ### What changes were proposed in this pull request? Support using Kerberos authentication type to initialize Gravitino client in gvfs. ### Why are the changes needed? Fix: #2711 ### How was this patch tested? Add some uts for: 1. use principal and keytab to auth. 2. kerberos configs for gvfs. 3. some invalid kerberos case. Test locally and use `kerberos ticket cache` to initialize gvfs. The steps are as follows: 1. Deploy the KDC server locally, refer to the doc: https://blog.csdn.net/lo085213/article/details/105057186. 2. Register the service account `HTTP/localhost@HADOOP.COM` and client account `client@HADOOP.COM` in the KDC server. 3. Execute the `kinit -kt client.keytab client@HADOOP.COM` command locally. 4. Use the `klist` command to check the environment for tickets containing `client@HADOOP.COM`. 5. Write a unit test to load metalake through gvfs with the kerberos ticket cache. ![image](https://github.com/datastrato/gravitino/assets/26177232/f655e687-8412-4000-bb07-bd9ccadd8387) ![image](https://github.com/datastrato/gravitino/assets/26177232/a3d36646-37ad-44b9-8cca-129a18196663) ![image](https://github.com/datastrato/gravitino/assets/26177232/df7504a2-046d-45fa-9da3-7b681ebfd7e1) --------- Co-authored-by: xiaojiebao <xiaojiebao@xiaomi.com>
- Loading branch information
Showing
7 changed files
with
334 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
...ystem-hadoop3/src/test/java/com/datastrato/gravitino/filesystem/hadoop/KdcServerBase.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/* | ||
* Copyright 2024 Datastrato Pvt Ltd. | ||
* This software is licensed under the Apache License version 2. | ||
*/ | ||
|
||
package com.datastrato.gravitino.filesystem.hadoop; | ||
|
||
import java.io.File; | ||
import java.util.UUID; | ||
import org.apache.hadoop.minikdc.KerberosSecurityTestcase; | ||
|
||
public class KdcServerBase extends KerberosSecurityTestcase { | ||
private static final KerberosSecurityTestcase INSTANCE = new KerberosSecurityTestcase(); | ||
private static final String CLIENT_PRINCIPAL = "client@EXAMPLE.COM"; | ||
private static final String SERVER_PRINCIPAL = "HTTP/localhost@EXAMPLE.COM"; | ||
private static final String KEYTAB_FILE = | ||
new File(System.getProperty("test.dir", "target"), UUID.randomUUID().toString()) | ||
.getAbsolutePath(); | ||
|
||
static { | ||
try { | ||
INSTANCE.startMiniKdc(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private KdcServerBase() {} | ||
|
||
public static void stopKdc() { | ||
INSTANCE.stopMiniKdc(); | ||
} | ||
|
||
public static void initKeyTab() throws Exception { | ||
File keytabFile = new File(KEYTAB_FILE); | ||
String clientPrincipal = removeRealm(CLIENT_PRINCIPAL); | ||
String serverPrincipal = removeRealm(SERVER_PRINCIPAL); | ||
INSTANCE.getKdc().createPrincipal(keytabFile, clientPrincipal, serverPrincipal); | ||
} | ||
|
||
private static String removeRealm(String principal) { | ||
return principal.substring(0, principal.lastIndexOf("@")); | ||
} | ||
|
||
public static String getServerPrincipal() { | ||
return SERVER_PRINCIPAL; | ||
} | ||
|
||
public static String getClientPrincipal() { | ||
return CLIENT_PRINCIPAL; | ||
} | ||
|
||
public static String getKeytabFile() { | ||
return KEYTAB_FILE; | ||
} | ||
} |
196 changes: 196 additions & 0 deletions
196
...-hadoop3/src/test/java/com/datastrato/gravitino/filesystem/hadoop/TestKerberosClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
/* | ||
* Copyright 2024 Datastrato Pvt Ltd. | ||
* This software is licensed under the Apache License version 2. | ||
*/ | ||
package com.datastrato.gravitino.filesystem.hadoop; | ||
|
||
import static com.datastrato.gravitino.server.authentication.KerberosConfig.KEYTAB; | ||
import static com.datastrato.gravitino.server.authentication.KerberosConfig.PRINCIPAL; | ||
import static org.junit.jupiter.api.Assertions.assertThrows; | ||
import static org.mockserver.model.HttpResponse.response; | ||
|
||
import com.datastrato.gravitino.Config; | ||
import com.datastrato.gravitino.server.authentication.KerberosAuthenticator; | ||
import java.io.File; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.UUID; | ||
import org.apache.hadoop.conf.Configuration; | ||
import org.apache.hadoop.fs.Path; | ||
import org.apache.hc.core5.http.HttpStatus; | ||
import org.apache.hc.core5.http.Method; | ||
import org.junit.jupiter.api.AfterAll; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockserver.matchers.Times; | ||
import org.mockserver.model.Header; | ||
import org.mockserver.model.HttpRequest; | ||
|
||
public class TestKerberosClient extends TestGvfsBase { | ||
|
||
@BeforeAll | ||
public static void setup() { | ||
try { | ||
KdcServerBase.initKeyTab(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
TestGvfsBase.setup(); | ||
conf.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_AUTH_TYPE_KEY, | ||
GravitinoVirtualFileSystemConfiguration.KERBEROS_AUTH_TYPE); | ||
conf.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_PRINCIPAL_KEY, | ||
KdcServerBase.getClientPrincipal()); | ||
conf.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY, | ||
KdcServerBase.getKeytabFile()); | ||
} | ||
|
||
@AfterAll | ||
public static void teardown() { | ||
KdcServerBase.stopKdc(); | ||
} | ||
|
||
@Test | ||
public void testAuthConfigs() { | ||
// init conf | ||
Configuration configuration = new Configuration(); | ||
configuration.set( | ||
String.format( | ||
"fs.%s.impl.disable.cache", GravitinoVirtualFileSystemConfiguration.GVFS_SCHEME), | ||
"true"); | ||
configuration.set("fs.gvfs.impl", GVFS_IMPL_CLASS); | ||
configuration.set("fs.AbstractFileSystem.gvfs.impl", GVFS_ABSTRACT_IMPL_CLASS); | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_METALAKE_KEY, metalakeName); | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_SERVER_URI_KEY, | ||
GravitinoMockServerBase.serverUri()); | ||
|
||
// set auth type, but do not set other configs | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_AUTH_TYPE_KEY, | ||
GravitinoVirtualFileSystemConfiguration.KERBEROS_AUTH_TYPE); | ||
assertThrows( | ||
IllegalArgumentException.class, () -> managedFilesetPath.getFileSystem(configuration)); | ||
|
||
// set not exist keytab path | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY, | ||
"file://tmp/test.keytab"); | ||
assertThrows( | ||
IllegalArgumentException.class, () -> managedFilesetPath.getFileSystem(configuration)); | ||
} | ||
|
||
@Test | ||
public void testAuthWithPrincipalAndKeytabNormally() throws Exception { | ||
KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator(); | ||
Config config = new Config(false) {}; | ||
config.set(PRINCIPAL, KdcServerBase.getServerPrincipal()); | ||
config.set(KEYTAB, KdcServerBase.getKeytabFile()); | ||
kerberosAuthenticator.initialize(config); | ||
|
||
Configuration configuration = new Configuration(conf); | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_PRINCIPAL_KEY, | ||
KdcServerBase.getClientPrincipal()); | ||
configuration.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY, | ||
KdcServerBase.getKeytabFile()); | ||
|
||
// mock load metalake with principal and keytab | ||
String testMetalake = "test_kerberos_normally"; | ||
HttpRequest mockRequest = | ||
HttpRequest.request("/api/metalakes/" + testMetalake) | ||
.withMethod(Method.GET.name()) | ||
.withQueryStringParameters(Collections.emptyMap()); | ||
GravitinoMockServerBase.mockServer() | ||
.when(mockRequest, Times.unlimited()) | ||
.respond( | ||
httpRequest -> { | ||
List<Header> headers = httpRequest.getHeaders().getEntries(); | ||
for (Header header : headers) { | ||
if (header.getName().equalsIgnoreCase("Authorization")) { | ||
byte[] tokenValue = | ||
header.getValues().get(0).getValue().getBytes(StandardCharsets.UTF_8); | ||
kerberosAuthenticator.authenticateToken(tokenValue); | ||
} | ||
} | ||
return response().withStatusCode(HttpStatus.SC_OK); | ||
}); | ||
Path newPath = new Path(managedFilesetPath.toString().replace(metalakeName, testMetalake)); | ||
// Should auth successfully | ||
newPath.getFileSystem(configuration); | ||
} | ||
|
||
@Test | ||
public void testAuthWithInvalidInfo() throws Exception { | ||
KerberosAuthenticator kerberosAuthenticator = new KerberosAuthenticator(); | ||
Config config = new Config(false) {}; | ||
config.set(PRINCIPAL, KdcServerBase.getServerPrincipal()); | ||
config.set(KEYTAB, KdcServerBase.getKeytabFile()); | ||
kerberosAuthenticator.initialize(config); | ||
|
||
// test with invalid principal and keytab | ||
Configuration conf1 = new Configuration(conf); | ||
conf1.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_PRINCIPAL_KEY, | ||
"invalid@EXAMPLE.COM"); | ||
conf1.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY, | ||
KdcServerBase.getKeytabFile()); | ||
|
||
String testMetalake = "test_invalid"; | ||
HttpRequest mockRequest = | ||
HttpRequest.request("/api/metalakes/" + testMetalake) | ||
.withMethod(Method.GET.name()) | ||
.withQueryStringParameters(Collections.emptyMap()); | ||
GravitinoMockServerBase.mockServer() | ||
.when(mockRequest, Times.unlimited()) | ||
.respond( | ||
httpRequest -> { | ||
List<Header> headers = httpRequest.getHeaders().getEntries(); | ||
for (Header header : headers) { | ||
if (header.getName().equalsIgnoreCase("Authorization")) { | ||
byte[] tokenValue = | ||
header.getValues().get(0).getValue().getBytes(StandardCharsets.UTF_8); | ||
kerberosAuthenticator.authenticateToken(tokenValue); | ||
} | ||
} | ||
return response().withStatusCode(HttpStatus.SC_OK); | ||
}); | ||
Path newPath = new Path(managedFilesetPath.toString().replace(metalakeName, testMetalake)); | ||
Assertions.assertThrows(IllegalStateException.class, () -> newPath.getFileSystem(conf1)); | ||
|
||
// test with principal and invalid keytab | ||
File invalidKeytabFile = | ||
new File(System.getProperty("test.dir", "target"), UUID.randomUUID().toString()); | ||
if (invalidKeytabFile.exists()) { | ||
invalidKeytabFile.delete(); | ||
} | ||
invalidKeytabFile.createNewFile(); | ||
|
||
Configuration conf2 = new Configuration(conf); | ||
conf2.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_PRINCIPAL_KEY, | ||
KdcServerBase.getClientPrincipal()); | ||
conf2.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY, | ||
invalidKeytabFile.getAbsolutePath()); | ||
Assertions.assertThrows(IllegalStateException.class, () -> newPath.getFileSystem(conf2)); | ||
invalidKeytabFile.delete(); | ||
|
||
// test with principal and no keytab | ||
Configuration conf3 = new Configuration(conf); | ||
conf3.set( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_PRINCIPAL_KEY, | ||
KdcServerBase.getClientPrincipal()); | ||
// remove keytab configuration | ||
conf3.unset( | ||
GravitinoVirtualFileSystemConfiguration.FS_GRAVITINO_CLIENT_KERBEROS_KEYTAB_FILE_PATH_KEY); | ||
Assertions.assertThrows(IllegalStateException.class, () -> newPath.getFileSystem(conf3)); | ||
} | ||
} |
Oops, something went wrong.