/******************************************************************************* * 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. * ***************************************************************************** * __ _ _ ___ * ( )( \/\/ )/ __) * /__\ \ / \__ \ * (_)(_) \/\/ (___/ * * AWS SDK for .NET */ using Amazon.Runtime; using Amazon.Runtime.Internal.Settings; using Amazon.Runtime.Internal.Util; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace Amazon.Util { /// /// This class allows profiles supporting AWS credentials and SAML-based authentication to be registered with /// the SDK so that they can later be reference by a profile name. The credential profiles will be available /// for use in the AWS Toolkit for Visual Studio and the AWS Tools for Windows PowerShell. /// /// The credentials are stored under the current users AppData folder encrypted using Windows Data Protection API. /// /// /// To reference a profile from an application's App.config or Web.config use the AWSProfileName setting. /// /// <?xml version="1.0" encoding="utf-8" ?> /// <configuration> /// <appSettings> /// <add key="AWSProfileName" value="development"/> /// </appSettings> /// </configuration> /// /// /// [Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.CredentialManagement.NetSDKCredentialsFile, SharedCredentialsFile, or SAMLEndpointManager. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")] public static class ProfileManager { // if a profile does not contain a profile type entry, we assume AWS credentials public const string AWSCredentialsProfileType = "AWS"; public const string SAMLRoleProfileType = "SAML"; public static bool IsAvailable { get { return UserCrypto.IsUserCryptAvailable; } } /// /// Registers an AWS credentials profile that can later be referenced by the profileName. /// This profile will only be visible for the current user. /// /// Name given to the AWS credentials. /// The AWS access key id /// The AWS secret key public static void RegisterProfile(string profileName, string accessKeyId, string secretKey) { AWSCredentialsProfile.Persist(profileName, accessKeyId, secretKey); } /// /// /// Registers a role-based profile to be used with SAML authentication. The profile contains /// details of the role to be assumed when AWS credentials are requested based on the role and /// a reference to a SAML endpoint profile containing details of the endpoint to be called to /// authenticate the user. /// /// /// If user identity information is not supplied then the identity of the logged-in user will /// be used when authenticaton is performed against the endpoint referenced in the SAML endpoint /// profile. If identity is provided, no password information is stored in the role profile and /// the user must supply the password for the identity prior to authentication with the endpoint. /// /// /// Name to be assigned to the profile /// /// The name assigned to the endpoint settings, previously saved with RegisterSAMLEndpoint. /// /// /// The arn of the role that the user wants to assume when using this profile. This /// must be one of the set returned by the saml endpoint when the user authenticates. /// /// /// Optional. By default the identity of the logged-in user will be used when authentication /// is performed - the user will not be prompted to supply a password. By supplying a custom /// identity for this parameter, the user will be prompted to supply the password for the /// identity prior to authentication. /// public static void RegisterSAMLRoleProfile(string profileName, string endpointName, string roleArn, string userIdentity) { RegisterSAMLRoleProfile(profileName, endpointName, roleArn, userIdentity, null); } /// /// /// Registers a role-based profile to be used with SAML authentication. The profile contains /// details of the role to be assumed when AWS credentials are requested based on the role and /// a reference to a SAML endpoint profile containing details of the endpoint to be called to /// authenticate the user. /// /// /// If user identity information is not supplied then the identity of the logged-in user will /// be used when authenticaton is performed against the endpoint referenced in the SAML endpoint /// profile. If identity is provided, no password information is stored in the role profile and /// the user must supply the password for the identity prior to authentication with the endpoint. /// /// /// Name to be assigned to the profile /// /// The name assigned to the endpoint settings, previously saved with RegisterSAMLEndpoint. /// /// /// The arn of the role that the user wants to assume when using this profile. This /// must be one of the set returned by the saml endpoint when the user authenticates. /// /// /// Optional. By default the identity of the logged-in user will be used when authentication /// is performed - the user will not be prompted to supply a password. By supplying a custom /// identity for this parameter, the user will be prompted to supply the password for the /// identity prior to authentication. /// /// /// Set for profiles intended to be used in regions where a region-specific STS endpoint /// must be used (eg cn-north-1). If left empty/null, the global sts.amazonaws.com endpoint /// will be used when credentials are obtained for this profile. /// public static void RegisterSAMLRoleProfile(string profileName, string endpointName, string roleArn, string userIdentity, string stsRegion) { SAMLRoleProfile.Persist(profileName, endpointName, roleArn, userIdentity, null, stsRegion); } /// /// Registers an endpoint to be used in conjunction with SAML role profiles. The role profiles /// reference the endpoint settings to obtain the actual endpoint and any customization settings /// needed to perform authentication. /// /// Name to be assigned to the endpoint settings. /// The full uri of the authentication endpoint. /// /// The authentication type to use when performing calls against the endpoint. Valid values are 'NTLM', /// 'Digest', 'Kerberos' and 'Negotiate'. The default if not configured (null/empty string) is 'Kerberos'. /// /// The unique id assigned to the new settings. public static string RegisterSAMLEndpoint(string endpointName, Uri endpoint, string authenticationType) { return SAMLEndpointSettings.Persist(endpointName, endpoint, authenticationType); } /// /// Deletes the settings for an AWS credentials or SAML role profile from the SDK account store. /// /// The name of the profile to remove. public static void UnregisterProfile(string profileName) { var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); var os = ReadProfileSettings(settings, profileName); if (os != null) { settings.Remove(os.UniqueKey); PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredProfiles, settings); } } /// /// Lists all profile names registered with the SDK account store. /// /// The profile names. public static IEnumerable ListProfileNames() { var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); return settings.Select(os => os.GetValueOrDefault(SettingsConstants.DisplayNameField, null)).ToList(); } /// /// Loads and returns all available credential profiles registered in the store. /// /// Collection of profiles. public static IEnumerable ListProfiles() { var profiles = new List(); var profileNames = ListProfileNames(); foreach (var profileName in profileNames) { try { if (SAMLRoleProfile.CanCreateFrom(profileName)) profiles.Add(SAMLRoleProfile.LoadFrom(profileName)); else if (AWSCredentialsProfile.CanCreateFrom(profileName)) profiles.Add(AWSCredentialsProfile.LoadFrom(profileName)); } catch (Exception e) { Logger.GetLogger(typeof(ProfileManager)).Error(e, "Error loading AWS credential or SAML role profile '{0}'", profileName); } } return profiles; } /// /// Checks if a given profile is known in the SDK credential store. /// /// The name of the profile to test for existence /// True if the profile exists. public static bool IsProfileKnown(string profileName) { return (ReadProfileSettings(profileName) != null); } /// /// Copies the contents of the source profile to the destination. If the destination /// profile does not exist a new profile is created. Note that if the destination /// profile exists, all keys it contains are removed and replaced with keys from the /// source profile. /// /// The name of the profile to copy from. /// The name of the profile to create or update. /// The unique id assigned to the destination settings. public static string CopyProfileSettings(string sourceProfileName, string destinationProfileName) { var sourceSettings = ReadProfileSettings(sourceProfileName); if (sourceSettings == null) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "An AWS credentials or SAML role profile with name '{0}' could not be found.", sourceProfileName)); return CopyProfileSettings(sourceSettings, destinationProfileName); } /// /// Copies the contents of the source profile to the destination. If the destination /// profile does not exist a new profile is created. Note that if the destination /// profile exists, all keys it contains are removed and replaced with keys from the /// source profile. /// /// The source profile to copy keys and values from. /// The name of the profile to create or update. /// The unique id assigned to the destination settings. public static string CopyProfileSettings(SettingsCollection.ObjectSettings source, string destinationProfileName) { var allSettings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); var destination = ReadProfileSettings(allSettings, destinationProfileName); // overwrite with new object if dest exists, not merge, otherwise we can potentially mix credential // profile types if (destination == null) destination = allSettings.NewObjectSettings(Guid.NewGuid().ToString()); else destination = allSettings.NewObjectSettings(destination.UniqueKey); destination[SettingsConstants.DisplayNameField] = destinationProfileName; foreach (var k in source.Keys) { if (!k.Equals(SettingsConstants.DisplayNameField)) destination[k] = source[k]; } PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredProfiles, allSettings); return destination.UniqueKey; } /// /// Tries to get the AWS credentials from a profile in the SDK account store. /// /// The profile to get the credentials for. /// Outputs the credentials for the profile. /// Returns true if the profile exists otherwise false is returned. public static bool TryGetAWSCredentials(string profileName, out AWSCredentials credentials) { credentials = null; try { AWSCredentialsProfile profile; if (TryGetProfile(profileName, out profile)) credentials = profile.Credentials; } catch (Exception e) { Logger.GetLogger(typeof(ProfileManager)).Error(e, "Error loading AWS credentials from profile {0}", profileName); } return credentials != null; } /// /// Gets the AWS credentials from a profile in the SDK account store. /// /// The profile to get the credentials for. /// The AWS credentials for the profile. /// Thrown if the profile does not exist public static AWSCredentials GetAWSCredentials(string profileName) { AWSCredentialsProfile profile; if (TryGetProfile(profileName, out profile)) return profile.Credentials; throw new AmazonClientException(string.Format(CultureInfo.InvariantCulture, "A profile named {0} has not been registered or contains invalid data.", profileName)); } /// /// Returns the profile with the specified name, if it has been registered in the SDK store. /// /// The name of the registered profile /// The loaded profile data public static ProfileSettingsBase GetProfile(string profileName) { if (!IsProfileKnown(profileName)) throw new AmazonClientException(string.Format(CultureInfo.InvariantCulture, "A profile named {0} has not been registered.", profileName)); var profile = GetProfile(profileName) ?? (ProfileSettingsBase) GetProfile(profileName); if (profile == null) throw new AmazonClientException(string.Format(CultureInfo.InvariantCulture, "A profile named {0} was found but could not be loaded.", profileName)); return profile; } /// /// Returns the persisted data in the SDK store as a profile of the specified type T. /// /// The name of the profile holding the settings. /// The loaded profile. An exception is thrown if the profile could not be loaded. /// Thrown if the profile does not exist /// /// Currently supported profile types: AWSCredentialsProfile and SAMLRoleProfile. /// public static T GetProfile(string profileName) where T : ProfileSettingsBase { if (typeof(T) == typeof(AWSCredentialsProfile)) return AWSCredentialsProfile.LoadFrom(profileName) as T; if (typeof(T) == typeof(SAMLRoleProfile)) return SAMLRoleProfile.LoadFrom(profileName) as T; throw new ArgumentException("Unrecognized profile type parameter"); } /// /// Tries to load the specified profile data corresponding to profile type T from a named /// profile in the SDK account store. /// /// The name of the profile holding the settings. /// The loaded profile data. /// Returns true if the profile exists otherwise false is returned. /// /// Currently supported profile types: AWSCredentialsProfile and SAMLRoleProfile. /// public static bool TryGetProfile(string profileName, out T profile) where T : ProfileSettingsBase { profile = null; try { if (typeof(T) == typeof(AWSCredentialsProfile)) profile = AWSCredentialsProfile.LoadFrom(profileName) as T; else if (typeof(T) == typeof(SAMLRoleProfile)) profile = SAMLRoleProfile.LoadFrom(profileName) as T; else throw new ArgumentException("Unrecognized profile type parameter", (typeof(T).FullName)); } catch (Exception e) { Logger.GetLogger(typeof(ProfileManager)).Error(e, "Unable to load profile {0}, unknown profile, missing/invalid data or unrecognized profile type.", profileName); } return profile != null; } /// /// Attempts to load the settings defining a SAML endpoint. /// /// The name assigned to the settings for the endpoint. /// The instantiated endpoint. /// True if the settings were successfully loaded. public static bool TryGetSAMLEndpoint(string endpointName, out SAMLEndpointSettings endpointSettings) { endpointSettings = null; try { endpointSettings = SAMLEndpointSettings.LoadFrom(endpointName); } catch (Exception e) { Logger.GetLogger(typeof(ProfileManager)).Error(e, "Unable to load SAML endpoint profile '{0}', unknown profile or missing/invalid data.", endpointName); } return endpointSettings != null; } /// /// Loads the settings defining a SAML endpoint. /// /// The name assigned to the settings for the endpoint. /// The loaded settings. An exception is thrown if they could not be loaded. /// Thrown if the endpoint settings do not exist. public static SAMLEndpointSettings GetSAMLEndpoint(string endpointName) { SAMLEndpointSettings endpointSettings; if (!TryGetSAMLEndpoint(endpointName, out endpointSettings)) throw new AmazonClientException(string.Format(CultureInfo.InvariantCulture, "A SAML endpoint profile with name {0} has not been registered or is invalid.", endpointName)); return endpointSettings; } internal static SettingsCollection.ObjectSettings ReadProfileSettings(string profileName) { var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); return ReadProfileSettings(settings, profileName); } internal static SettingsCollection.ObjectSettings ReadProfileSettings(SettingsCollection settings, string profileName) { return settings.FirstOrDefault(x => string.Equals(x[SettingsConstants.DisplayNameField], profileName, StringComparison.OrdinalIgnoreCase)); } internal static SettingsCollection.ObjectSettings ReadSettings(SettingsCollection settings, string settingsKey) { return settings.FirstOrDefault(x => string.Equals(x.UniqueKey, settingsKey, StringComparison.OrdinalIgnoreCase)); } } /// /// Common base contract for all types of credential and role profiles. /// [Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.CredentialManagement.CredentialProfile. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")] public abstract class ProfileSettingsBase { /// /// The user-defined name for the settings. /// public string Name { get; protected set; } /// /// The unique id of the profile in the backing store. /// public string UniqueId { get; protected set; } /// /// Saves the profile data to backing store, returning the unique id /// assigned to the data. /// public abstract string Persist(); protected static SettingsCollection.ObjectSettings LoadCredentialsProfile(string profileName) { var os = ProfileManager.ReadProfileSettings(profileName); if (os == null) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "An AWS credentials or SAML role profile with name '{0}' could not be found.", profileName)); return os; } } /// /// The persisted data for a set of AWS credentials. At a minimum this /// is access key and secret key data. /// [Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.CredentialManagement.CredentialProfile. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")] public class AWSCredentialsProfile : ProfileSettingsBase { public BasicAWSCredentials Credentials { get; private set; } /// /// Tests if an AWSCredentialsProfile instance could be instantiated from /// the persisted settings data. /// /// The name given to the persisted settings (previously verified as existing). /// True if the settings are compatible with an AWSCredentialsProfile type. public static bool CanCreateFrom(string profileName) { var os = LoadCredentialsProfile(profileName); return CanCreateFrom(os); } /// /// Tests if an AWSCredentialsProfile instance could be instantiated from /// the persisted settings data. /// /// The persisted settings. /// True if the settings are compatible with an AWSCredentialsProfile type. public static bool CanCreateFrom(SettingsCollection.ObjectSettings os) { var osProfileType = os.GetValueOrDefault(SettingsConstants.ProfileTypeField, null); // legacy AWS profiles will not have the type key present if (osProfileType == null || osProfileType.Equals(ProfileManager.AWSCredentialsProfileType, StringComparison.OrdinalIgnoreCase)) { try { Validate(os); return true; } catch (InvalidDataException) { var msg = (string.Format(CultureInfo.InvariantCulture, "Profile '{0}' indicates AWS credential type but does not contain AWS credential key materials", os[SettingsConstants.DisplayNameField])); Logger.GetLogger(typeof(AWSCredentialsProfile)).InfoFormat(msg); } } return false; } /// /// Instantiates an AWSCredentialsProfile instance from the specified profile name. /// /// The name of the profile holding the settings. /// New credentials profile instance. An exception is thrown if the profile data is invalid. public static AWSCredentialsProfile LoadFrom(string profileName) { var os = LoadCredentialsProfile(profileName); return LoadFrom(os); } /// /// Instantiates an AWSCredentialsProfile instance from the supplied settings collection. /// /// The settings representing the stored profile. /// New credentials profile instance. An exception is thrown if the profile data is invalid. public static AWSCredentialsProfile LoadFrom(SettingsCollection.ObjectSettings os) { if (os == null) throw new ArgumentNullException("os"); if (!CanCreateFrom(os)) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Profile '{0}' does not contain AWS credential materials", os[SettingsConstants.DisplayNameField])); Validate(os); var accessKeyId = os.GetValueOrDefault(SettingsConstants.AccessKeyField, null); var secretkey = os.GetValueOrDefault(SettingsConstants.SecretKeyField, null); return new AWSCredentialsProfile(os[SettingsConstants.DisplayNameField], accessKeyId, secretkey); } /// /// Validates the contents of the specified profile. /// /// The name of the AWS credentials profile to validate. /// Thrown if the profile settings fail to validate. public static void Validate(string profileName) { var os = LoadCredentialsProfile(profileName); Validate(os); } /// /// Verifies that the persisted settings contains the minimal viable data to /// instantiate an AWSCredentialsProfile instance. /// /// The persisted settings. /// Thrown if the profile settings fail to validate. private static void Validate(SettingsCollection.ObjectSettings os) { var accessKeyId = os.GetValueOrDefault(SettingsConstants.AccessKeyField, null); if (accessKeyId == null) throw new InvalidDataException("Missing or invalid access key value in the profile settings."); var secretkey = os.GetValueOrDefault(SettingsConstants.SecretKeyField, null); if (secretkey == null) throw new InvalidDataException("Missing or invalid secret key value in the profile settings."); } /// /// Persists the profile data to the store file. /// /// The unique ID assigned to the settings. public override string Persist() { return Persist(Name, Credentials.GetCredentials().AccessKey, Credentials.GetCredentials().SecretKey); } /// /// Creates or updates the profile data in the store file. /// /// The unique ID assigned to the settings. public static string Persist(string profileName, string accessKeyId, string secretKey) { var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); var os = ProfileManager.ReadProfileSettings(settings, profileName); if (os == null) os = settings.NewObjectSettings(Guid.NewGuid().ToString()); os[SettingsConstants.ProfileTypeField] = ProfileManager.AWSCredentialsProfileType; os[SettingsConstants.DisplayNameField] = profileName; os[SettingsConstants.AccessKeyField] = accessKeyId; os[SettingsConstants.SecretKeyField] = secretKey; PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredProfiles, settings); return os.UniqueKey; } private AWSCredentialsProfile(string profileName, string accessKeyId, string secretKey) { Name = profileName; Credentials = new BasicAWSCredentials(accessKeyId, secretKey); } } /// /// The persisted data for a SAML endpoint. One or more role profiles /// will reference this to obtain the common endpoint and other data needed /// to perform authentication with a set of user credentials. /// [Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.CredentialManagement.SAMLEndpoint. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")] public class SAMLEndpointSettings : ProfileSettingsBase { /// /// The default authentication type to use when attempting to perform an /// authentication call against the configured endpoint. /// public static readonly string DefaultAuthenticationType = "Kerberos"; /// /// The authentication endpoint which must be a HTTPS scheme. /// public Uri Endpoint { get; private set; } private string _authenticationType = null; /// /// The authentication type to use when calling the endpoint. /// public string AuthenticationType { get { return string.IsNullOrEmpty(_authenticationType) ? DefaultAuthenticationType : _authenticationType; } } /// /// Tests if a SAMLEndpointSettings instance could be instantiated from /// the persisted settings data. /// /// The name given to the persisted settings. /// True if the settings are compatible. public static bool CanCreateFrom(string endpointName) { var os = LoadSettings(endpointName); return CanCreateFrom(os); } /// /// Tests if a SAMLEndpointSettings instance could be instantiated from /// the persisted settings data. /// /// The persisted settings. /// True if the settings are compatible. public static bool CanCreateFrom(SettingsCollection.ObjectSettings os) { var endpoint = os.GetValueOrDefault(SettingsConstants.EndpointField, null); return !string.IsNullOrEmpty(endpoint); } /// /// Instantiates an instance from settings stored with the specified name. /// /// The name of the endpoint settings in the store. /// Profile instance or an exception if the profile data does not exist/contains invalid data. public static SAMLEndpointSettings LoadFrom(string endpointName) { var os = LoadSettings(endpointName); return LoadFrom(os); } /// /// Instantiates an instance from the supplied settings. /// /// The persisted settings. /// Profile instance or an exception if the profile data is invalid. public static SAMLEndpointSettings LoadFrom(SettingsCollection.ObjectSettings os) { if (os == null) throw new ArgumentNullException("os"); if (!CanCreateFrom(os)) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Endpoint settings '{0}' does not contain SAML endpoint materials", os[SettingsConstants.DisplayNameField])); Validate(os); var endpoint = os.GetValueOrDefault(SettingsConstants.EndpointField, null); string authenticationType = os.GetValueOrDefault(SettingsConstants.AuthenticationTypeField, null); return new SAMLEndpointSettings(os[SettingsConstants.DisplayNameField], new Uri(endpoint, UriKind.RelativeOrAbsolute), authenticationType); } /// /// Validates the contents of the specified endpoint settings. /// /// The name of the SAML endpoint settings to validate. /// Thrown if the settings fail to validate. public static void Validate(string endpointName) { var os = LoadSettings(endpointName); Validate(os); } /// /// Verifies that the persisted settings contains the minimal viable data to /// instantiate a SAMLEndpointSettings instance. /// /// The persisted settings. /// Thrown if the settings fail to validate. private static void Validate(SettingsCollection.ObjectSettings os) { var endpoint = os.GetValueOrDefault(SettingsConstants.EndpointField, null); if (endpoint == null) throw new InvalidDataException("Missing endpoint value in the profile settings."); try { var u = new Uri(endpoint); if (!string.Equals(u.Scheme, "https", StringComparison.OrdinalIgnoreCase)) throw new InvalidDataException("The scheme of the endpoint must be HTTPS."); } catch (UriFormatException e) { throw new InvalidDataException("The configured endpoint is not valid.", e); } } /// /// Persists the settings to the storage file. /// /// The unique id assigned to the profile public override string Persist() { return Persist(Name, Endpoint, AuthenticationType); } /// /// Creates or updates the settings data for a SAML endpoint in the backing store file. An error is /// thrown if the scheme for the endpoint is not https. /// /// The name of the settings to create or update /// The authentication endpoint /// Optional authentication type to use when performing calls against the endpoint /// The unique id assigned to the profile public static string Persist(string settingsName, Uri endpoint, string authenticationType) { if (!string.Equals(endpoint.Scheme, "https", StringComparison.OrdinalIgnoreCase)) throw new AmazonClientException("Endpoint uri is not Https protocol."); var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredSAMLEndpoints); var os = ProfileManager.ReadProfileSettings(settings, settingsName); if (os == null) os = settings.NewObjectSettings(Guid.NewGuid().ToString()); os[SettingsConstants.EndpointField] = endpoint.ToString(); os[SettingsConstants.DisplayNameField] = settingsName; if (!string.IsNullOrEmpty(authenticationType) && !authenticationType.Equals(DefaultAuthenticationType, StringComparison.OrdinalIgnoreCase)) os[SettingsConstants.AuthenticationTypeField] = authenticationType; PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredSAMLEndpoints, settings); return os.UniqueKey; } /// /// Constructs an endpoint settings instance. /// /// The user-defined name to assign to the settings. /// /// The absolute uri, including any query and relyingParty data, of the endpoint. /// /// /// The authentication type to use when performing requests against the endpoint. /// private SAMLEndpointSettings(string settingsName, Uri endpoint, string authenticationType) { Name = settingsName; Endpoint = endpoint; if (!string.IsNullOrEmpty(authenticationType)) _authenticationType = authenticationType; } private static SettingsCollection.ObjectSettings LoadSettings(string endpointName) { var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredSAMLEndpoints); var os = ProfileManager.ReadProfileSettings(settings, endpointName); if (os == null) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "SAML endpoint settings with name '{0}' could not be found.", endpointName)); return os; } } /// /// /// The persisted data for a saml role profile for a user. This profile /// references an endpoint profile containing the actual endpoint to be used, and /// adds details of the role to be assumed when the profile is selected. /// /// /// Optionally the profile can store a username and domain to be used during /// authentication (default behavior, if this is not specified, is to use the user's /// default network credentials). /// /// [Obsolete("This class is obsolete and will be removed in a future release. Please use Amazon.Runtime.CredentialProfile. Visit http://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/net-dg-config-creds.html for further details.")] public class SAMLRoleProfile : ProfileSettingsBase { private object _synclock = new object(); /// /// The ARN of the role that is to be assumed. /// public string RoleArn { get; internal set; } /// /// If non-default network credentials are to used contains /// the user identity (in domain\user format, domain optional) that /// should be used to supply credentials when the profile is used in /// authentication. The user must be prompted to supply the /// corresponding password prior to authentication. /// public string UserIdentity { get; internal set; } /// /// If a specific user identity was specified in the profile, /// returns true to indicate a password needs to be obtained from /// the user before obtaining network credentials to be used on /// authentication. The default is to use the credentials /// associated with the currently logged-in user or process to /// perform authentication, which does not require the user to be /// prompted. /// public bool UseDefaultUserIdentity { get { return string.IsNullOrEmpty(UserIdentity); } } /// /// /// For regions with a region-specific endpoint for STS (eg cn-north-1) this /// field can be set to ensure calls to obtain temporary credentials /// after successful authentication are forwarded to the correct regional /// endpoint. /// /// /// This field does not need to be set when running in a region for /// which the sts.amazonaws.com endpoint is valid. /// /// public string Region { get; private set; } private SAMLImmutableCredentials _session = null; /// /// Retrieves the active credential session, if any, associated with the /// role profile. /// /// /// The current credentials valid for the role specified in the profile. Returns /// null if no active session is available, or the active session has expired. /// /// /// When a user successfully authenticates and receives temporary AWS /// credentials for a role, the profile is updated with details of the /// session. When the profile is loaded by other processes or tools, if /// session data is present and still valid it can be retrieved using this /// method avoiding the need to re-authenticate and get additional temporary /// credentials. /// public SAMLImmutableCredentials GetCurrentSession() { SAMLImmutableCredentials session = null; lock (_synclock) { if (_session != null && _session.Expires <= AWSSDKUtils.CorrectedUtcNow) { UpdateProfileSessionData(null); _session = null; } session = _session; } return session; } /// /// Persists the current credentials to a 'session' key in the RoleSessions.json file. /// This enables external applications and tools using the same profile to obtain credentials /// without needing to separately re-authenticate the user prior to expiry of the current /// credentials. After persisting the session data it can be retrieved using GetCurrentSession(). /// /// /// Although the credentials are temporary we still encrypt the stored data when at rest in /// the sdk credential store. /// /// /// The current credentials valid for the role specified in the profile. /// public void PersistSession(SAMLImmutableCredentials credentials) { lock (_synclock) { UpdateProfileSessionData(credentials); _session = credentials; } } /// /// Stores or clears the persisted session data. /// /// private void UpdateProfileSessionData(SAMLImmutableCredentials credentials) { string sessionData = null; if (credentials != null) sessionData = credentials.ToJson(); Persist(sessionData); } /// /// The endpoint settings from which the actual endpoint to use in authentication /// is obtained. /// public SAMLEndpointSettings EndpointSettings { get; internal set; } /// /// Tests if a SAMLRoleProfile instance could be instantiated from /// the persisted settings data. /// /// The name given to the persisted settings. /// True if the settings are compatible with a SAMLRoleProfile type. public static bool CanCreateFrom(string profileName) { var os = LoadCredentialsProfile(profileName); return CanCreateFrom(os); } /// /// Tests if a SAMLRoleProfile instance could be instantiated from /// the persisted settings data. /// /// The persisted settings. /// True if the settings are compatible with a SAMLRoleProfile type. public static bool CanCreateFrom(SettingsCollection.ObjectSettings os) { var osProfileType = os.GetValueOrDefault(SettingsConstants.ProfileTypeField, null); return osProfileType != null && osProfileType.Equals(ProfileManager.SAMLRoleProfileType, StringComparison.OrdinalIgnoreCase); } /// /// Instantiates an instance from settings stored with the specified name. /// /// The name of the endpoint profile. /// Profile instance or an exception if the profile data does not exist/contains invalid data. public static SAMLRoleProfile LoadFrom(string profileName) { var os = LoadCredentialsProfile(profileName); return LoadFrom(os); } /// /// Instantiates an instance from the supplied settings. In addition to the profile settings /// the SDK will inspect for a RoleSessions.json file containing active session data and if /// an entry for the profile is present, will add the session data to the returned profile /// object. /// /// The persisted settings. /// Profile instance or an exception if the profile data is invalid. public static SAMLRoleProfile LoadFrom(SettingsCollection.ObjectSettings os) { if (os == null) throw new ArgumentNullException("os"); if (!CanCreateFrom(os)) throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Profile '{0}' does not contain SAML role materials", os[SettingsConstants.DisplayNameField])); Validate(os); var endpointName = os[SettingsConstants.EndpointNameField]; var endpointSettings = ProfileManager.GetSAMLEndpoint(endpointName); var profileName = os[SettingsConstants.DisplayNameField]; var roleArn = os[SettingsConstants.RoleArnField]; var userIdentity = os.GetValueOrDefault(SettingsConstants.UserIdentityField, null); var region = os.GetValueOrDefault(SettingsConstants.Region, null); SAMLImmutableCredentials activeCredentials = LoadActiveSessionCredentials(profileName); return new SAMLRoleProfile(profileName, endpointSettings, roleArn, userIdentity, activeCredentials, region); } /// /// Validates the contents of the specified profile. /// /// The name of the SAML role profile to validate. /// Thrown if the profile settings fail to validate. public static void Validate(string profileName) { var os = LoadCredentialsProfile(profileName); Validate(os); } /// /// Validates that the presented settings would result in a valid role profile /// instance. /// /// The persisted settings. /// Thrown if the profile settings fail to validate. private static void Validate(SettingsCollection.ObjectSettings os) { var endpointName = os.GetValueOrDefault(SettingsConstants.EndpointNameField, null); if (endpointName == null) throw new InvalidDataException("Missing EndpointName data."); SAMLEndpointSettings endpointSettings; if (!ProfileManager.TryGetSAMLEndpoint(endpointName, out endpointSettings)) throw new InvalidDataException(string.Format(CultureInfo.InvariantCulture, "Endpoint settings with the name '{0}' could not be found.", endpointName)); if (string.IsNullOrEmpty(os[SettingsConstants.RoleArnField])) throw new InvalidDataException("Missing role ARN data."); } /// /// Stores the data in the role profile to the backing store file. /// public override string Persist() { return Persist(Name, EndpointSettings.Name, RoleArn, UserIdentity, null, Region); } private string Persist(string session) { return Persist(Name, EndpointSettings.Name, RoleArn, UserIdentity, session, Region); } /// /// /// Registers a role-based profile to be used with SAML authentication. The profile contains /// details of the role to be assumed when AWS credentials are requested based on the role and /// a reference to a SAML endpoint profile containing details of the endpoint to be called to /// authenticate the user. /// /// /// If user identity information is not supplied then the identity of the logged-in user will /// be used when authenticaton is performed against the endpoint referenced in the SAML endpoint /// profile. If identity is provided, no password information is stored in the role profile and /// the user must supply the password for the identity prior to authentication with the endpoint. /// /// /// Name to be assigned to the profile /// /// The name of the settings in the SAML endpoints file containing details of the /// endpoint to authenticate with. /// /// /// The arn of the role that the user wants to assume when using this profile. This /// must be one of the set returned by the saml endpoint when the user authenticates. /// /// /// Optional, can be used to prompt the user for a password for the account when authentication /// is performed from a system that is not domain-joined. /// /// /// Optional, details of the currently active credentials for the role that we want to /// persist into the profile for other tools or processes to pick up, avoiding the need /// to continually re-authenticate the user as they switch between tools. The active session, /// if any, is stored separately from the profile using the file RoleSessions.json. /// /// /// Set for profiles intended to be used in regions where a region-specific STS endpoint /// must be used (eg cn-north-1). If left empty/null, the global sts.amazonaws.com endpoint /// will be used when credentials are obtained for this profile. /// /// The unique id assigned to the profile. public static string Persist(string profileName, string endpointSettingsName, string roleArn, string userIdentity, string session, string region) { if (string.IsNullOrEmpty(profileName) || string.IsNullOrEmpty(endpointSettingsName) || string.IsNullOrEmpty(roleArn)) throw new ArgumentException("Profile name, endpoint settings name and role ARN must be supplied."); SAMLEndpointSettings endpointSettings; if (!ProfileManager.TryGetSAMLEndpoint(endpointSettingsName, out endpointSettings)) { var msg = string.Format(CultureInfo.CurrentCulture, "Failed to load SAML endpoint settings with name {0}", endpointSettingsName); throw new ArgumentException(msg); } var settings = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredProfiles); var os = ProfileManager.ReadProfileSettings(settings, profileName); if (os == null) os = settings.NewObjectSettings(Guid.NewGuid().ToString()); os[SettingsConstants.ProfileTypeField] = ProfileManager.SAMLRoleProfileType; os[SettingsConstants.DisplayNameField] = profileName; os[SettingsConstants.EndpointNameField] = endpointSettings.Name; os[SettingsConstants.RoleArnField] = roleArn; os[SettingsConstants.UserIdentityField] = userIdentity; if (!string.IsNullOrEmpty(region)) os[SettingsConstants.Region] = region; PersistActiveSessionCredentials(profileName, session); PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredProfiles, settings); return os.UniqueKey; } /// /// Tests for and loads any active session credentials for the specified profile. The session data /// exists in a separate file from the profile, RoleSessions.json. /// /// /// private static SAMLImmutableCredentials LoadActiveSessionCredentials(string profileName) { SAMLImmutableCredentials sessionCredentials = null; var roleSessions = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredRoleSessions); if (roleSessions != null) { var settings = ProfileManager.ReadSettings(roleSessions, profileName); if (settings != null) { var roleSession = settings[SettingsConstants.RoleSession]; sessionCredentials = SAMLImmutableCredentials.FromJson(roleSession); } } return sessionCredentials; } /// /// Stores the supplied session data into the RoleSessions.json backing file. /// /// /// private static void PersistActiveSessionCredentials(string profileName, string session) { var roleSessions = PersistenceManager.Instance.GetSettings(SettingsConstants.RegisteredRoleSessions); if (string.IsNullOrEmpty(session) && roleSessions == null) return; var settings = ProfileManager.ReadSettings(roleSessions, profileName); if (settings == null) settings = roleSessions.NewObjectSettings(profileName); settings[SettingsConstants.RoleSession] = session; PersistenceManager.Instance.SaveSettings(SettingsConstants.RegisteredRoleSessions, roleSessions); } /// /// Constructs a profile data instance that will use the specified network identity /// during authentication with configured endpoint. /// /// The user-defined name of the profile that sourced this data. /// The settings for the authentication endpoint. /// The role that should be assumed on successful authentication. /// The credentials to supply in authentication, in domain\user format. /// /// Deserialized credential data from the profile, if still valid. Null if the profile does not /// contain any active credentials, or the credentials it did hold are now invalid. /// /// /// Set for profiles intended to be used in regions where a region-specific STS endpoint /// must be used (eg cn-north-1). If left empty/null, the global sts.amazonaws.com endpoint /// will be used when credentials are obtained for this profile. /// private SAMLRoleProfile(string profileName, SAMLEndpointSettings endpointSettings, string roleArn, string userIdentity, SAMLImmutableCredentials currentSession, string region) { Name = profileName; EndpointSettings = endpointSettings; RoleArn = roleArn; UserIdentity = userIdentity; _session = currentSession; Region = region; } } }