Skip to content

feat: add service principal auth for azure pipelines scaler#7578

Open
m-amaresh wants to merge 10 commits intokedacore:mainfrom
m-amaresh:main
Open

feat: add service principal auth for azure pipelines scaler#7578
m-amaresh wants to merge 10 commits intokedacore:mainfrom
m-amaresh:main

Conversation

@m-amaresh
Copy link
Copy Markdown

@m-amaresh m-amaresh commented Mar 31, 2026

Add Azure Pipelines scaler support for Azure AD service principal authentication.

This change extends the scaler to support:

  • service principal authentication with tenantId + clientId + clientSecret
  • service principal authentication with tenantId + clientId + clientCertificate (+ optional clientCertificatePassword)

Existing authentication behavior remains supported:

  • personalAccessToken continues to use PAT-based Basic authentication
  • Azure Workload Identity continues to use Azure AD bearer-token authentication

For service principal and Azure Workload Identity authentication, the scaler acquires an Azure AD token for the Azure DevOps resource and calls the Azure DevOps REST API with bearer authentication.

This PR also:

  • adds unit test coverage for the new authentication modes and validation rules
  • updates the generated scaler schema
  • adds Azure Pipelines e2e coverage for service principal authentication with client secret
  • adds a changelog entry

Related Issue

Fixes #4853
Fixes #7581

Checklist

  • When introducing a new scaler, I agree with the scaling governance policy
  • I have verified that my change is according to the deprecations & breaking changes policy
  • Tests have been added (if applicable)
  • Ensure make generate-scalers-schema has been run to update any outdated generated files
  • Changelog has been updated and is aligned with our changelog requirements, only when the change impacts end users
  • A PR is opened to update our Helm chart (if applicable)
  • A PR is opened to update the documentation on keda-docs (if applicable)
  • Commits are signed with Developer Certificate of Origin (DCO)

Relates to kedacore/keda-docs#1726

Notes

  • clientSecret and clientCertificate are mutually exclusive
  • clientCertificatePassword is only valid when clientCertificate is provided
  • tenantId and clientId are required for service principal authentication
  • certificate-based e2e coverage is not included in this PR because the current test environment does not expose Azure DevOps certificate credentials

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@m-amaresh m-amaresh requested a review from a team as a code owner March 31, 2026 19:47
@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Mar 31, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@github-actions
Copy link
Copy Markdown

Thank you for your contribution! 🙏

Please understand that we will do our best to review your PR and give you feedback as soon as possible, but please bear with us if it takes a little longer as expected.

While you are waiting, make sure to:

  • Add an entry in our changelog in alphabetical order and link related issue
  • Update the documentation, if needed
  • Add unit & e2e tests for your changes
  • GitHub checks are passing
  • Is the DCO check failing? Here is how you can fix DCO issues

Once the initial tests are successful, a KEDA member will ensure that the e2e tests are run. Once the e2e tests have been successfully completed, the PR may be merged at a later date. Please be patient.

Learn more about our contribution guide.

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@biggles007
Copy link
Copy Markdown

This would actually complete my feature request #7581 and remove the requirement for a PAT in Azure Container Apps.

- Remove triggerMetadata from order tags for clientSecret, clientCertificate,
  and clientCertificatePassword to prevent plaintext secrets in ScaledObject spec
- Remove unreachable authType fallback block in getAzurePipelineRequest
- Fix pre-existing tests that relied on the fallback by setting authType explicitly
- Regenerate scalers schema with clientCertificate and clientCertificatePassword fields

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
…pers

Drop the podIdentity parameter from getAzurePipelineRequest,
getPoolIDFromName, and validatePoolID — auth dispatch now uses
authContext.authType set during metadata parsing, so the parameter
was dead code after the SPN auth refactor.

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@m-amaresh
Copy link
Copy Markdown
Author

Hello Team, Can I get a review?

Copy link
Copy Markdown
Member

@rickbrouwer rickbrouwer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that the last commit wants to remove the podIdentity parameter from getPoolIDFromName and validatePoolID. But it only suppresses it with _.
I think that the parameter is still present in both signatures (parseAzurePipelinesMetadata still pass podIdentity).

Can you check this and compare it with getAzurePipelineRequest, where the parameter is removed from the signature and all calls.

Other than that, I think everything looks good.

Comment thread pkg/scalers/azure_pipelines_scaler_test.go
…atePoolID

Auth is carried via metadata.authContext which is already populated before
these functions are called. The parameter was suppressed with _ but still
present in both signatures and all call sites.

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@keda-automation keda-automation requested a review from a team April 4, 2026 10:33
@m-amaresh m-amaresh requested a review from rickbrouwer April 4, 2026 10:37
@m-amaresh
Copy link
Copy Markdown
Author

