Skip to content
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

Merged
merged 13 commits into from
Jan 5, 2021
Merged

Add AzureSasCredential #17636

merged 13 commits into from
Jan 5, 2021

Conversation

kasobol-msft
Copy link
Contributor

@kasobol-msft kasobol-msft commented Dec 17, 2020

In this PR:

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) 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).

@ghost ghost added the Azure.Core label Dec 17, 2020
@kasobol-msft kasobol-msft marked this pull request as ready for review December 17, 2020 22:12
Copy link
Contributor

@pakrym pakrym left a 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.
Copy link
Member

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);
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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))
Copy link
Member

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?

Copy link
Member

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?

Copy link
Contributor Author

@kasobol-msft kasobol-msft Dec 27, 2020

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.

Copy link
Contributor Author

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);
Copy link
Member

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
Copy link
Member

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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();
Copy link
Contributor

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();
Copy link
Contributor

@pakrym pakrym Jan 4, 2021

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));
Copy link
Member

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?

Copy link
Contributor Author

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
Copy link
Member

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kasobol-msft
Copy link
Contributor Author

It seems that allocation optimizations must wait till we target higher .NET.

@check-enforcer
Copy link

check-enforcer bot commented Jan 4, 2021

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:
/check-enforcer evaluate
Typically evaulation only takes a few seconds. If you know that your pull request is not covered by a pipeline and this is expected you can override Check Enforcer using the following command:
/check-enforcer override
Note that using the override command triggers alerts so that follow-up investigations can occur (PRs still need to be approved as normal).

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 prepare-pipelines
This will run a pipeline that analyzes the source tree and creates the pipelines necessary to build and validate your pull request. Once the pipeline has been created you can trigger the pipeline using the following comment:
/azp run net - [service] - ci

@kasobol-msft
Copy link
Contributor Author

/azp run net - core - ci

@azure-pipelines
Copy link

Azure Pipelines failed to run 1 pipeline(s).

@kasobol-msft
Copy link
Contributor Author

/azp run net - core - ci

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@kasobol-msft kasobol-msft merged commit b3e61dd into Azure:master Jan 5, 2021
ghost pushed a commit to Azure/azure-sdk-for-python that referenced this pull request Jan 6, 2021
**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)
rakshith91 pushed a commit to rakshith91/azure-sdk-for-python that referenced this pull request Jan 8, 2021
**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)
annelo-msft pushed a commit to annelo-msft/azure-sdk-for-net that referenced this pull request Feb 17, 2021
* 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.
nffdiogosilva pushed a commit to nffdiogosilva/azure-core that referenced this pull request Oct 17, 2022
**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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants