-
Notifications
You must be signed in to change notification settings - Fork 9.2k
HADOOP-17536. ABFS: Supporting customer provided encryption key #2707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
0e5ed8a
05aba4e
87335a3
9e93938
cc4b587
fd4c2d0
536b5d1
173bc3c
8844880
0c04e50
1ebf5ae
2cd36d7
4ce069d
8da250b
176eb38
09b462b
53d4900
8c94d32
25f63ce
4e5268f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,11 +25,16 @@ | |
| import java.net.MalformedURLException; | ||
| import java.net.URL; | ||
| import java.net.URLEncoder; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.MessageDigest; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.time.Instant; | ||
| import java.util.ArrayList; | ||
| import java.util.Base64; | ||
| import java.util.List; | ||
| import java.util.Locale; | ||
|
|
||
| import org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations; | ||
| import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting; | ||
| import org.apache.hadoop.thirdparty.com.google.common.base.Strings; | ||
| import org.apache.hadoop.security.ssl.DelegatingSSLSocketFactory; | ||
|
|
@@ -52,6 +57,7 @@ | |
|
|
||
| import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.*; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations.DEFAULT_DELETE_CONSIDERED_IDEMPOTENT; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.FileSystemConfigurations.SERVER_SIDE_ENCRYPTION_ALGORITHM; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes.HTTPS_SCHEME; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.HttpHeaderConfigurations.*; | ||
| import static org.apache.hadoop.fs.azurebfs.constants.HttpQueryParams.*; | ||
|
|
@@ -61,6 +67,7 @@ | |
| */ | ||
| public class AbfsClient implements Closeable { | ||
| public static final Logger LOG = LoggerFactory.getLogger(AbfsClient.class); | ||
|
|
||
| private final URL baseUrl; | ||
| private final SharedKeyCredentials sharedKeyCredentials; | ||
| private final String xMsVersion = "2019-12-12"; | ||
|
|
@@ -69,6 +76,8 @@ public class AbfsClient implements Closeable { | |
| private final AbfsConfiguration abfsConfiguration; | ||
| private final String userAgent; | ||
| private final AbfsPerfTracker abfsPerfTracker; | ||
| private final String clientProvidedEncryptionKey; | ||
| private final String clientProvidedEncryptionKeySHA; | ||
|
|
||
| private final String accountName; | ||
| private final AuthType authType; | ||
|
|
@@ -78,7 +87,8 @@ public class AbfsClient implements Closeable { | |
|
|
||
| private AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCredentials, | ||
| final AbfsConfiguration abfsConfiguration, | ||
| final AbfsClientContext abfsClientContext) { | ||
| final AbfsClientContext abfsClientContext) | ||
| throws IOException { | ||
| this.baseUrl = baseUrl; | ||
| this.sharedKeyCredentials = sharedKeyCredentials; | ||
| String baseUrlString = baseUrl.toString(); | ||
|
|
@@ -88,6 +98,17 @@ private AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCreden | |
| this.accountName = abfsConfiguration.getAccountName().substring(0, abfsConfiguration.getAccountName().indexOf(AbfsHttpConstants.DOT)); | ||
| this.authType = abfsConfiguration.getAuthType(accountName); | ||
|
|
||
| String encryptionKey = this.abfsConfiguration | ||
| .getClientProvidedEncryptionKey(); | ||
| if (encryptionKey != null) { | ||
| this.clientProvidedEncryptionKey = getBase64EncodedString(encryptionKey); | ||
| this.clientProvidedEncryptionKeySHA = getBase64EncodedString( | ||
| getSHA256Hash(encryptionKey)); | ||
| } else { | ||
| this.clientProvidedEncryptionKey = null; | ||
| this.clientProvidedEncryptionKeySHA = null; | ||
| } | ||
|
|
||
| String sslProviderName = null; | ||
|
|
||
| if (this.baseUrl.toString().startsWith(HTTPS_SCHEME)) { | ||
|
|
@@ -111,19 +132,38 @@ private AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCreden | |
| public AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCredentials, | ||
| final AbfsConfiguration abfsConfiguration, | ||
| final AccessTokenProvider tokenProvider, | ||
| final AbfsClientContext abfsClientContext) { | ||
| final AbfsClientContext abfsClientContext) | ||
| throws IOException { | ||
| this(baseUrl, sharedKeyCredentials, abfsConfiguration, abfsClientContext); | ||
| this.tokenProvider = tokenProvider; | ||
| } | ||
|
|
||
| public AbfsClient(final URL baseUrl, final SharedKeyCredentials sharedKeyCredentials, | ||
| final AbfsConfiguration abfsConfiguration, | ||
| final SASTokenProvider sasTokenProvider, | ||
| final AbfsClientContext abfsClientContext) { | ||
| final AbfsClientContext abfsClientContext) | ||
| throws IOException { | ||
| this(baseUrl, sharedKeyCredentials, abfsConfiguration, abfsClientContext); | ||
| this.sasTokenProvider = sasTokenProvider; | ||
| } | ||
|
|
||
| private byte[] getSHA256Hash(String key) throws IOException { | ||
| try { | ||
| final MessageDigest digester = MessageDigest.getInstance("SHA-256"); | ||
| return digester.digest(key.getBytes(StandardCharsets.UTF_8)); | ||
| } catch (NoSuchAlgorithmException e) { | ||
| throw new IOException(e); | ||
| } | ||
| } | ||
|
|
||
| private String getBase64EncodedString(String key) { | ||
bilaharith marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return getBase64EncodedString(key.getBytes(StandardCharsets.UTF_8)); | ||
| } | ||
|
|
||
| private String getBase64EncodedString(byte[] bytes) { | ||
| return Base64.getEncoder().encodeToString(bytes); | ||
| } | ||
|
|
||
| @Override | ||
| public void close() throws IOException { | ||
| if (tokenProvider instanceof Closeable) { | ||
|
|
@@ -159,6 +199,18 @@ List<AbfsHttpHeader> createDefaultHeaders() { | |
| return requestHeaders; | ||
| } | ||
|
|
||
| private void addCustomerProvidedKeyHeaders( | ||
| final List<AbfsHttpHeader> requestHeaders) { | ||
| if (clientProvidedEncryptionKey != null) { | ||
| requestHeaders.add( | ||
| new AbfsHttpHeader(X_MS_ENCRYPTION_KEY, clientProvidedEncryptionKey)); | ||
| requestHeaders.add(new AbfsHttpHeader(X_MS_ENCRYPTION_KEY_SHA256, | ||
| clientProvidedEncryptionKeySHA)); | ||
| requestHeaders.add(new AbfsHttpHeader(X_MS_ENCRYPTION_ALGORITHM, | ||
| SERVER_SIDE_ENCRYPTION_ALGORITHM)); | ||
| } | ||
| } | ||
|
|
||
| AbfsUriQueryBuilder createDefaultUriQueryBuilder() { | ||
| final AbfsUriQueryBuilder abfsUriQueryBuilder = new AbfsUriQueryBuilder(); | ||
| abfsUriQueryBuilder.addQuery(QUERY_PARAM_TIMEOUT, DEFAULT_TIMEOUT); | ||
|
|
@@ -268,6 +320,9 @@ public AbfsRestOperation createPath(final String path, final boolean isFile, fin | |
| final String permission, final String umask, | ||
| final boolean isAppendBlob, final String eTag) throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| if (isFile) { | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
| } | ||
| if (!overwrite) { | ||
| requestHeaders.add(new AbfsHttpHeader(IF_NONE_MATCH, AbfsHttpConstants.STAR)); | ||
| } | ||
|
|
@@ -412,6 +467,7 @@ public AbfsRestOperation append(final String path, final byte[] buffer, | |
| AppendRequestParameters reqParams, final String cachedSasToken) | ||
| throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
| // JDK7 does not support PATCH, so to workaround the issue we will use | ||
| // PUT and specify the real method in the X-Http-Method-Override header. | ||
| requestHeaders.add(new AbfsHttpHeader(X_HTTP_METHOD_OVERRIDE, | ||
steveloughran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
@@ -495,6 +551,7 @@ public AbfsRestOperation flush(final String path, final long position, boolean r | |
| boolean isClose, final String cachedSasToken) | ||
| throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
| // JDK7 does not support PATCH, so to workaround the issue we will use | ||
| // PUT and specify the real method in the X-Http-Method-Override header. | ||
| requestHeaders.add(new AbfsHttpHeader(X_HTTP_METHOD_OVERRIDE, | ||
|
|
@@ -523,6 +580,7 @@ public AbfsRestOperation flush(final String path, final long position, boolean r | |
| public AbfsRestOperation setPathProperties(final String path, final String properties) | ||
bilaharith marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
| // JDK7 does not support PATCH, so to workaround the issue we will use | ||
| // PUT and specify the real method in the X-Http-Method-Override header. | ||
| requestHeaders.add(new AbfsHttpHeader(X_HTTP_METHOD_OVERRIDE, | ||
|
|
@@ -556,6 +614,8 @@ public AbfsRestOperation getPathStatus(final String path, final boolean includeP | |
| // only traversal (execute) permission is required. | ||
| abfsUriQueryBuilder.addQuery(HttpQueryParams.QUERY_PARAM_ACTION, AbfsHttpConstants.GET_STATUS); | ||
| operation = SASTokenProvider.GET_STATUS_OPERATION; | ||
| } else { | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
| } | ||
| abfsUriQueryBuilder.addQuery(HttpQueryParams.QUERY_PARAM_UPN, String.valueOf(abfsConfiguration.isUpnUsed())); | ||
| appendSASTokenToQuery(path, operation, abfsUriQueryBuilder); | ||
|
|
@@ -574,6 +634,7 @@ public AbfsRestOperation getPathStatus(final String path, final boolean includeP | |
| public AbfsRestOperation read(final String path, final long position, final byte[] buffer, final int bufferOffset, | ||
| final int bufferLength, final String eTag, String cachedSasToken) throws AzureBlobFileSystemException { | ||
| final List<AbfsHttpHeader> requestHeaders = createDefaultHeaders(); | ||
| addCustomerProvidedKeyHeaders(requestHeaders); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the purposes of debugging, add a log line if request fails and the sha256 of the key used in request and the server returned sha256 in response are different.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Server doesn't send back the actual sha of the encryption key with which the data is encrypted if the encryption key sent is wrong |
||
| requestHeaders.add(new AbfsHttpHeader(RANGE, | ||
| String.format("bytes=%d-%d", position, position + bufferLength - 1))); | ||
| requestHeaders.add(new AbfsHttpHeader(IF_MATCH, eTag)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,7 @@ public class AbfsHttpOperation implements AbfsPerfLoggable { | |
| private String requestId = ""; | ||
| private String expectedAppendPos = ""; | ||
| private ListResultSchema listResultSchema = null; | ||
| private List<AbfsHttpHeader> responseHeaders; | ||
|
|
||
| // metrics | ||
| private int bytesSent; | ||
|
|
@@ -339,9 +340,10 @@ public void processResponse(final byte[] buffer, final int offset, final int len | |
| if (this.requestId == null) { | ||
| this.requestId = AbfsHttpConstants.EMPTY_STRING; | ||
| } | ||
| responseHeaders = AbfsIoUtils.getResponseHeaders(connection); | ||
| // dump the headers | ||
| AbfsIoUtils.dumpHeadersToDebugLog("Response Headers", | ||
| connection.getHeaderFields()); | ||
| responseHeaders); | ||
|
||
|
|
||
| if (AbfsHttpConstants.HTTP_METHOD_HEAD.equals(this.method)) { | ||
| // If it is HEAD, and it is ERROR | ||
|
|
@@ -558,6 +560,10 @@ public String getSignatureMaskedEncodedUrl() { | |
| return this.maskedEncodedUrl; | ||
| } | ||
|
|
||
| public List<AbfsHttpHeader> getResponseHeaders() { | ||
| return this.responseHeaders; | ||
| } | ||
|
|
||
| public static class AbfsHttpOperationWithFixedResult extends AbfsHttpOperation { | ||
| /** | ||
| * Creates an instance to represent fixed results. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -224,7 +224,7 @@ public void loadConfiguredFileSystem() throws Exception { | |
| this.fileSystemName = authorityParts[0]; | ||
|
|
||
| // Reset URL with configured filesystem | ||
| final String abfsUrl = this.getFileSystemName() + "@" + this.getAccountName(); | ||
| final String abfsUrl = this.getFileSystemName() + "@" + authorityParts[1]; | ||
|
||
| URI defaultUri = null; | ||
|
|
||
| defaultUri = new URI(abfsScheme, abfsUrl, null, null, null); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dot as a delimiter has been the trend. Any reason to deviate ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should not use the dot here. The dot is used to namespace. - is fine else make it one word