Hello @rickbrouwer , Can you please review the changes. Also If it goes thru, I was not sure about next release cadence and I have added the docs in 2.20 version

https://deploy-preview-1726--keda.netlify.app/docs/2.20/scalers/azure-pipelines/

Comment thread pkg/scalers/azure_pipelines_scaler.go Outdated
Comment thread pkg/scalers/azure_pipelines_scaler.go Outdated
@rickbrouwer
Copy link
Copy Markdown
Member

I was not sure about next release cadence and I have added the docs in 2.20 version

Yes, that is correct 👍🏼

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@keda-automation keda-automation requested a review from a team April 5, 2026 17:44
Comment thread pkg/scalers/azure_pipelines_scaler_test.go Outdated
Comment thread pkg/scalers/azure_pipelines_scaler_test.go Outdated
Comment thread pkg/scalers/azure_pipelines_scaler_test.go Outdated
Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@m-amaresh
Copy link
Copy Markdown
Author

@rickbrouwer

@rickbrouwer
Copy link
Copy Markdown
Member

rickbrouwer commented Apr 7, 2026

/run-e2e azure_pipelines
Update: You can check the progress here

@m-amaresh
Copy link
Copy Markdown
Author

m-amaresh commented Apr 7, 2026

@rickbrouwer the e2e test failed

