Skip to content

Commit

Permalink
auth azure: support access token
Browse files Browse the repository at this point in the history
  • Loading branch information
LiliDeng committed Jan 8, 2025
1 parent 21abd2a commit e0d591e
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 5 deletions.
41 changes: 41 additions & 0 deletions lisa/sut_orchestrator/azure/common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import base64
import hashlib
import json
import os
Expand All @@ -26,6 +27,7 @@

import requests
from assertpy import assert_that
from azure.core.credentials import AccessToken, TokenCredential
from azure.core.exceptions import ResourceExistsError
from azure.keyvault.certificates import (
CertificateClient,
Expand Down Expand Up @@ -1696,6 +1698,10 @@ def get_or_create_storage_container(
"""
Create a Azure Storage container if it does not exist.
"""
if "AZURE_STORAGE_ACCESS_TOKEN" in os.environ:
credential = StaticAccessTokenCredential(
os.environ["AZURE_STORAGE_ACCESS_TOKEN"]
)
blob_service_client = get_blob_service_client(
cloud=cloud,
credential=credential,
Expand Down Expand Up @@ -2696,6 +2702,41 @@ def get_size(disk_type: schema.DiskType, data_disk_iops: int = 1) -> int:
raise LisaException(f"Data disk type {disk_type} is unsupported.")


class StaticAccessTokenCredential(TokenCredential):
def decode_jwt(self, token: str) -> Any:
# The second part of the JWT is the payload
payload = token.split(".")[1]
# Add padding to ensure Base64 decoding works properly
padded_payload = payload + "=" * (4 - len(payload) % 4)
# Decode the Base64 URL-safe encoded payload
decoded_payload = base64.urlsafe_b64decode(padded_payload)
# Convert the payload into a dictionary
return json.loads(decoded_payload)

def __init__(self, token: str) -> None:
"""
Initialize StaticAccessTokenCredential with the provided token.
:param token: The Azure access token as a string.
"""
self._token = token
# Parse the token to get the expiration timestamp
decoded_token = self.decode_jwt(os.environ["AZURE_ACCESS_TOKEN"])
# 'exp' is the UNIX timestamp for expiration
self._expires_on = decoded_token.get("exp")

def get_token(self, *scopes: str, **kwargs: Any) -> AccessToken:
"""
Get the access token for the specified scopes.
:param scopes: The OAuth 2.0 scopes the token applies to.
:param kwargs: Additional keyword arguments that may be required by the SDK.
:return: An AccessToken instance containing the token and its expiry time.
"""
# You can choose to print or log the scopes and kwargs for debugging if needed
return AccessToken(self._token, self._expires_on)


def get_certificate_client(
vault_url: str, platform: "AzurePlatform"
) -> CertificateClient:
Expand Down
23 changes: 18 additions & 5 deletions lisa/sut_orchestrator/azure/platform_.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Type, Union, cast

import requests
from azure.core.credentials import TokenCredential
from azure.core.exceptions import HttpResponseError, ResourceNotFoundError
from azure.identity import DefaultAzureCredential
from azure.mgmt.compute.models import (
Expand Down Expand Up @@ -105,6 +106,7 @@
DataDiskCreateOption,
DataDiskSchema,
SharedImageGallerySchema,
StaticAccessTokenCredential,
check_or_create_resource_group,
check_or_create_storage_account,
convert_to_azure_node_space,
Expand Down Expand Up @@ -246,6 +248,7 @@ class AzurePlatformSchema:
),
)
service_principal_key: str = field(default="")
access_token: str = field(default="")
subscription_id: str = field(
default="",
metadata=field_metadata(
Expand Down Expand Up @@ -320,6 +323,7 @@ def __post_init__(self, *args: Any, **kwargs: Any) -> None:
"service_principal_tenant_id",
"service_principal_client_id",
"service_principal_key",
"access_token",
"subscription_id",
"shared_resource_group_name",
"resource_group_name",
Expand All @@ -338,6 +342,8 @@ def __post_init__(self, *args: Any, **kwargs: Any) -> None:
add_secret(self.subscription_id, mask=PATTERN_GUID)
if self.service_principal_key:
add_secret(self.service_principal_key)
if self.access_token:
add_secret(self.access_token)
if self.service_principal_client_id:
add_secret(self.service_principal_client_id, mask=PATTERN_GUID)

Expand Down Expand Up @@ -407,14 +413,14 @@ class AzurePlatform(Platform):
)
_arm_template: Any = None

_credentials: Dict[str, DefaultAzureCredential] = {}
_credentials: Dict[str, Union[DefaultAzureCredential, TokenCredential]] = {}
_locations_data_cache: Dict[str, AzureLocation] = {}

def __init__(self, runbook: schema.Platform) -> None:
super().__init__(runbook=runbook)

# for type detection
self.credential: DefaultAzureCredential
self.credential: Union[DefaultAzureCredential, TokenCredential]
self.cloud: Cloud

# It has to be defined after the class definition is loaded. So it
Expand Down Expand Up @@ -936,10 +942,17 @@ def _initialize_credential(self) -> None:
] = azure_runbook.service_principal_client_id
if azure_runbook.service_principal_key:
os.environ["AZURE_CLIENT_SECRET"] = azure_runbook.service_principal_key
if azure_runbook.access_token:
os.environ["AZURE_ACCESS_TOKEN"] = azure_runbook.access_token

credential = DefaultAzureCredential(
authority=self.cloud.endpoints.active_directory,
)
if "AZURE_ACCESS_TOKEN" in os.environ:
credential = StaticAccessTokenCredential(
os.environ["AZURE_ACCESS_TOKEN"]
)
else:
credential = DefaultAzureCredential(
authority=self.cloud.endpoints.active_directory,
)

with SubscriptionClient(
credential,
Expand Down

0 comments on commit e0d591e

Please sign in to comment.