Public/Persistence/Set-FederatedIdentity.ps1
|
function Set-FederatedIdentity { [CmdletBinding(SupportsShouldProcess = $true, DefaultParameterSetName = "GitHub")] param ( [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias('resource-id')] [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleter( "Microsoft.ManagedIdentity/userAssignedIdentities" )][string]$Id, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceNameCompleterAttribute( "Microsoft.ManagedIdentity/userAssignedIdentities", "ResourceGroupName" )] [Alias('identity-name', 'user-assigned-identity')] [string]$ManagedIdentityName, [Parameter(Mandatory = $false)] [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceGroupCompleterAttribute()] [Alias('rg', 'resource-group')] [string]$ResourceGroupName, [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true)] [Alias('federated-identity-name', 'credential-name')] [string]$Name = 'federatedCredential', [Parameter(Mandatory = $true, ParameterSetName = "GitHub")] [Alias('github-organization')] [string]$GitHubOrganization, [Parameter(Mandatory = $true, ParameterSetName = "GitHub")] [Alias('github-repository')] [string]$GitHubRepository, [Parameter(Mandatory = $false, ParameterSetName = "GitHub")] [Alias('branch-name')] [string]$Branch = 'main', [Parameter(Mandatory = $true, ParameterSetName = "Custom")] [Alias('issuer-url')] [string]$Issuer, [Parameter(Mandatory = $true, ParameterSetName = "Custom")] [string]$Subject, [Parameter(Mandatory = $false, ParameterSetName = "Custom")] [Parameter(Mandatory = $false, ParameterSetName = "GitHub")] [string[]]$Audiences = @("api://AzureADTokenExchange"), [Parameter(Mandatory = $false, ParameterSetName = "Remove")] [switch]$Remove, [Parameter(Mandatory = $false, ParameterSetName = "Get")] [switch]$Get ) begin { [void] $ResourceGroupName Write-Verbose " Starting function $($MyInvocation.MyCommand.Name)" $MyInvocation.MyCommand.Name | Invoke-BlackCat } process { # Resolve managed identity name to resource ID if needed if ($ManagedIdentityName -and -not $Id) { Write-Verbose "Resolving ManagedIdentity: $ManagedIdentityName" $uami = Get-ManagedIdentity -Name $ManagedIdentityName -OutputFormat Object if ($uami) { $Id = $uami.id } else { Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message "Managed identity not found: $ManagedIdentityName" -Severity 'Error' return } } if (-not $Id) { Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message "No managed identity specified. Use -Id or -ManagedIdentityName (-Name is the FIC credential name)" -Severity 'Error' return } $ficUri = '{0}{1}/federatedIdentityCredentials/{2}?api-version=2023-01-31' -f $script:SessionVariables.armUri, $Id, $Name # Handle Get operation if ($Get) { $ficListUri = '{0}{1}/federatedIdentityCredentials?api-version=2023-01-31' -f $script:SessionVariables.armUri, $Id $ficListParams = @{ Uri = $ficListUri Headers = $script:authHeader Method = 'GET' UserAgent = $script:SessionVariables.userAgent } try { $result = Invoke-RestMethod @ficListParams return $result.value } catch { Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message $_.Exception.Message -Severity 'Error' return } } # Handle Remove operation if ($Remove) { if ($PSCmdlet.ShouldProcess("Federated Identity Credential: $Name", "Remove")) { $ficParams = @{ Uri = $ficUri Headers = $script:authHeader Method = 'DELETE' UserAgent = $script:SessionVariables.userAgent } try { Invoke-RestMethod @ficParams | Out-Null Write-Host "Removed FIC: $Name" -ForegroundColor Green return $true } catch { if ($_.Exception.Response.StatusCode -eq 404) { Write-Warning "FIC not found: $Name" } else { Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message $_.Exception.Message -Severity 'Error' } return $false } } return } # Build issuer and subject based on parameter set switch ($PSCmdlet.ParameterSetName) { "GitHub" { $issuerUrl = "https://token.actions.githubusercontent.com" $subjectClaim = "repo:$($GitHubOrganization)/$($GitHubRepository):ref:refs/heads/$Branch" $description = "GitHub Actions: $GitHubOrganization/$GitHubRepository (branch: $Branch)" } "Custom" { $issuerUrl = $Issuer $subjectClaim = $Subject $description = "Custom OIDC: $Issuer" } } # Create or update FIC if ($PSCmdlet.ShouldProcess($description, "Set Federated Identity Credential")) { try { $ficBody = @{ properties = @{ issuer = $issuerUrl subject = $subjectClaim audiences = $Audiences } } | ConvertTo-Json $ficParams = @{ Headers = $script:authHeader Uri = $ficUri Method = 'PUT' ContentType = 'application/json' Body = $ficBody UserAgent = $script:SessionVariables.userAgent } $result = Invoke-RestMethod @ficParams Write-Host "Set FIC: $Name" -ForegroundColor Green return $result } catch { Write-Message -FunctionName $MyInvocation.MyCommand.Name -Message $_.Exception.Message -Severity 'Error' return $null } } } <# .SYNOPSIS Sets or removes federated identity credentials for managed identities. .DESCRIPTION Manages federated identity credentials for UAMIs to enable OIDC-based authentication. Supports: - GitHub Actions integration (default) - Custom OIDC providers with custom issuer/subject - Removal of existing credentials - Querying existing credentials .PARAMETER Id Full ARM resource ID of the UAMI. .PARAMETER ManagedIdentityName UAMI display name (resolved via Get-ManagedIdentity). .PARAMETER ResourceGroupName Resource group for tab-completion. Not required. .PARAMETER Name FIC name. Defaults to 'federatedCredential'. .PARAMETER GitHubOrganization GitHub organization name (for GitHub Actions). .PARAMETER GitHubRepository GitHub repository name (for GitHub Actions). .PARAMETER Branch GitHub branch name. Defaults to 'main'. .PARAMETER Issuer Custom OIDC issuer URL (e.g., https://myoidc.blob.core.windows.net/oidc). .PARAMETER Subject Custom OIDC subject claim (e.g., 'repo:org/repo:ref:refs/heads/main'). .PARAMETER Audiences Audience claim(s). Defaults to 'api://AzureADTokenExchange'. .PARAMETER Remove Removes the specified FIC. .PARAMETER Get Lists all FICs for the UAMI. .EXAMPLE Set-FederatedIdentity -ManagedIdentityName "uami-cicd" -GitHubOrganization "myorg" -GitHubRepository "myrepo" Creates GitHub Actions FIC for main branch. .EXAMPLE Set-FederatedIdentity -ManagedIdentityName "uami-prod" -Name "custom-fic" -Issuer "https://bc.blob.core.windows.net/oidc" -Subject "blackcat-token-exchange" Creates custom OIDC FIC. .EXAMPLE Set-FederatedIdentity -ManagedIdentityName "uami-prod" -Name "old-fic" -Remove Removes the specified FIC. .EXAMPLE Set-FederatedIdentity -ManagedIdentityName "uami-prod" -Get Lists all FICs for the UAMI. .OUTPUTS [PSCustomObject] FIC details (when creating/updating) [Boolean] Success status (when removing) [Array] List of FICs (when using -Get) .NOTES Author: BlackCat Security Framework Required permissions: - Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/write - Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/delete .LINK MITRE ATT&CK Tactic: TA0003 - Persistence https://attack.mitre.org/tactics/TA0003/ .LINK MITRE ATT&CK Technique: T1098.001 - Additional Cloud Credentials https://attack.mitre.org/techniques/T1098/001/ #> } |