ERROR scale_handler error resolving auth params                                                                                                                                                                     
  "error": "error parsing azure Pipelines metadata: agent pool with id `***` not found:                                                                                                                               
  the Azure DevOps REST API returned error. status: 40***                                                                                                                                                             
  response: {\"message\":\"TF40***444: Please sign-in at least once as ***\\45b7d***e-b57d-4***-b***7c-5ea8a8bb5faa                                                                                                   
  in a web browser to enable access to the service.\",                                                                                                                                                                
  \"typeName\":\"...UnauthorizedRequestException\"...}"   

Can you add the SPN to ADO org and grant read permission on pools.

Also can you create a certificate and certificate password and configure for e2e test.

@m-amaresh
Copy link
Copy Markdown
Author

@rickbrouwer

@rickbrouwer
Copy link
Copy Markdown
Member

Please don't push. We will do our best to check your PR as soon as can.

Copy link
Copy Markdown
Member

@JorTurFer JorTurFer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that can be used by other Azure scalers as it relies on azcore.TokenCredentials, does it make sense to create another auth that can be used for all the azure scalers instead of adding the parameters on this scaler itself?

Comment thread pkg/scalers/azure_pipelines_scaler.go Outdated
Comment on lines +225 to +261
if meta.TenantID != "" || meta.ClientID != "" || meta.ClientSecret != "" || meta.ClientCertificate != "" || meta.ClientCertificatePassword != "" {
missing := make([]string, 0, 3)
if meta.TenantID == "" {
missing = append(missing, "tenantId")
}
if meta.ClientID == "" {
missing = append(missing, "clientId")
}
usingClientSecret := meta.ClientSecret != ""
usingClientCertificate := meta.ClientCertificate != ""
if usingClientSecret && usingClientCertificate {
return nil, fmt.Errorf("clientSecret and clientCertificate are mutually exclusive")
}
if !usingClientSecret && !usingClientCertificate {
missing = append(missing, "one of clientSecret or clientCertificate")
}
if meta.ClientCertificatePassword != "" && !usingClientCertificate {
return nil, fmt.Errorf("clientCertificatePassword requires clientCertificate")
}
if len(missing) != 0 {
return nil, fmt.Errorf("incomplete service principal configuration, missing %s", strings.Join(missing, ", "))
}

var (
cred azcore.TokenCredential
err error
)
if usingClientCertificate {
cred, err = newAzurePipelinesClientCertificateCredential(meta.TenantID, meta.ClientID, meta.ClientCertificate, meta.ClientCertificatePassword)
} else {
cred, err = newAzurePipelinesClientSecretCredential(meta.TenantID, meta.ClientID, meta.ClientSecret)
}
if err != nil {
return nil, err
}
meta.authContext.authType = azurePipelinesAuthTypeServicePrincipal
return cred, nil
Copy link
Copy Markdown
Member

@JorTurFer JorTurFer Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, move this code inside podIdentity none, as pod identity takes precedence over PAT

@JorTurFer
Copy link
Copy Markdown
Member

Checking the error

2026-04-07T15:16:56Z ERROR scale_handler error resolving auth params {"type": "ScaledObject", "namespace": "azure-pipelines-sp-test-ns", "name": "azure-pipelines-sp-test-so", "triggerIndex": 0, "error": "error parsing azure Pipelines metadata: agent pool with id 11 not found: the Azure DevOps REST API returned error. urlString: https://dev.azure.com/kedaoss/_apis/distributedtask/pools?poolID=11 status: 4
01 response: {"$id":"1","innerException":null,"message":"TF401444: Please sign-in at least once as e0372f7f-a362-47fb-9631-74a5c4ba8bbf\\e0372f7f-a362-47fb-9631-74a5c4ba8bbf\\45b7d23e-b57d-4331-b17c-5ea8a8bb5faa in a web browser to enable access to the service.","typeName":"Microsoft.TeamFoundation.Framework.Server.UnauthorizedRequestException, Microsoft.TeamFoundation.Framework.Server","typeKey":"UnauthorizedRequestException","errorCode":0,"eventId":3000}"}

It looks like the clientID + client Secret isn't working. The error 401 suggest failures getting the token rather than missing permissions for the identity (it's used in other e2e tests as it's the identity set for WIF)

@m-amaresh
Copy link
Copy Markdown
Author

@JorTurFer I dont have a full setup now but I added a some test code to check
I created an Entra Application

token_json=$(curl -sS -X POST "https://login.microsoftonline.com/${TF_AZURE_SP_TENANT}/oauth2/v2.0/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    --data-urlencode "client_id=${TF_AZURE_SP_APP_ID}" \
    --data-urlencode "client_secret=${AZURE_SP_KEY}" \
    --data-urlencode "scope=499b84ac-1321-427f-aa17-267ca6975798/.default" \
    --data-urlencode "grant_type=client_credentials")
    
 curl -sS -w "%{http_code}" \
    -H "Authorization: Bearer ${token}" \
    -H "Accept: application/json" \
    "${AZURE_DEVOPS_ORGANIZATION_URL}/_apis/projects?api-version=7.1"
    
 {
    "$id": "1",
    "innerException": null,
    "message": "TF401444: Please sign-in at least once as 4b74REDACTEDaea79673d in a web browser to enable access to the service.",
    "typeName": "Microsoft.TeamFoundation.Framework.Server.UnauthorizedRequestException, Microsoft.TeamFoundation.Framework.Server",
    "typeKey": "UnauthorizedRequestException",
    "errorCode": 0,
    "eventId": 3000
}

After this I added the Entra Application to ADO org and then same again

{
    "count": 1,
    "value": [
        {
            "id": "fdREDACTEDe323",
            "name": "quick-fox",
            "description": "Quick fox darted through the tall grass, its sleek fur a blur against the backdrop of the setting sun.",
            "url": "https://dev.azure.com/REDACTED/_apis/projects/fdcREDACTED23",
            "state": "wellFormed",
            "revision": 24,
            "visibility": "private",
            "lastUpdateTime": "2025-02-14T17:28:11.477Z"
        }
    ]
}

@oriolaf
Copy link
Copy Markdown

oriolaf commented Apr 16, 2026

When could this be merged? very interested :)

@rickbrouwer
Copy link
Copy Markdown
Member

rickbrouwer commented Apr 18, 2026

/run-e2e azure_pipelines
Update: You can check the progress here

Signed-off-by: m-amaresh <115941749+m-amaresh@users.noreply.github.com>
@keda-automation keda-automation requested a review from a team April 18, 2026 16:31
@semgrep-code-kedacore
Copy link
Copy Markdown

Semgrep found 18 context-todo findings:

Consider to use well-defined context

@m-amaresh
Copy link
Copy Markdown
Author

m-amaresh commented Apr 18, 2026

@rickbrouwer , as I have mentioned earlier, Can you please add the SPN to ADO org so that e2e can run successfully.

@JorTurFer
Copy link
Copy Markdown
Member

This is something that can be used by other Azure scalers as it relies on azcore.TokenCredentials, does it make sense to create another auth that can be used for all the azure scalers instead of adding the parameters on this scaler itself?

Did you have chance to think about this? I really think that as this is on top of azcore.TokenCredential makes sense to extract from pipeline specific into a shared thing that we can add to all azure scalers. If we merge this as it is, moving this feature to be auth provider will be a problem as we have to maintain both here.
@kedacore/keda-core-contributors @kedacore/keda-core-maintainers

Personally, I love this feature as it extends the authentication options for Azure, but I'm not sure about adding it only at scaler level

@rickbrouwer
Copy link
Copy Markdown
Member

Personally, I love this feature as it extends the authentication options for Azure, but I'm not sure about adding it only at scaler level

Agree!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support client id and secret in Azure Pipelines scaler Authenticating with Azure DevOps using SPN

5 participants