-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Add AzureSasCredential #17636
Add AzureSasCredential #17636
Conversation
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.
LGTM, please get an architect to sign off on this PR
private string _signature; | ||
|
||
/// <summary> | ||
/// Signature used to authenticate to an Azure service. |
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.
Nit - let's use "Shared access signature" in all the doc comments. The property/param names are fine though.
string signature = _credential.Signature; | ||
if (signature.StartsWith("?", StringComparison.InvariantCulture)) | ||
{ | ||
signature = signature.Substring(1); |
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.
It would be better to use StringBuilder and Spans to minimize allocations in the pipeline if possible.
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.
Ouch. In Java substring
would share internal array with parent. Good to know.
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.
I changed the code. However, StringBuilder.Append
that takes ReadOnlySpan<string>
isn't available in both netstandard20 and net461 that we target :/ So it isn't as good as it could be in net5... unless there's better way.
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.
Looks like we won't get any better with .NET version we're targeting (see comments below). I brought back original code.
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.
{ | ||
signature = signature.Substring(1); | ||
} | ||
if (!query.Contains(signature)) |
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.
Why is this check needed? Won't we validate no SAS URI on the client .ctor?
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.
Can the query
be 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.
It's not related to presence of the SAS in client's URI.
I used AzureKeyCredentialPolicy
as a blueprint for this one and found this test. That test checks idempotency of the policy if request is retried. I created similar tests for SAS policy and discovered that we'd be appending SAS on each retry. So this is to prevent that.
In storage auth policy is per-retry in all languages. We had issues where requests were so long that retry could end up with expired token.
Query is empty string in worst case.
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.
Java SDK handles this problem in an interesting way. Its retry policy makes a copy of its HttpMessage equivalent before calling rest of pipeline chain, so that retries won't step on each other. Maybe doing something like that in .NET would be right solution to this problem.
/// <inheritdoc/> | ||
public override void OnSendingRequest(HttpMessage message) | ||
{ | ||
base.OnSendingRequest(message); |
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.
Nit - consider doing this last? It feels like changing the URI should happen before whatever goes on in base
, but it's a super nit thing because base
is empty and probably always will be.
|
||
namespace Azure.Core | ||
{ | ||
internal class AzureSasCredentialPolicy : HttpPipelineSynchronousPolicy |
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 consider deriving from the base policy class so we can eventually:
While there's not a scenario that requires it today, we can think about supporting automatic refresh in a future release. The pipeline policy would listen for 403s on the way back, check if the SAS token expired, raise an event (probably sync or async depending on the path), allow the event to compute/fetch a new SAS token, and mark the message as retriable so customers never notice the expiration. This could be added without breaking.
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.
Pedantically, by REST conventions, retries should only be performed on a 401. A 403 should be considered terminal and indicating "we know who you are and you'll never be allowed to do this."
Do we have services not returning 401 for expired credentials?
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.
Since AzureSasCredentialPolicy is internal class - can this be deferred until it's actually implemented? We'd be doing same thing synchronous policy does in SAS policy.
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.
I think it's fine. I would just rename this class to AzureSasCredentialSynchronousPolicy. It's shared source and it might be easier to transition to a base policy by adding a new subclass and removing this one overtime.
string signature = _credential.Signature; | ||
if (signature.StartsWith("?", StringComparison.InvariantCulture)) | ||
{ | ||
signature = signature.AsSpan().Slice(1).ToString(); |
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.
nit: Substring would result in the same amount of allocations.
var newQuery = new StringBuilder(query, query.Length + signature.Length + 1); | ||
newQuery.Append(string.IsNullOrEmpty(query) ? '?' : '&'); | ||
newQuery.Append(signature); | ||
message.Request.Uri.Query = newQuery.ToString(); |
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.
The StringBuilder version is actually more allocating than the code you had before.
var s = string.IsNullOrEmpty(query) ? '?' + signature : query + '&' + signature;
is compiled into
string s= (string.IsNullOrEmpty(text) ? string.Concat("?", text2) : string.Concat(text, "&", text2));
The string.Concat
call is a single allocation. While StringBuilder + internal array + ToString call is 3 allocations.
This reverts commit 6375606.
/// </exception> | ||
public void Update(string signature) | ||
{ | ||
Argument.AssertNotNullOrEmpty(signature, nameof(signature)); |
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.
Should we be using IsNullOrWhitespace for these? I think we use OrEmpty in other key credentails, but it seems like OrWhitespace would be better. @schaabs?
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.
I borrowed that from here
Argument.AssertNotNullOrEmpty(key, nameof(key)); |
|
||
namespace Azure.Core | ||
{ | ||
internal class AzureSasCredentialPolicy : HttpPipelineSynchronousPolicy |
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.
I think it's fine. I would just rename this class to AzureSasCredentialSynchronousPolicy. It's shared source and it might be easier to transition to a base policy by adding a new subclass and removing this one overtime.
string signature = _credential.Signature; | ||
if (signature.StartsWith("?", StringComparison.InvariantCulture)) | ||
{ | ||
signature = signature.Substring(1); |
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.
It seems that allocation optimizations must wait till we target higher .NET. |
This pull request is protected by Check Enforcer. What is Check Enforcer?Check Enforcer helps ensure all pull requests are covered by at least one check-run (typically an Azure Pipeline). When all check-runs associated with this pull request pass then Check Enforcer itself will pass. Why am I getting this message?You are getting this message because Check Enforcer did not detect any check-runs being associated with this pull request within five minutes. This may indicate that your pull request is not covered by any pipelines and so Check Enforcer is correctly blocking the pull request being merged. What should I do now?If the check-enforcer check-run is not passing and all other check-runs associated with this PR are passing (excluding license-cla) then you could try telling Check Enforcer to evaluate your pull request again. You can do this by adding a comment to this pull request as follows: What if I am onboarding a new service?Often, new services do not have validation pipelines associated with them, in order to bootstrap pipelines for a new service, you can issue the following command as a pull request comment: |
/azp run net - core - ci |
Azure Pipelines failed to run 1 pipeline(s). |
/azp run net - core - ci |
Azure Pipelines successfully started running 1 pipeline(s). |
**In this PR:** - Add `AzureSasCredential` per Azure/azure-sdk#1954 - `AzureSasCredential` is the name that has been settled on the end of discussion. - Add `AzureSasCredentialPolicy` that appends SAS to query **Remarks:** - Some service (like storage in the Portal) present SAS with leading "?". This has to be stripped before appending - The validation if serviceUri already contain sas (mentioned [here](Azure/azure-sdk#1954 (comment))) will be responsibility of service clients: - the format varies between services (i.e. Event Grid SAS and Storage SAS are vastly different) - it would be good to fail fast (at client creation) rather than late (at request send). **References** - [.NET PR](Azure/azure-sdk-for-net#17636)
**In this PR:** - Add `AzureSasCredential` per Azure/azure-sdk#1954 - `AzureSasCredential` is the name that has been settled on the end of discussion. - Add `AzureSasCredentialPolicy` that appends SAS to query **Remarks:** - Some service (like storage in the Portal) present SAS with leading "?". This has to be stripped before appending - The validation if serviceUri already contain sas (mentioned [here](Azure/azure-sdk#1954 (comment))) will be responsibility of service clients: - the format varies between services (i.e. Event Grid SAS and Storage SAS are vastly different) - it would be good to fail fast (at client creation) rather than late (at request send). **References** - [.NET PR](Azure/azure-sdk-for-net#17636)
* Add AzureSasCredential * corner case. * api. * doc update. * less allocations. * call base last. * Revert "less allocations." This reverts commit 6375606. * validation feedback. * policy rename. * another attempt to reduce allocations. * Revert "another attempt to reduce allocations." This reverts commit 89cc867. * changelog.
**In this PR:** - Add `AzureSasCredential` per Azure/azure-sdk#1954 - `AzureSasCredential` is the name that has been settled on the end of discussion. - Add `AzureSasCredentialPolicy` that appends SAS to query **Remarks:** - Some service (like storage in the Portal) present SAS with leading "?". This has to be stripped before appending - The validation if serviceUri already contain sas (mentioned [here](Azure/azure-sdk#1954 (comment))) will be responsibility of service clients: - the format varies between services (i.e. Event Grid SAS and Storage SAS are vastly different) - it would be good to fail fast (at client creation) rather than late (at request send). **References** - [.NET PR](Azure/azure-sdk-for-net#17636)
In this PR:
AzureSasCredential
per Board Review: Add SharedKey/SAS Credential types to Azure.Core (all languages) azure-sdk#1954AzureSasCredential
is the name that has been settled on the end of discussion.AzureSasCredentialPolicy
(internal shared source) that appends SAS to queryRemarks: