Added support for logging string to sign in Storage and ability to pass in account name when generating a UserDelegationKey#18075
Conversation
| UserDelegationKey userDelegationKey) { | ||
| return new BlobSasImplUtil(blobServiceSasSignatureValues, getBlobContainerName()) | ||
| .generateUserDelegationSas(userDelegationKey, getAccountName()); | ||
| .generateUserDelegationSas(userDelegationKey, getAccountName(), Context.NONE); |
|
|
||
| when: | ||
| bc.setMetadata(metadata) | ||
| bc.setMetadataWithResponse(metadata, null, null, new com.azure.core.util.Context("Log-String-To-Sign", true)) |
| final String canonicalName = getCanonicalName(storageSharedKeyCredentials.getAccountName()); | ||
| final String signature = storageSharedKeyCredentials.computeHmac256(stringToSign(canonicalName)); | ||
| final String stringToSign = stringToSign(canonicalName); | ||
| if (context != null && Boolean.TRUE.equals(context.getData("Log-String-To-Sign").orElse(false))) { |
There was a problem hiding this comment.
I would use a different key such as azure-log-string-to-sign to ensure there is no overlap and to indicate it's Azure SDK specific
| if (context != null && Boolean.TRUE.equals(context.getData("Log-String-To-Sign").orElse(false))) { | ||
| logger.info("The string to sign computed by the SDK is: {}{}", stringToSign, | ||
| System.getProperty("line.separator")); | ||
| logger.warning("Please remember to disable 'Log-String-To-Sign' before going to production as this " | ||
| + "string can potentially contain PII."); | ||
| } |
There was a problem hiding this comment.
Since this is in multiple spots let's make it a method so if it needs to be updated only one location needs modification
| private static String convertExceptionMessage(String message, HttpResponse response) { | ||
| if (response != null) { | ||
| if (response.getStatusCode() == 403) { | ||
| return message + "\nIf you are using a StorageSharedKeyCredential, and the server returned an " |
There was a problem hiding this comment.
Should we use String.format here with line separators instead of \n or are we using '\n' as that would be what is returned by the service.
There was a problem hiding this comment.
I think the service returns \n so its fine if we leave this as \n
There was a problem hiding this comment.
Changed to %n as CI was angry
|
|
||
| private String buildStringToSign(URL requestURL, String httpMethod, Map<String, String> headers) { | ||
| private String buildStringToSign(URL requestURL, String httpMethod, Map<String, String> headers, | ||
| boolean logStringToSign) { |
There was a problem hiding this comment.
Why not accept the context itself instead of a boolean?
There was a problem hiding this comment.
The context in the pipeline policy isnt an AzureContext, its like a HttpPipelineContext so I'd need to do that weird conversion somewhere. Would you prefer I do it in the pipeline policy?
There was a problem hiding this comment.
HttpPipelineContext is the combination of HttpRequest and Context where the Context APIs are wrapped and hidden by the HttpPipelineContext APIs, in the end it should contain all information from the Context.
| * | ||
| * @param blobServiceSasSignatureValues {@link BlobServiceSasSignatureValues} | ||
| * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. | ||
| * @see BlobServiceAsyncClient#getUserDelegationKey(OffsetDateTime, OffsetDateTime) for more information on how to |
There was a problem hiding this comment.
I would change this to an {@link} as @see serves a different purpose to link to a class with its own special section. This is an example: https://azuresdkdocs.blob.core.windows.net/$web/java/azure-core-http-netty/1.7.0/com/azure/core/http/netty/NettyAsyncHttpClientBuilder.html
| * @param accountName The account name. | ||
| * @param context Additional context that is passed through the code when generating a SAS. | ||
| * | ||
| * @return A {@code String} representing all SAS query parameters. |
There was a problem hiding this comment.
Does it represent all SAS query parameters or is it the SAS query parameters string?
There was a problem hiding this comment.
It should be 'the'. I've fixed it.
|
|
||
| /** | ||
| * Generates a service SAS for the container using the specified {@link BlobServiceSasSignatureValues} | ||
| * Note : The client must be authenticated via {@link StorageSharedKeyCredential} |
There was a problem hiding this comment.
This will generate on the same line as the sentence above due to Javadocs being HTML, let's make this another <p> so get a line break.
| * | ||
| * @param blobServiceSasSignatureValues {@link BlobServiceSasSignatureValues} | ||
| * @param userDelegationKey A {@link UserDelegationKey} object used to sign the SAS values. | ||
| * @see BlobServiceClient#getUserDelegationKey(OffsetDateTime, OffsetDateTime) for more information on how to get a |
There was a problem hiding this comment.
Same comment as the other Javadoc with inlined @see
| * | ||
| * @return A {@code String} representing all SAS query parameters. | ||
| */ | ||
| public String generateUserDelegationSas(BlobServiceSasSignatureValues blobServiceSasSignatureValues, |
There was a problem hiding this comment.
Is there going to be an issue that this method assumes the container name from the BlobContainerClient instance but then allows for any account name to be passed? This could result in cases where the container doesn't exist in the other account, and given that to me this method feels closer to a static utility which allows generating a SAS for any account and container given the UserDelegationKey.
There was a problem hiding this comment.
I think thats a valid concern. The reason we wanted to put account name here is specifically for situations where a user passes in a custom domain name, and we can't parse an account name as a result.
| * | ||
| * @return A {@code String} representing all SAS query parameters. | ||
| */ | ||
| public String generateUserDelegationSas(BlobServiceSasSignatureValues blobServiceSasSignatureValues, |
There was a problem hiding this comment.
Same comment as BlobContainerClient around this being a mix of external values and values on the instance, it ends up being an in-between of static utility to generate any SAS given the delegation key and requested permissions but also being information specific to the blob such as container, name, version, and snapshot.
|
|
||
| private String buildStringToSign(URL requestURL, String httpMethod, Map<String, String> headers) { | ||
| private String buildStringToSign(URL requestURL, String httpMethod, Map<String, String> headers, | ||
| boolean logStringToSign) { |
There was a problem hiding this comment.
HttpPipelineContext is the combination of HttpRequest and Context where the Context APIs are wrapped and hidden by the HttpPipelineContext APIs, in the end it should contain all information from the Context.
| public static void logStringToSign(ClientLogger logger, String stringToSign, Context context) { | ||
| if (context != null && Boolean.TRUE.equals(context.getData(Constants.STORAGE_LOG_STRING_TO_SIGN).orElse(false))) { | ||
| logger.info("The string to sign computed by the SDK is: {}{}", stringToSign, | ||
| System.getProperty("line.separator")); |
There was a problem hiding this comment.
Should use System.lineSeparator() instead as it is a convenience wrapper for this.
|
|
||
| if (logStringToSign) { | ||
| StorageImplUtils.logStringToSign(logger, stringToSign, | ||
| new Context(Constants.STORAGE_LOG_STRING_TO_SIGN, true)); |
There was a problem hiding this comment.
Let's make this a constant so we don't need to instantiate a new instance every time this code path is executed
| public static String convertStorageExceptionMessage(String message, HttpResponse response) { | ||
| if (response != null) { | ||
| if (response.getStatusCode() == 403) { | ||
| return String.format("If you are using a StorageSharedKeyCredential, and the server returned an " |
There was a problem hiding this comment.
Let's make this base message a constant as all parameters for the formatting are constant, that way we don't need to make additional calls to String.format and wait for this to be optimized by JIT
| if (context != null && Boolean.TRUE.equals(context.getData(Constants.STORAGE_LOG_STRING_TO_SIGN).orElse(false))) { | ||
| logger.info("The string to sign computed by the SDK is: {}{}", stringToSign, | ||
| System.lineSeparator()); | ||
| logger.warning("Please remember to disable '{}' before going to production as this " |
There was a problem hiding this comment.
Let's make this log message a constant string as it doesn't have any instance data
Resolves #17935 and
Resolves #6713