Class StubSts
A stand-in for AWS STS on a port of this machine, for a test of code that gets its credentials from a web identity token, as a pod with IAM roles for service accounts does, or by assuming a role with other credentials.
It answers AssumeRoleWithWebIdentity and AssumeRole with canned responses and records every request.
public sealed class StubSts : IDisposable
- Inheritance
-
StubSts
- Implements
- Inherited Members
Examples
using var sts = new StubSts();
sts.Enqueue(StubStsResponse.Credentials(new AwsCredentials("ASIAEXAMPLE", "secret", "token", DateTimeOffset.UtcNow.AddHours(1))));
await File.WriteAllTextAsync(tokenFile, "a-token");
var provider = new WebIdentityCredentialsProvider(tokenFile, "arn:aws:iam::123456789012:role/orders", sts.Endpoint);
using var client = new DynamoDbClient(new DynamoDbClientOptions { Region = AwsRegion.FromName("us-east-1"), Credentials = provider, HttpHandler = stub });
// ... call the code under test, then:
var token = sts.Requests[0].WebIdentityToken;
Remarks
Why a server and not a handler. A provider of credentials has its own HTTP client, and HttpHandler carries the requests to DynamoDB and no others. So a StubHandler does not
see the fetch, and a test without this server would call the real STS. The stub is an HTTP/1.1 server on 127.0.0.1 and a free port, which it listens on from the constructor on. It makes no call to a network.
Two ways to reach it. Hand the provider the endpoint, which reads no variable, so the test can run in parallel with any other:
new WebIdentityCredentialsProvider(tokenFile, roleArn, sts.Endpoint). Or set AWS_ENDPOINT_URL_STS to Endpoint yourself, with AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN, to go through the
default chain as a pod does. new AssumeRoleCredentialsProvider(source, roleArn, options: new AssumeRoleOptions { StsEndpoint = sts.Endpoint, Region = region }) reads no variable either; without
Region it looks for the region to sign for in the environment. The stub never touches the process environment, because that would make every test that uses it unsafe to run beside the others; a test that changes the variables runs on its own, by the rule of its test framework.
The token file is yours. The provider reads a file that the test writes, and the stub does not touch the disk. It takes any token and any signature of AssumeRole, and validates nothing; the
Authorization line is in Headers.
The stub picks the response for a request in this order: the next response queued with Enqueue(StubStsResponse); DefaultResponse. If there is neither, it answers 400 with the code StubStsNoResponse, so a
test that forgot to queue reads STS rejected the web identity token with StubStsNoResponse., or ... assume the role ...: StubStsNoResponse., in the CredentialsUnavailableException of the provider. A provider retries some answers by itself: each attempt is a
request here, as for a StubHandler, and it waits a second between attempts on its own clock. That includes an attempt it gave up on: a provider on the system clock gives an attempt 5 seconds, so on a busy machine a queued
response can go to an attempt nobody waits for any more. A test that does not count fetches sets DefaultResponse, which answers them all.
Requests are answered one at a time, in the order they arrive. The stub is thread-safe, and with one port per instance tests with an instance each run in parallel.
Constructors
StubSts()
Initializes a new instance of the StubSts class, which listens from here on.
public StubSts()
Properties
DefaultResponse
Gets or sets the response for requests that no queued response answers, for a test that does not care how many fetches there are. null (the default) answers the error that names StubStsNoResponse.
public StubStsResponse? DefaultResponse { get; set; }
Property Value
Endpoint
Gets the address of the stub, for example http://127.0.0.1:50123/: what a provider takes, or what AWS_ENDPOINT_URL_STS holds as AbsoluteUri.
public Uri Endpoint { get; }
Property Value
Requests
Gets the requests received so far, oldest first. The list is a snapshot; get it again after more requests. It stays readable after Dispose().
public IReadOnlyList<RecordedStsRequest> Requests { get; }
Property Value
Methods
Dispose()
Stops listening and closes an open connection: a client in the middle of a request sees the connection end. It returns once the server has stopped.
public void Dispose()
Enqueue(StubStsResponse)
Queues a response for a future request. Each queued response answers exactly one request, in the order queued.
public void Enqueue(StubStsResponse response)
Parameters
responseStubStsResponseThe response.
Exceptions
- ArgumentNullException
responseis null.