Public/Persistence/Set-FederatedIdentity.ps1

function Set-FederatedIdentity {
    [CmdletBinding(SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByResourceId")]
        [Alias('resource-id')]
        [Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters.ResourceIdCompleter(
            "Microsoft.ManagedIdentity/userAssignedIdentities"
        )][string]$Id,

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = "ByName")]
        [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)]
        [Alias('github-organization')]
        [string]$GitHubOrganization,

        [Parameter(Mandatory = $true)]
        [Alias('github-repository')]
        [string]$GitHubRepository,

        [Parameter(Mandatory = $false)]
        [Alias('branch-name')]
        [string]$Branch = 'main'
    )

    begin {
        [void] $ResourceGroupName # Only used to trigger the ResourceGroupCompleter
        
        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-Host " Looking up Managed Identity: $ManagedIdentityName..." -ForegroundColor Cyan
            $uami = Get-ManagedIdentity -Name $ManagedIdentityName -OutputFormat Object
            if ($uami) {
                $Id = $uami.id
                Write-Host " Found: $($uami.name)" -ForegroundColor Green
            }
            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 "Either -Id or -ManagedIdentityName must be provided" -Severity 'Error'
            return
        }

        if ($PSCmdlet.ShouldProcess("Federated Identity Credential for $GitHubOrganization/$GitHubRepository on branch $Branch")) {
            try {
                $baseUri = 'https://management.azure.com'
                $uri = '{0}{1}/federatedIdentityCredentials/{2}?api-version=2023-01-31' -f $baseUri, $Id, $Name

                $body = @{
                    properties = @{
                        issuer    = "https://token.actions.githubusercontent.com"
                        subject   = "repo:$($GitHubOrganization)/$($GitHubRepository):ref:refs/heads/$Branch"
                        audiences = @("api://AzureADTokenExchange")
                    }
                } | ConvertTo-Json

                $requestParam = @{
                    Headers     = $script:authHeader
                    Uri         = $uri
                    Method      = 'PUT'
                    ContentType = 'application/json'
                    Body        = $body
                    UserAgent   = $($sessionVariables.userAgent)
                }

                (Invoke-RestMethod @requestParam)
            }
            catch {
                Write-Message $($MyInvocation.MyCommand.Name) -Message $($_.Exception.Message) -Severity 'Error'
            }
        }
    }
<#
.SYNOPSIS
Sets a federated identity credential for a managed identity.
 
.DESCRIPTION
Sets a federated identity credential for a managed identity to enable OIDC-based authentication. This enables external workloads (GitHub Actions, GitLab CI, etc.) to obtain Azure access tokens without storing credentials. Useful for establishing persistent access from external CI/CD systems.
 
.PARAMETER Id
The resource ID of the user-assigned managed identity in Azure. This should be the full resource ID path.
Aliases: resource-id
 
.PARAMETER ManagedIdentityName
The name of the user-assigned managed identity. The function will automatically look up the resource ID.
Aliases: identity-name, user-assigned-identity
 
.PARAMETER Name
The name of the federated credential to create. Defaults to 'federatedCredential'.
Aliases: federated-identity-name, credential-name
 
.PARAMETER GitHubOrganization
The GitHub organization name where the repository is located.
 
.PARAMETER GitHubRepository
The name of the GitHub repository to federate with the managed identity.
 
.PARAMETER Branch
The branch name to associate with the federated credential. Defaults to 'main'.
 
.EXAMPLE
Set-FederatedIdentity -ManagedIdentityName "uami-hr-cicd-automation" -GitHubOrganization "myorg" -GitHubRepository "myrepo"
 
Creates a federated credential using the managed identity name, linking it to the main branch of the myorg/myrepo GitHub repository.
 
.EXAMPLE
Set-FederatedIdentity -Id "/subscriptions/12345678-1234-1234-1234-123456789012/resourcegroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentity" -GitHubOrganization "myorg" -GitHubRepository "myrepo"
 
Creates a federated credential using the full resource ID.
 
.EXAMPLE
Set-FederatedIdentity -ManagedIdentityName "myIdentity" -Name "dev-credential" -GitHubOrganization "myorg" -GitHubRepository "myrepo" -Branch "development"
 
Creates a federated credential named "dev-credential" linking to the development branch.
 
.EXAMPLE
Get-ManagedIdentity -Name "myIdentity" | Set-FederatedIdentity -GitHubOrganization "myorg" -GitHubRepository "myrepo"
 
Pipes a managed identity to create a federated credential.
 
.NOTES
Requires appropriate Azure RBAC permissions to manage managed identities.
The function uses Azure REST API version 2023-01-31.
 
.LINK
https://learn.microsoft.com/en-us/azure/active-directory/develop/workload-identity-federation-create-trust-github
 
.LINK
MITRE ATT&CK Tactic: TA0003 - Persistence
https://attack.mitre.org/tactics/TA0003/
 
.LINK
MITRE ATT&CK Technique: T1098.001 - Account Manipulation: Additional Cloud Credentials
https://attack.mitre.org/techniques/T1098/001/
#>

}