Get-sMFA-AWS-Assume_SAML_Role.ps1

<#
     .SYNOPSIS
        Assume AWS Role from SAML.
    .DESCRIPTION
        Assumes AWS Role(s) from SAML asseration.
        Dependencies:
            * System which executes a script must have Microsoft Framework 4.6.2 and above installed.
            * SecureMFA_SupportTools.dll file must be present in script directory.
            * SecureMFA_SupportTools.json configuration file must be present in script directory.
            * Following latest AWS modules must be installed on the system AWS.Tools.SecurityToken and AWS.Tools.Common
            * Uses default system proxy settings
                                     
            Bellow is a sample of valid Json config file with minimal configuration required for script to work:
                {
                "aws_sts_saml_endpoint": "https://FQDN/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices",
                "ui_environment": "MyCompany",
                "proxy_server": "http://proxy.adatum.labnet:8080",
                "proxy_bypass_localaddresses": "false"
                }
 
    .PARAMETER AWS_ProfileName
        Specifies profile name in the credentials file.
 
    .PARAMETER AWS_SessionDurationSeconds
        Specifies how long the assumed role keys are valid after retrieval from AWS STS; the default value is one hour.
 
    .PARAMETER Region
        Specify defaul AWS region for CLI profile.
         
    .PARAMETER Output
        Specify defaul AWS output format for CLI profile.
 
    .NOTES
        Version: 1.0.1.8
        Author: SecureMfa.com
        Creation Date: 11/11/2021
        Purpose/Change: New release
   
    .EXAMPLE
        C:\PS> Get-sMFA-AWS-Assume_SAML_Role -AWS_ProfileName sMFA-SAML
 
        This command will assume available user roles from AWS STS SAML Endpoint and configure 'sMFA-SAML' profile in /.aws/credentials file with aws_access_key_id and aws_secret_access_key
         
#>



#>

