/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using Amazon.Runtime.Internal; using Amazon.Runtime.CredentialManagement.Internal; using Amazon.Util; using System; using System.Collections.Generic; using System.Globalization; using System.IO; #if !BCL35 using Amazon.Runtime.Credentials.Internal; #endif using Amazon.Runtime.Internal.Settings; using Amazon.Util.Internal; namespace Amazon.Runtime.CredentialManagement { /// /// Factory to construct different types of AWSCredentials based on a profile. /// public static class AWSCredentialsFactory { private static HashSet CallbackProfileTypes = new HashSet() { CredentialProfileType.SAMLRoleUserIdentity, CredentialProfileType.AssumeRoleExternalMFA, CredentialProfileType.AssumeRoleMFA, #if !BCL35 CredentialProfileType.SSO, #endif }; private const string RoleSessionNamePrefix = "aws-dotnet-sdk-session-"; /// /// Gets the AWSCredentials for this profile if CanCreateAWSCredentials is true /// and AWSCredentials can be created. Throws an exception otherwise. /// /// See for a list of AWSCredentials returned by this method. /// /// The profile to get AWSCredentials for. /// The profile source, for profiles that reference other profiles. /// AWSCredentials for this profile. public static AWSCredentials GetAWSCredentials(CredentialProfile profile, ICredentialProfileSource profileSource) { return GetAWSCredentials(profile.Name, profileSource, profile.Options, profile.Region, false); } /// /// Gets the AWSCredentials for this profile if CanCreateAWSCredentials is true /// and AWSCredentials can be created. Throws an exception otherwise. /// /// See for a list of AWSCredentials returned by this method. /// /// The options to get AWSCredentials for. /// The profile source, for options that reference other profiles. /// AWSCredentials for the options given. public static AWSCredentials GetAWSCredentials(CredentialProfileOptions options, ICredentialProfileSource profileSource) { return GetAWSCredentials(null, profileSource, options, null, false); } /// /// Gets the AWSCredentials for this profile if CanCreateAWSCredentials is true /// and AWSCredentials can be created. Throws an exception otherwise. /// /// See for a list of AWSCredentials returned by this method. /// /// The profile to get AWSCredentials for. /// The profile source, for profiles that reference other profiles. /// If true, throw a descriptive exception for any credentials that would not operate as-is. /// In other words, any credentials that require programmatic callbacks at runtime. /// AWSCredentials for this profile. public static AWSCredentials GetAWSCredentials(CredentialProfile profile, ICredentialProfileSource profileSource, bool nonCallbackOnly) { return GetAWSCredentials(profile.Name, profileSource, profile.Options, profile.Region, nonCallbackOnly); } /// /// Gets the AWSCredentials for this profile if CanCreateAWSCredentials is true /// and AWSCredentials can be created. Throws an exception otherwise. /// /// See for a list of AWSCredentials returned by this method. /// /// The options to get AWSCredentials for. /// The profile source, for options that reference other profiles. /// If true, throw a descriptive exception for any credentials that would not operate as-is. /// In other words, any credentials that require programmatic callbacks at runtime. /// AWSCredentials for the options given. public static AWSCredentials GetAWSCredentials(CredentialProfileOptions options, ICredentialProfileSource profileSource, bool nonCallbackOnly) { return GetAWSCredentials(null, profileSource, options, null, nonCallbackOnly); } /// /// Return the credentials for the profile if valid credentials can created. /// /// The profile to create credentials with. /// The profile source, for profiles that reference other profiles. /// The credentials for the profile. /// True if credentials can be created from the profile, false otherwise. public static bool TryGetAWSCredentials(CredentialProfile profile, ICredentialProfileSource profileSource, out AWSCredentials credentials) { credentials = GetAWSCredentialsInternal(profile.Name, profile.ProfileType, profile.Options, profile.Region, profileSource, false); return credentials != null; } /// /// Return the credentials for the profile if valid credentials can created. /// /// The options to get AWSCredentials for. /// The profile source, for profiles that reference other profiles. /// The credentials for the profile. /// True if credentials can be created from the profile, false otherwise. public static bool TryGetAWSCredentials(CredentialProfileOptions options, ICredentialProfileSource profileSource, out AWSCredentials credentials) { var profileType = CredentialProfileTypeDetector.DetectProfileType(options); credentials = GetAWSCredentialsInternal(null, profileType, options, null, profileSource, false); return credentials != null; } /// /// Determine if the profileType will generate AWSCredentials that require a callback to be set on them. /// /// /// internal static bool IsCallbackRequired(CredentialProfileType? profileType) { return profileType.HasValue && CallbackProfileTypes.Contains(profileType.Value); } private static AWSCredentials GetAWSCredentials( string profileName, ICredentialProfileSource profileSource, CredentialProfileOptions options, RegionEndpoint stsRegion, bool nonCallbackOnly) { #if !BCL35 var ssoTokenFileCache = new SSOTokenFileCache( CryptoUtilFactory.CryptoInstance, new FileRetriever(), new DirectoryRetriever()); #endif var profileType = CredentialProfileTypeDetector.DetectProfileType(options); if (nonCallbackOnly && profileType.HasValue && IsCallbackRequired(profileType.Value)) { if (profileType == CredentialProfileType.AssumeRoleExternalMFA || profileType == CredentialProfileType.AssumeRoleMFA) { var mfaMessage = profileName == null ? "The credential options represent AssumeRoleAWSCredentials that require an MFA. This is not allowed here. " + "Please use credential options for AssumeRoleAWSCredentials that don't require an MFA, or a different type of credentials." : String.Format(CultureInfo.InvariantCulture, "The profile [{0}] is an assume role profile that requires an MFA. This type of profile is not allowed here. " + "Please use an assume role profile that doesn't require an MFA, or a different type of profile.", profileName); throw new InvalidOperationException(mfaMessage); } #if !BCL35 else if (profileType == CredentialProfileType.SSO && !ssoTokenFileCache.Exists(options)) { var ssoMessage = profileName == null ? $"The credential options represent {nameof(SSOAWSCredentials)}. This is not allowed here. " + "Please use a different type of credentials." : String.Format(CultureInfo.InvariantCulture, "The profile [{0}] is an SSO profile. This type of profile is not allowed here. " + "Please use a different type of profile.", profileName); throw new InvalidOperationException(ssoMessage); } #endif else if (profileType == CredentialProfileType.SAMLRoleUserIdentity) { var samlMessage = profileName == null ? "The credential options represent FederatedAWSCredentials that specify a user identity. This is not allowed here. " + "Please use credential options for FederatedAWSCredentials without an explicit user identity, or a different type of credentials." : String.Format(CultureInfo.InvariantCulture, "The profile [{0}] is a SAML role profile that specifies a user identity. This type of profile is not allowed here. " + "Please use a SAML role profile without an explicit user identity, or a different type of profile.", profileName); throw new InvalidOperationException(samlMessage); } } return GetAWSCredentialsInternal(profileName, profileType, options, stsRegion, profileSource, true); } private static AWSCredentials GetAWSCredentialsInternal( string profileName, CredentialProfileType? profileType, CredentialProfileOptions options, RegionEndpoint stsRegion, ICredentialProfileSource profileSource, bool throwIfInvalid, HashSet profileLoopAvoidance = null) { if (profileType.HasValue) { switch (profileType) { case CredentialProfileType.Basic: case CredentialProfileType.BasicWithServices: case CredentialProfileType.BasicWithGlobalEndpoint: case CredentialProfileType.BasicWithServicesAndGlobalEndpoint: return new BasicAWSCredentials(options.AccessKey, options.SecretKey); case CredentialProfileType.Session: case CredentialProfileType.SessionWithServices: case CredentialProfileType.SessionWithGlobalEndpoint: case CredentialProfileType.SessionWithServicesAndGlobalEndpoint: return new SessionAWSCredentials(options.AccessKey, options.SecretKey, options.Token); case CredentialProfileType.AssumeRole: case CredentialProfileType.AssumeRoleWithServices: case CredentialProfileType.AssumeRoleWithGlobalEndpoint: case CredentialProfileType.AssumeRoleWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleExternal: case CredentialProfileType.AssumeRoleExternalWithServices: case CredentialProfileType.AssumeRoleExternalWithGlobalEndpoint: case CredentialProfileType.AssumeRoleExternalWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleMFA: case CredentialProfileType.AssumeRoleMFAWithServices: case CredentialProfileType.AssumeRoleMFAWithGlobalEndpoint: case CredentialProfileType.AssumeRoleMFAWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleExternalMFA: case CredentialProfileType.AssumeRoleExternalMFAWithServices: case CredentialProfileType.AssumeRoleExternalMFAWithGlobalEndpoint: case CredentialProfileType.AssumeRoleExternalMFAWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleSessionName: case CredentialProfileType.AssumeRoleSessionNameWithServices: case CredentialProfileType.AssumeRoleSessionNameWithGlobalEndpoint: case CredentialProfileType.AssumeRoleSessionNameWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleMFASessionNameWithServices: case CredentialProfileType.AssumeRoleMFASessionNameWithGlobalEndpoint: case CredentialProfileType.AssumeRoleMFASessionNameWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleExternalSessionName: case CredentialProfileType.AssumeRoleMFASessionName: case CredentialProfileType.AssumeRoleExternalMFASessionName: case CredentialProfileType.AssumeRoleExternalMFASessionNameWithServices: case CredentialProfileType.AssumeRoleExternalMFASessionNameWithGlobalEndpoint: case CredentialProfileType.AssumeRoleExternalMFASessionNameWithServicesAndGlobalEndpoint: if (profileName != null) { if (profileLoopAvoidance == null) { profileLoopAvoidance = new HashSet(); } else if (profileLoopAvoidance.Contains(profileName)) { var sourceMessage = string.Format(CultureInfo.InvariantCulture, "Error reading profile [{0}]: the source profile definition is cyclical.", profileName); return ThrowOrReturnNull(sourceMessage, null, throwIfInvalid); } profileLoopAvoidance.Add(profileName); } AWSCredentials sourceCredentials; try { sourceCredentials = GetSourceAWSCredentials(options.SourceProfile, profileSource, throwIfInvalid, profileLoopAvoidance); } catch (InvalidDataException e) { var sourceMessage = profileName == null ? string.Format(CultureInfo.InvariantCulture, "Error reading source profile [{0}] for the credential options provided.", options.SourceProfile) : string.Format(CultureInfo.InvariantCulture, "Error reading source profile [{0}] for profile [{1}].", options.SourceProfile, profileName); return ThrowOrReturnNull(sourceMessage, e, throwIfInvalid); } #pragma warning disable CS0612 // Type or member is obsolete var roleSessionName = options.RoleSessionName ?? RoleSessionNamePrefix + AWSSDKUtils.CorrectedUtcNow.Ticks; #pragma warning restore CS0612 // Type or member is obsolete var assumeRoleOptions = new AssumeRoleAWSCredentialsOptions() { ExternalId = options.ExternalID, MfaSerialNumber = options.MfaSerial }; return new AssumeRoleAWSCredentials(sourceCredentials, options.RoleArn, roleSessionName, assumeRoleOptions); case CredentialProfileType.AssumeRoleCredentialSource: case CredentialProfileType.AssumeRoleCredentialSourceWithGlobalEndpoint: case CredentialProfileType.AssumeRoleCredentialSourceWithServices: case CredentialProfileType.AssumeRoleCredentialSourceWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleCredentialSourceSessionName: case CredentialProfileType.AssumeRoleCredentialSourceSessionNameWithServices: case CredentialProfileType.AssumeRoleCredentialSourceSessionNameWithGlobalEndpoint: case CredentialProfileType.AssumeRoleCredentialSourceSessionNameWithServicesAndGlobalEndpoint: // get credentials specified by credentialSource try { sourceCredentials = GetCredentialSourceAWSCredentials(options.CredentialSource, throwIfInvalid); } catch (InvalidDataException e) { var sourceMessage = profileName == null ? string.Format(CultureInfo.InvariantCulture, "Error reading credential source [{0}] for the credential options provided.", options.CredentialSource) : string.Format(CultureInfo.InvariantCulture, "Error reading credential source [{0}] for profile [{1}].", options.CredentialSource, profileName); return ThrowOrReturnNull(sourceMessage, e, throwIfInvalid); } #pragma warning disable CS0612 // Type or member is obsolete roleSessionName = options.RoleSessionName ?? RoleSessionNamePrefix + AWSSDKUtils.CorrectedUtcNow.Ticks; #pragma warning restore CS0612 // Type or member is obsolete assumeRoleOptions = new AssumeRoleAWSCredentialsOptions(); return new AssumeRoleAWSCredentials(sourceCredentials, options.RoleArn, roleSessionName, assumeRoleOptions); case CredentialProfileType.AssumeRoleWithWebIdentity: case CredentialProfileType.AssumeRoleWithWebIdentityWithServices: case CredentialProfileType.AssumeRoleWithWebIdentityWithGlobalEndpoint: case CredentialProfileType.AssumeRoleWithWebIdentityWithServicesAndGlobalEndpoint: case CredentialProfileType.AssumeRoleWithWebIdentitySessionName: case CredentialProfileType.AssumeRoleWithWebIdentitySessionNameWithServices: case CredentialProfileType.AssumeRoleWithWebIdentitySessionNameWithGlobalEndpoint: case CredentialProfileType.AssumeRoleWithWebIdentitySessionNameWithServicesAndGlobalEndpoint: return new AssumeRoleWithWebIdentityCredentials(options.WebIdentityTokenFile, options.RoleArn, options.RoleSessionName); #if !BCL35 case CredentialProfileType.SSO: { var ssoCredentialsOptions = new SSOAWSCredentialsOptions { SessionName = options.SsoSession }; return new SSOAWSCredentials( options.SsoAccountId, options.SsoRegion, options.SsoRoleName, options.SsoStartUrl, ssoCredentialsOptions ); } #endif case CredentialProfileType.SAMLRole: case CredentialProfileType.SAMLRoleWithServices: case CredentialProfileType.SAMLRoleWithGlobalEndpoint: case CredentialProfileType.SAMLRoleWithServicesAndGlobalEndpoint: case CredentialProfileType.SAMLRoleUserIdentity: case CredentialProfileType.SAMLRoleUserIdentityWithServices: case CredentialProfileType.SAMLRoleUserIdentityWithGlobalEndpoint: case CredentialProfileType.SAMLRoleUserIdentityWithServicesAndGlobalEndpoint: if (UserCrypto.IsUserCryptAvailable) { var federatedOptions = new FederatedAWSCredentialsOptions() { STSRegion = stsRegion, UserIdentity = options.UserIdentity, ProfileName = profileName }; return new FederatedAWSCredentials(new SAMLEndpointManager().GetEndpoint(options.EndpointName), options.RoleArn, federatedOptions); } else { return ThrowOrReturnNull("Federated credentials are not available on this platform.", null, throwIfInvalid); } case CredentialProfileType.CredentialProcess: return new ProcessAWSCredentials(options.CredentialProcess); default: var defaultMessage = profileName == null ? string.Format(CultureInfo.InvariantCulture, "Invalid ProfileType {0} for the credential options provided.", profileType) : string.Format(CultureInfo.InvariantCulture, "Invalid ProfileType {0} for credential profile [{1}].", profileType, profileName); return ThrowOrReturnNull(defaultMessage, null, throwIfInvalid); } } else { return ThrowInvalidOrReturnNull(profileName, throwIfInvalid); } } private static AWSCredentials GetCredentialSourceAWSCredentials(string credentialSourceType, bool throwIfInvalid) { AWSCredentials credentials; CredentialSourceType type; try { type = (CredentialSourceType)Enum.Parse(typeof(CredentialSourceType), credentialSourceType, true); } catch { return ThrowOrReturnNull(string.Format(CultureInfo.InvariantCulture, "Credential source [{0}] is invalid.", credentialSourceType), null, throwIfInvalid); } switch (type) { case CredentialSourceType.Ec2InstanceMetadata: credentials = DefaultInstanceProfileAWSCredentials.Instance; break; case CredentialSourceType.Environment: credentials = new EnvironmentVariablesAWSCredentials(); break; case CredentialSourceType.EcsContainer: var relativeUri = Environment.GetEnvironmentVariable(ECSTaskCredentials.ContainerCredentialsURIEnvVariable); var fullUri = Environment.GetEnvironmentVariable(ECSTaskCredentials.ContainerCredentialsFullURIEnvVariable); if (string.IsNullOrEmpty(relativeUri) && string.IsNullOrEmpty(fullUri)) { return ThrowOrReturnNull($"Cannot fetch credentials from container - neither {ECSTaskCredentials.ContainerCredentialsURIEnvVariable} or {ECSTaskCredentials.ContainerCredentialsFullURIEnvVariable}" + " environment variables are set.", null, throwIfInvalid); } credentials = new ECSTaskCredentials(null); break; default: return ThrowOrReturnNull(string.Format(CultureInfo.InvariantCulture, "Credential source [{0}] is not implemented.", credentialSourceType), null, throwIfInvalid); } return credentials; } private static AWSCredentials GetSourceAWSCredentials(string sourceProfileName, ICredentialProfileSource profileSource, bool throwIfInvalid, HashSet profileLoopAvoidance = null) { CredentialProfile sourceProfile = null; if (profileSource.TryGetProfile(sourceProfileName, out sourceProfile)) { if (sourceProfile.CanCreateAWSCredentials) { var sourceCredentials = GetAWSCredentialsInternal(sourceProfile.Name, sourceProfile.ProfileType, sourceProfile.Options, sourceProfile.Region, profileSource, throwIfInvalid, profileLoopAvoidance); if (sourceCredentials == null) { return ThrowOrReturnNull(string.Format(CultureInfo.InvariantCulture, "Could not get credentials from source profile [{0}].", sourceProfileName), null, throwIfInvalid); } return sourceCredentials; } else { return ThrowInvalidOrReturnNull(sourceProfileName, throwIfInvalid); } } else { return ThrowOrReturnNull(string.Format(CultureInfo.InvariantCulture, "Source profile [{0}] was not found.", sourceProfileName), null, throwIfInvalid); } } private static BasicAWSCredentials ThrowInvalidOrReturnNull(string profileName, bool doThrow) { var message = profileName == null ? "The credential options provided are not valid. Please ensure the options contain a valid combination of properties." : string.Format(CultureInfo.InvariantCulture, "Credential profile [{0}] is not valid. Please ensure the profile contains a valid combination of properties.", profileName); return ThrowOrReturnNull(message, null, doThrow); } private static BasicAWSCredentials ThrowOrReturnNull(string message, Exception innerException, bool doThrow) { if (doThrow) { throw new InvalidDataException(message, innerException); } else { return null; } } } }