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

feat: ComputeCredentials implements IBlobSigner #2308

Merged
merged 5 commits into from
Jan 19, 2023

Conversation

amanda-tarafa
Copy link
Contributor

No description provided.

@amanda-tarafa amanda-tarafa added the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jan 16, 2023
@amanda-tarafa amanda-tarafa requested a review from jskeet January 16, 2023 15:12
@amanda-tarafa amanda-tarafa requested a review from a team as a code owner January 16, 2023 15:12
@amanda-tarafa
Copy link
Contributor Author

I've set the do not merge so that I can properly test this code with Storage URL signing before merging.
But this shouls be ready for review.

Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

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

Generally fine - some nits, but nothing particularly significant. Happy to discuss the property vs method for the service account email.

{
Content = new StringContent(dummyServiceAccountEmail)
});
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: indent

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this was fixed on a later commit on the same PR.

/// instance if that happens so fetching the service account default email is re-attempted.
/// </para>
/// </remarks>
public Task<string> DefaultServiceAccountEmailTask => _defaultServiceAccountEmailCache.Value;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It feels slightly odd to have a property that kicks of an HTTP request.

Could we make this a method? It can still be lazy, but GetDefaultServiceAccountEmailAsync() feels a little cleaner to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, agreed.

/// The effective Compute Engine default service account email URL.
/// This takes account of the GCE_METADATA_HOST environment variable.
/// </summary>
internal static string EffectiveComputeDefaultServiceAccountEmailUrl => GetEffectiveMetadataUrl(ComputeDefaultServiceAccountEmailSuffix, DefaultMetadataServerUrl + ComputeDefaultServiceAccountEmailSuffix);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wrap after =>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, done.


/// <summary>
/// Creates and HTTP form from <paramref name="request"/> and posts it to <paramref name="url"/>.
/// If <paramref name="authenticationHeaderValue"/> is not null, it's value it's included as the
Copy link
Collaborator

Choose a reason for hiding this comment

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

"it's value it's => "its value is"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

{
Content = ParameterUtils.CreateFormUrlEncodedContent(request)
};
httpRequest.Headers.Authorization = authenticationHeaderValue;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this still okay if authenticationHeaderValue is null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh nice catch. It is OK now because there's no default value for the Auth header, but best to only change it if there's something to change it to.

var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored")
{
HttpClientFactory = new MockHttpClientFactory(messageHandler),
Clock= clock
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: space before =

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -693,14 +693,5 @@ public async Task SignBlobAsync()
var signature = await credential.SignBlobAsync(Encoding.ASCII.GetBytes("toSign"));
Assert.Equal("VvQqmCec5sESTXmt3ojPodmqZhV30MffzuyCvz6DatyW4uO9IMLWbUmynPYNNnn6w0EIVV0CyrARd56VLxgL+DVdsX726W3dfY13nNJo7i8PyV8R2i8yRLimqqraa1fDb29V2n+FsS0OzBPibDscViQHVlu0PBRApsVsjGG+eB4=", signature);
}

[Fact]
public async Task SignBlobAsync_UnsupportedCredential()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Keep the test, just change the credential type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, duh

/// For instance, to perform IAM API calls for signing blobs of data.
/// </summary>
/// <remarks>Lazy to build one HtppClient only if it is needed.</remarks>
private readonly Lazy<ConfigurableHttpClient> _authenticatedHtppClient;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Htpp => Http

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -234,6 +244,13 @@ private async Task<string> GetDefaultServiceAccountEmail()
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}

private ConfigurableHttpClient BuildAuthenticatedHttpClient()
{
var httpClietnArgs = BuildCreateHtppClientArgs();
Copy link
Collaborator

Choose a reason for hiding this comment

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

Clietn => Client

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/// </remarks>
public async Task<string> SignBlobAsync(byte[] blob, CancellationToken cancellationToken = default)
{
var request = new IamSignBlobRequest
Copy link
Collaborator

Choose a reason for hiding this comment

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

Possibly unwrap to a single line?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep done.

Copy link
Contributor Author

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

All comments adressed on the last commit. PTAL

{
Content = new StringContent(dummyServiceAccountEmail)
});
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this was fixed on a later commit on the same PR.

/// instance if that happens so fetching the service account default email is re-attempted.
/// </para>
/// </remarks>
public Task<string> DefaultServiceAccountEmailTask => _defaultServiceAccountEmailCache.Value;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, agreed.

/// The effective Compute Engine default service account email URL.
/// This takes account of the GCE_METADATA_HOST environment variable.
/// </summary>
internal static string EffectiveComputeDefaultServiceAccountEmailUrl => GetEffectiveMetadataUrl(ComputeDefaultServiceAccountEmailSuffix, DefaultMetadataServerUrl + ComputeDefaultServiceAccountEmailSuffix);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, done.


/// <summary>
/// Creates and HTTP form from <paramref name="request"/> and posts it to <paramref name="url"/>.
/// If <paramref name="authenticationHeaderValue"/> is not null, it's value it's included as the
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

{
Content = ParameterUtils.CreateFormUrlEncodedContent(request)
};
httpRequest.Headers.Authorization = authenticationHeaderValue;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh nice catch. It is OK now because there's no default value for the Auth header, but best to only change it if there's something to change it to.

var initializer = new ComputeCredential.Initializer("http://will.be.ignored", "http://will.be.ignored")
{
HttpClientFactory = new MockHttpClientFactory(messageHandler),
Clock= clock
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -693,14 +693,5 @@ public async Task SignBlobAsync()
var signature = await credential.SignBlobAsync(Encoding.ASCII.GetBytes("toSign"));
Assert.Equal("VvQqmCec5sESTXmt3ojPodmqZhV30MffzuyCvz6DatyW4uO9IMLWbUmynPYNNnn6w0EIVV0CyrARd56VLxgL+DVdsX726W3dfY13nNJo7i8PyV8R2i8yRLimqqraa1fDb29V2n+FsS0OzBPibDscViQHVlu0PBRApsVsjGG+eB4=", signature);
}

[Fact]
public async Task SignBlobAsync_UnsupportedCredential()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, duh

/// For instance, to perform IAM API calls for signing blobs of data.
/// </summary>
/// <remarks>Lazy to build one HtppClient only if it is needed.</remarks>
private readonly Lazy<ConfigurableHttpClient> _authenticatedHtppClient;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@@ -234,6 +244,13 @@ private async Task<string> GetDefaultServiceAccountEmail()
return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
}