Function Get-sMFA-AWS-Assume_SAML_Role {
Param
(
    [Parameter(Mandatory=$false,ParameterSetName="Default")]
    [String]$AWS_ProfileName = "sMFA-SAML",
    [Parameter(Mandatory=$false,ParameterSetName="Default")]
    [int]$AWS_SessionDurationSeconds = 3600,
    [Parameter(Mandatory=$false,ParameterSetName="Default")]
    [string]$region = 'eu-west-1',
    [Parameter(Mandatory=$false,ParameterSetName="Default")]
    [string]$output = 'json'
)

DynamicParam
{
    # create a dictionary to return, and collection of parameters
    $paramDictionary = New-Object -Type System.Management.Automation.RuntimeDefinedParameterDictionary
    $attributeCollection = New-Object -Type System.Collections.ObjectModel.Collection[System.Attribute]
 
    # create a new [string] parameter for all parameter sets, and decorate with a [ValidateSet]
    $dynParam = New-Object -Type System.Management.Automation.RuntimeDefinedParameter("Profile", [String], $attributeCollection)
    $attributes = New-Object System.Management.Automation.ParameterAttribute
    $fname = "Profiles.json"
    $paramOptions = New-Object System.Management.Automation.ValidateSetAttribute -ArgumentList (ConvertFrom-Json (Get-Content (join-path $PSScriptRoot $fname) -Raw))
 
    $attributeCollection.Add($attributes)
    $attributeCollection.Add($paramOptions)
    $paramDictionary.Add("Profile", $dynParam)
 
    return $paramDictionary
}

Process
{    
    #Static Parameters
    $AWS_Directory = "${env:\userprofile}\.aws\"
    $AWS_Directory_cred = "$AWS_Directory`credentials"
    $Event_Source = "SecureMFA_SupportTools"
    $SSO_auth_success = $false;
    $SSO_roles_success = $false;
    $SSO_profile_deleted = $false;

    #Checking Dependencies
    #EventLog source dependency
    $ErrMsg = "ResetOTP EventLog source is missing. Please execute following PS command 'New-EventLog -Source SecureMFA_SupportTools -LogName Application' on the system before using the app."
    if (((Get-ChildItem HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\Application).pschildname | where { $_ -eq $Event_Source} | measure).Count -eq 0) 
    {write-host $ErrMsg -ForegroundColor red; pause; break}
    
    #PS Modules Dependencies
    if ((Get-InstalledModule -Name "AWS.Tools.SecurityToken" -MinimumVersion 4.1.15.0 -ErrorAction SilentlyContinue) -eq $null) {  Throw "PowerShell module 'AWS.Tools.SecurityToken' is required for this command. Please install a module with the following command: Install-Module -Name 'AWS.Tools.SecurityToken' -Repository PSGallery -MinimumVersion 4.1.15.0"}
    if ((Get-InstalledModule -Name "AWS.Tools.Common" -MinimumVersion 4.1.15.0 -ErrorAction SilentlyContinue) -eq $null) {  Throw "PowerShell module 'AWS.Tools.Common' is required for this command. Please install a module with the following command: Install-Module -Name 'AWS.Tools.Common' -Repository PSGallery -MinimumVersion 4.1.15.0"}
    
    #Config file dependency
    if ( $PSBoundParameters.Keys.Contains("Profile") )
    {
        $configfile = (Join-Path -Path $PSScriptRoot -ChildPath ($PSBoundParameters.Profile + '_SecureMFA_SupportTools.json'))
    }
    else {$configfile = (Join-Path -Path $PSScriptRoot -ChildPath SecureMFA_SupportTools.json)}
    #Test config file path.
    $ErrMsg = "$configfile file is missing. Please copy a file to script directory and try again."
    if (!(Test-Path $configfile)) { write-host $ErrMsg -ForegroundColor red; pause; break }
    #DLL file dependency
    $dllpath = (Join-Path -Path $PSScriptRoot -ChildPath SecureMFA_SupportTools.dll)
    $ErrMsg = "$configfile file is missing. Please copy a file to script directory and try again."
    if (!(Test-Path $dllpath)) { write-host $ErrMsg -ForegroundColor red; pause; break }

    #Read JSON file Configuration
    $json = Get-Content -Raw $configfile | ConvertFrom-Json
    $url = $json.aws_sts_saml_endpoint
    $environment = $json.ui_environment
    $webproxy = $json.proxy_server
    $bypassproxyonlocal; if($json.proxy_bypass_localaddresses -eq "true") {$bypassproxyonlocal = 1} else {$bypassproxyonlocal = 0}
    
    #Get user's input if required
    write-host " -- Assume AWS Role from SAML for $environment --" -ForegroundColor Green    
    Write-host "aws_sts_saml_endpoint from JSON configuration file:" $url -ForegroundColor Cyan

    Try {
            [System.Reflection.Assembly]::LoadFile($dllpath) | Out-Null
            [string]$saml_asseration = ""

            #Retreve client token using Hasicorp Vaul OIDC auth flow
            $atoken = [SecureMFA_SupportTools.IDPAUTH]::RetrieveSAMLToken($Url,[ref]$saml_asseration) | Out-String                       
            
            if($saml_asseration.Length -ge 100) {$SSO_auth_success = $true; write-host "SSO authentication has been successful;"} else {write-host "An error occurred during SSO authentication; please check configuration and try again."}
    
            #Decode Base64 and extract Roles from SAMLResponse value
            if($SSO_auth_success) 
                {
                [xml]$DecodedToken = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($saml_asseration))
                $SessionName =  ($DecodedToken.Response.Assertion.AttributeStatement.Attribute | where {$_.name -eq 'https://aws.amazon.com/SAML/Attributes/RoleSessionName'}).AttributeValue
                $SessionRoles = ($DecodedToken.Response.Assertion.AttributeStatement.Attribute | where {$_.name -eq 'https://aws.amazon.com/SAML/Attributes/Role'}).AttributeValue
                write-host "User: $SessionName has claim roles: $SessionRoles"
                }
  
            #Get SAML Roles
            if ($SessionRoles.Count -ge 2) 
            {

                write-host "List of roles from SAML assertation"
                $i = 0
                Foreach ($SessionRole in $SessionRoles)
                {     
                 write-host "[$i]" $SessionRole -ForegroundColor Cyan
                 $i++
                }

                #Select a SAML role
                Do { $SessionRoleNumber = Read-host "Select a role [Number]"}
                while (( $SessionRoleNumber -ge $SessionRoles.Count ) -or ( $SessionRoleNumber -lt 0 ));

                #Converts a positive or negative integer to a positive integer
                $SessionSelectedRole = $SessionRoles[[Math]::Abs($SessionRoleNumber)];
                $SSO_roles_success = $true;
            } elseif ($SessionRoles.Count -eq 1) {$SessionSelectedRole = $SessionRoles ; $SSO_roles_success = $true;} else { write-host "No SAML roles exist for AWS servcie in asssertation." -ForegroundColor Red}


            #If SAML ROLES eixist
            if($SSO_roles_success) 
            {
            #Convert selected role to role and principal arns
            $SessionSelectedRole
            $arns = ($SessionSelectedRole) -split ','
            [string]$role_arn = ($arns | Select-String ":role/") -split ' '
            [string]$principal_arn = ($arns | Select-String ":saml-provider/") -split ' '
       
            # Get STS Credentials with SAML
            $sts = Use-STSRoleWithSAML -PrincipalArn $principal_arn -RoleArn $role_arn -SAMLAssertion $saml_asseration -DurationInSeconds $AWS_SessionDurationSeconds

            # Extract STS responce details
            [string]$aws_access_key_id = $sts.Credentials.AccessKeyId;
            [string]$aws_secret_access_key = $sts.Credentials.SecretAccessKey;
            [string]$aws_session_token = $sts.Credentials.SessionToken;
            [string]$aws_saml_accountid = ($sts.AssumedRoleUser.Arn | select-string -pattern "(?:sts::)(.*?)(?::)").Matches | % {$_.groups[1].Value}
            [string]$aws_saml_role = ($sts.AssumedRoleUser.Arn | select-string -pattern "(?:assumed-role/)(.*?)(?:/)").Matches | % {$_.groups[1].Value}
            [string]$aws_saml_duration_seconds = $AWS_SessionDurationSeconds
            [string]$aws_saml_expiration = $sts.Credentials.Expiration
            [string]$aws_saml_user_display = $sts.AssumedRoleUser.AssumedRoleId
            

            # Create the folder and credentials file if it doesn't exist
            if(!(Test-Path $AWS_Directory)){New-Item -ItemType Directory $AWS_Directory}
            if(!(Test-Path $AWS_Directory_cred)){New-Item -ItemType File $AWS_Directory_cred}
            
            #Delete if profile exist
            if (Get-AWSCredential -ProfileName $AWS_ProfileName) {Remove-AWSCredentialProfile -ProfileName $AWS_ProfileName -ProfileLocation $AWS_Directory_cred -Force; $SSO_profile_deleted = $true ; write-host "AWS credentilas profile [$AWS_ProfileName] has been deleted." -ForegroundColor Yellow}
            
            # Must set return type as array to handle null values
            [Array]$AWS_credentials = Get-Content -Path $AWS_Directory_cred -Raw

            # Add blank line if needed
            if(!($SSO_profile_deleted)) {if($AWS_credentials -and $AWS_credentials[-1] -ne ''){$AWS_credentials += '' }}

            $AWS_credentials += "[$AWS_ProfileName]"
            $AWS_credentials += "output = $output"
            $AWS_credentials += "region = $region"
            $AWS_credentials += "aws_access_key_id = $aws_access_key_id"
            $AWS_credentials += "aws_secret_access_key = $aws_secret_access_key"
            $AWS_credentials += "aws_session_token = $aws_session_token"
            $AWS_credentials += "aws_saml_accountid = $aws_saml_accountid"
            $AWS_credentials += "aws_saml_role = $aws_saml_role"
            $AWS_credentials += "aws_saml_duration = $aws_saml_duration_seconds"

            # Save Changes as UTF-8 without a BOM
            [System.IO.File]::WriteAllLines($AWS_Directory_cred, $AWS_credentials, [System.Text.UTF8Encoding]($False))
            write-host "#############################################################################################" -ForegroundColor Green 
            write-host "AWS credentilas profile [$AWS_ProfileName] has been created." -ForegroundColor Green
            write-host "Credentials file: $AWS_Directory_cred" -ForegroundColor Green
            write-host "User: $aws_saml_user_display" -ForegroundColor Green
            write-host "Role: $aws_saml_role" -ForegroundColor Green
            write-host "AWS token expires: $aws_saml_expiration" -ForegroundColor Green
            write-host "To renew AWS assumed role token run 'Get-sMFA-AWS-Assume_SAML_Role' PowerShell command again." -ForegroundColor Green
            write-host "#############################################################################################" -ForegroundColor Green           
            }
            #NO SAML ROLES eixist
            else { Write-host "No AWS SAML roles were found to be extracted from SAML assertion using $url . Please make sure that your STS is sending ROLE attribute values using the following 'https://aws.amazon.com/SAML/Attributes/Role'" -ForegroundColor Yellow}        
            
            #Cleanup
            $AWS_credentials = $null
        }

    #On error acction
    catch [System.Exception] { 
            $completed = get-date
            $line = $_.InvocationInfo.ScriptLineNumber
            $msg = $_.Exception.Message 

            Write-Host -ForegroundColor Red "Error: $msg"
            Write-EventLog –LogName Application –Source $Event_Source –EntryType Error –EventID 5559 –Message “$msg Executed by: $env:username Computer: $env:computername Line: $line”                 
            }  
    }
}