PowerShell / PSResourceGet

PSResourceGet is the package manager for PowerShell
https://www.powershellgallery.com/packages/Microsoft.PowerShell.PSResourceGet
MIT License
486 stars 92 forks source link

PAT authentication failing with Azure Devops on premise Artifacts feed #1593

Open gerryleys opened 7 months ago

gerryleys commented 7 months ago

Prerequisites

Steps to reproduce

Access to onprem Azure Devops Artifacts feed failed when authenticating using a PAT token, created by Azure Devops.

$Token = "...TOKEN..." | ConvertTo-SecureString -AsPlainText -Force $Credential = New-Object System.Management.Automation.PSCredential('PAT', $Token) Set-Secret -Name AzureArtifactToken -Secret $Credential $CredentialInfo = New-Object Microsoft.PowerShell.PSResourceGet.UtilClasses.PSCredentialInfo ("MySecrets", "AzureArtifactToken")

$Params = @{ Name = 'RepoName' Uri = 'https://mydomain.org/org/grp/_packaging/grp/nuget/v3/index.json' Trusted = $true CredentialInfo = $CredentialInfo ApiVersion = 'v3' } Set-PSResourceRepository @Params

After examine this issue further, we found out that the API server calls were performed by a HttpClient class method, using 'negotiate' authentication by default (in our situation). As the PAT token in the SecretStore is retreived as a PSCredential, PSResourceGet passes this PAT token as a 'NetworkCredential' to the ServerApiCalls class methods (incl. v3).

        HttpClientHandler handler = new HttpClientHandler()
        {
            AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
            Credentials = networkCredential
        };

But, a PAT token requires 'Basic authentication' to succeed. See https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=Windows#use-a-pat-in-your-code. To test this, I modified the API calls (incl. V3) with a token detection (username = 'PAT') and added a HttpClient AuthenticationHeader with Basic authentication. After rebuilding and testing this code, the Authentication was successful!

    public ServerApiCall(PSRepositoryInfo repository, NetworkCredential networkCredential)
    {
        this.Repository = repository;

        HttpClientHandler handler = new HttpClientHandler();
        bool token = false;

        if(networkCredential != null) 
        {
            if (String.Equals("PAT", networkCredential.UserName))
            {
                token = true;
            } 
        };

        if (token)
        {
            string credString = string.Format(":{0}", networkCredential.Password);
            byte[] byteArray = Encoding.ASCII.GetBytes(credString);

            _sessionClient = new HttpClient(handler);
            _sessionClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));

        } else {

            handler.Credentials = networkCredential;

            _sessionClient = new HttpClient(handler);
        };
    }

Expected behavior

Find-PSResource -> list of requested packages

Actual behavior

Find-PSResource: 'Response status code does not indicate success: 401 (Unauthorized).' Re-run the command with -Credential.

Error details

No response

Environment data

ModuleType Version    PreRelease Name                                ExportedCommands
---------- -------    ---------- ----                                ----------------
Binary     1.0.2                 Microsoft.PowerShell.PSResourceGet  {Find-PSResource, Get-InstalledPSResource, Get-PSResourceRepository, Get-PSScriptFileInfo…}

Key   : PSVersion
Value : 7.3.2
Name  : PSVersion

Key   : PSEdition
Value : Core
Name  : PSEdition

Key   : GitCommitId
Value : 7.3.2
Name  : GitCommitId

Key   : OS
Value : Microsoft Windows 10.0.14393
Name  : OS

Key   : Platform
Value : Win32NT
Name  : Platform

Key   : PSCompatibleVersions
Value : {1.0, 2.0, 3.0, 4.0…}
Name  : PSCompatibleVersions

Key   : PSRemotingProtocolVersion
Value : 2.3
Name  : PSRemotingProtocolVersion

Key   : SerializationVersion
Value : 1.1.0.1
Name  : SerializationVersion

Key   : WSManStackVersion
Value : 3.0
Name  : WSManStackVersion

Visuals

No response

SydneyhSmith commented 7 months ago

Thanks @gerryleys for opening the issue and providing detailed steps-- we really appreciate it...looks like you have a good fix, would you be interested in contributing a PR? We are hoping to get out a patch release

gerryleys commented 6 months ago

@SydneyhSmith I created a PR. A small adaptation from the first suggested quick fix. I used the linked PS SecretStore secret with a 'SecureString' datatype object to be treated as a (PAT) token, this makes more sense. This translates later in the code to a PSCredential object with username 'token', and will be handled with 'Basic Authorization'.

gerryleys commented 6 months ago

@SydneyhSmith Any news on this PR? Is there something I can do or modify? Thanks in advance.

SydneyhSmith commented 6 months ago

@gerryleys apologies for the delay, we have a few folks out on Spring Break and the rest at PowerShell Summit this week... I'll try to get a review on it in the next week