-
Notifications
You must be signed in to change notification settings - Fork 178
Add native support for Azure DevOps OIDC authentication #1027
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c1c95cb
8e4bb86
751c1a4
d4175ae
1bbc3c3
fd24dc4
553fd36
5bbb16b
649cc5d
ef7a058
eabfc73
8c95cd9
44c78c1
3304bbf
3f6505a
14aa770
3b5ab96
3393d78
4b4ca3e
9f02727
ac5db4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,13 @@ | ||
| import logging | ||
| import os | ||
| from typing import Optional | ||
|
|
||
| import requests | ||
|
|
||
| logger = logging.getLogger("databricks.sdk") | ||
|
|
||
|
|
||
| # TODO: Check the required environment variables while creating the instance rather than in the get_oidc_token method to allow early return. | ||
| class GitHubOIDCTokenSupplier: | ||
| """ | ||
| Supplies OIDC tokens from GitHub Actions. | ||
|
|
@@ -26,3 +30,79 @@ def get_oidc_token(self, audience: str) -> Optional[str]: | |
| return None | ||
|
|
||
| return response_json["value"] | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same: GitHubOIDC does not validate on create. Is there a reasons to change this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is done to ensure Early exit. This is similar to what is done in Go SDK. If the environment variables are not set then we are sure that we are not in Azure DevOps Environment so we should exit at the earliest and try other providers.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we add a TODO on the GitHub OIDC implementation to make it clear that it is the one with tech debt? |
||
|
|
||
| class AzureDevOpsOIDCTokenSupplier: | ||
| """ | ||
| Supplies OIDC tokens from Azure DevOps pipelines. | ||
|
|
||
| Constructs the OIDC token request URL using official Azure DevOps predefined variables. | ||
| See: https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables | ||
| """ | ||
|
|
||
| def __init__(self): | ||
| """Initialize and validate Azure DevOps environment variables.""" | ||
| # Get Azure DevOps environment variables. | ||
| self.access_token = os.environ.get("SYSTEM_ACCESSTOKEN") | ||
| self.collection_uri = os.environ.get("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") | ||
| self.project_id = os.environ.get("SYSTEM_TEAMPROJECTID") | ||
| self.plan_id = os.environ.get("SYSTEM_PLANID") | ||
| self.job_id = os.environ.get("SYSTEM_JOBID") | ||
| self.hub_name = os.environ.get("SYSTEM_HOSTTYPE") | ||
|
|
||
| # Check for required variables with specific error messages. | ||
| missing_vars = [] | ||
| if not self.access_token: | ||
| missing_vars.append("SYSTEM_ACCESSTOKEN") | ||
| if not self.collection_uri: | ||
| missing_vars.append("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI") | ||
| if not self.project_id: | ||
| missing_vars.append("SYSTEM_TEAMPROJECTID") | ||
| if not self.plan_id: | ||
| missing_vars.append("SYSTEM_PLANID") | ||
| if not self.job_id: | ||
| missing_vars.append("SYSTEM_JOBID") | ||
| if not self.hub_name: | ||
| missing_vars.append("SYSTEM_HOSTTYPE") | ||
|
|
||
| if missing_vars: | ||
| if "SYSTEM_ACCESSTOKEN" in missing_vars: | ||
| error_msg = "Azure DevOps OIDC: SYSTEM_ACCESSTOKEN env var not found. If calling from Azure DevOps Pipeline, please set this env var following https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#systemaccesstoken" | ||
| else: | ||
| error_msg = f"Azure DevOps OIDC: missing required environment variables: {', '.join(missing_vars)}" | ||
| raise ValueError(error_msg) | ||
|
|
||
| def get_oidc_token(self, audience: str) -> Optional[str]: | ||
| # Note: Azure DevOps OIDC tokens have a fixed audience of "api://AzureADTokenExchange". | ||
| # The audience parameter is ignored but kept for interface compatibility with other OIDC suppliers. | ||
|
|
||
| try: | ||
| # Construct the OIDC token request URL. | ||
| # Format: {collection_uri}{project_id}/_apis/distributedtask/hubs/{hubName}/plans/{planId}/jobs/{jobId}/oidctoken. | ||
| request_url = f"{self.collection_uri}{self.project_id}/_apis/distributedtask/hubs/{self.hub_name}/plans/{self.plan_id}/jobs/{self.job_id}/oidctoken" | ||
|
|
||
| # Add API version (audience is fixed to "api://AzureADTokenExchange" by Azure DevOps). | ||
| endpoint = f"{request_url}?api-version=7.2-preview.1" | ||
| headers = { | ||
| "Authorization": f"Bearer {self.access_token}", | ||
| "Content-Type": "application/json", | ||
| "Content-Length": "0", | ||
| } | ||
|
|
||
| # Azure DevOps OIDC endpoint requires POST request with empty body. | ||
| response = requests.post(endpoint, headers=headers) | ||
| if not response.ok: | ||
| logger.debug(f"Azure DevOps OIDC: token request failed with status {response.status_code}") | ||
| return None | ||
|
|
||
| # Azure DevOps returns the token in 'oidcToken' field. | ||
| response_json = response.json() | ||
| if "oidcToken" not in response_json: | ||
| logger.debug("Azure DevOps OIDC: response missing 'oidcToken' field") | ||
| return None | ||
|
|
||
| logger.debug("Azure DevOps OIDC: successfully obtained token") | ||
| return response_json["oidcToken"] | ||
| except Exception as e: | ||
| logger.debug(f"Azure DevOps OIDC: failed to get token: {e}") | ||
| return None | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seemed like unnecessary Double check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? what does this function do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function checks if the attribute exists in the config or not. This check is redundant, as it is done in the next line as well