// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 using System; using System.Collections.Generic; using Amazon; using Amazon.KeyManagementService; using Amazon.SecurityToken; using Amazon.SecurityToken.Model; using AWS.EncryptionSDK.Core; using static ExampleUtils.ExampleUtils; /// /// Demonstrates implementing a Custom Client Supplier. /// This Client Supplier will create KMS Clients with different IAM roles, /// depending on the Region passed. /// // When implementing a Custom Client Supplier ALWAYS extend the Base class, // not the interface. public class RegionalRoleClientSupplier : ClientSupplierBase { /// /// Maps a Region to the Arn of the IAM Role the client supplier will /// use when supplying a client. /// private static Dictionary _regionIAMRoleMap; /// /// Amazon Security Token Service, or STS, allows customers to fetch /// temporary credentials. /// private static IAmazonSecurityTokenService _stsClient; public RegionalRoleClientSupplier() { _regionIAMRoleMap = GetRegionIAMRoleMap(); _stsClient = new AmazonSecurityTokenServiceClient(); } /// /// This is the meat of a Client Supplier. /// Whenever the AWS Encryption SDK needs to create a KMS client, /// it will call GetClient for the regions in which it needs to call /// KMS. /// In this example, we utilize a Dictionary /// to map regions to particular IAM Roles. /// We use Amazon Security Token Service to fetch temporary credentials, /// and then provision a Key Management Service (KMS) Client /// with those credentials and the input region. /// /// GetClientInput is just the region /// A KMS Client /// If the Region requested is missing from the RegionIAMRole Map /// If the Assume Role call fails protected override IAmazonKeyManagementService _GetClient(GetClientInput input) { // Check our RegionIAMRole map for the provided region. // If it is missing, throw a Missing Region Exception. if (!_regionIAMRoleMap.ContainsKey(input.Region)) throw new MissingRegionException(input.Region); // Otherwise, call Amazon STS to assume the role. var iamArn = _regionIAMRoleMap[input.Region]; var task = _stsClient.AssumeRoleAsync(new AssumeRoleRequest { RoleArn = iamArn, DurationSeconds = 900, // 15 minutes is the minimum value RoleSessionName = "ESDK-NET-Custom-Client-Example" }); AssumeRoleResponse response; // Await the async response try { response = task.Result; } catch (Exception e) { throw new AssumeRoleException(input.Region, iamArn, e); } // Return a KMS Client with the credentials from STS and the Region. return new AmazonKeyManagementServiceClient( response.Credentials, RegionEndpoint.GetBySystemName(input.Region)); } } // Custom Exceptions SHOULD extend from the Library's Base Exception. // This is a quirk of using Dafny to generate the Encryption SDK. // The Encryption SDK will handle dotnet's System.Exception, // but the exception message will be altered. // By extending from the Library's Base Exception, // you can ensure the exception's message will be as intended. public class MissingRegionException : AwsCryptographicMaterialProvidersBaseException { public MissingRegionException(string region) : base( $"Region {region} is not supported by this client supplier") { } } public class AssumeRoleException : AwsCryptographicMaterialProvidersBaseException { public AssumeRoleException(string region, string roleArn, Exception e) : base( $"Attempt to assume Role Arn {roleArn} for Region {region}" + $" encountered unexpected: {e.GetType()}: \"{e.Message}\"") { // At this time, the Encryption SDK only retains exception messages, // and not the entire stack trace. // As such, it is helpful to manually log the exceptions // (ideally, a logging framework would be used, instead of console). Console.Out.Write(e); } }