private ConfigurableHttpClient BuildAuthenticatedHttpClient()
{
var httpClietnArgs = BuildCreateHtppClientArgs();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

/// </remarks>
public async Task<string> SignBlobAsync(byte[] blob, CancellationToken cancellationToken = default)
{
var request = new IamSignBlobRequest
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep done.

Copy link
Collaborator

@jskeet jskeet left a comment

Choose a reason for hiding this comment

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

Another couple of tiny suggestions, but basically all good.

/// </para>
/// <para>
/// Note that if, when fetching this value, an exception is thrown, the exception is cached and
/// will be rethrown every time this property is accessed. You can create a new <see cref="ComputeCredential"/>
Copy link
Collaborator

Choose a reason for hiding this comment

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

the exception is cached and will be rethrown by the task returned by any future call to this method

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, true.

/// The private key associated with the Compute service account is not known locally
/// by a ComputeCredential. Signing happens by executing a request to the IAM Credentials API
/// which increases latency and counts towards IAM Credentials API quotas. Aditionally, the first
/// time a ComputeCredential is used to sign data, a request to the metadata server is execute to
Copy link
Collaborator

Choose a reason for hiding this comment

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

execute => executed (or just "made")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"made"

public async Task<string> SignBlobAsync(byte[] blob, CancellationToken cancellationToken = default)
{
var request = new IamSignBlobRequest { Payload = blob };
var signBlobUrl = string.Format(GoogleAuthConsts.IamSignEndpointFormatString, await GetDefaultServiceAccountEmailAsync(cancellationToken).ConfigureAwait(false));
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder whether for the sake of both readability and debugging it's worth separating this out:

var request = new IamSignBlobRequest { Payload = blob };
var serviceAccountEmail = await GetDefaultServiceAccountEmailAsync(cancellationToken).ConfigureAwait(false);
var signBlobUrl = string.Format(GoogleAuthConsts.IamSignEndpointFormatString, serviceAccountEmail);
...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, will do that.

Copy link
Contributor Author

@amanda-tarafa amanda-tarafa left a comment

Choose a reason for hiding this comment

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

Latest comments addressed.

I'll keep the PR open until I've run all Storage tests locally. I'll flag any changes if needed.

/// </para>
/// <para>
/// Note that if, when fetching this value, an exception is thrown, the exception is cached and
/// will be rethrown every time this property is accessed. You can create a new <see cref="ComputeCredential"/>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes, true.

/// The private key associated with the Compute service account is not known locally
/// by a ComputeCredential. Signing happens by executing a request to the IAM Credentials API
/// which increases latency and counts towards IAM Credentials API quotas. Aditionally, the first
/// time a ComputeCredential is used to sign data, a request to the metadata server is execute to
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"made"

public async Task<string> SignBlobAsync(byte[] blob, CancellationToken cancellationToken = default)
{
var request = new IamSignBlobRequest { Payload = blob };
var signBlobUrl = string.Format(GoogleAuthConsts.IamSignEndpointFormatString, await GetDefaultServiceAccountEmailAsync(cancellationToken).ConfigureAwait(false));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, will do that.

- This has been commented for a while.
- It was not a unit test to start with as detecting Compute residency executes requests against the metadata server.
@amanda-tarafa amanda-tarafa force-pushed the signer-credentials branch 2 times, most recently from f59d8e6 to 98a104c Compare January 19, 2023 11:50
@amanda-tarafa amanda-tarafa removed the do not merge Indicates a pull request not ready for merge, due to either quality or timing. label Jan 19, 2023
Only TokeRequestExtensions.ExecuteAsync remains as it was public. But it does rely on the newly generalized ones.
Useful for credentials that require differently configured HttpClients but with the same base configuration.
For which, fetches service account email associated to a ComputeCredential. Closes googleapis#1857

Towards googleapis/google-cloud-dotnet#8413
@amanda-tarafa amanda-tarafa merged commit 8d3534d into googleapis:main Jan 19, 2023
@amanda-tarafa amanda-tarafa deleted the signer-credentials branch January 19, 2023 11:59
amanda-tarafa added a commit to amanda-tarafa/google-api-dotnet-client that referenced this pull request Jan 19, 2023
And support libraries from 1.59.0-alpha01 => 1.59.0

Features:

googleapis#2308 ComputeCredential implements IBlobSigner
amanda-tarafa added a commit to amanda-tarafa/google-api-dotnet-client that referenced this pull request Jan 19, 2023
And support libraries from 1.59.0-alpha01 => 1.59.0

Features:

- googleapis#2308 ComputeCredential implements IBlobSigner
amanda-tarafa added a commit that referenced this pull request Jan 19, 2023
And support libraries from 1.59.0-alpha01 => 1.59.0

Features:

- #2308 ComputeCredential implements IBlobSigner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants