-
Notifications
You must be signed in to change notification settings - Fork 131
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
Support GCP Auth #444
Support GCP Auth #444
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
import configparser | ||
import copy | ||
import functools | ||
import io | ||
import json | ||
import logging | ||
import os | ||
|
@@ -18,8 +19,11 @@ | |
from typing import (Any, BinaryIO, Callable, Dict, Iterable, Iterator, List, | ||
Optional, Type, Union) | ||
|
||
import google.auth | ||
import requests | ||
import requests.auth | ||
from google.auth import impersonated_credentials | ||
from google.auth.transport.requests import Request | ||
from google.oauth2 import service_account | ||
from requests.adapters import HTTPAdapter | ||
|
||
from .azure import (ARM_DATABRICKS_RESOURCE_ID, ENVIRONMENTS, AzureEnvironment, | ||
|
@@ -36,6 +40,8 @@ | |
|
||
HeaderFactory = Callable[[], Dict[str, str]] | ||
|
||
GcpScopes = ["https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/compute"] | ||
|
||
|
||
class CredentialsProvider(abc.ABC): | ||
""" CredentialsProvider is the protocol (call-side interface) | ||
|
@@ -265,6 +271,70 @@ def refreshed_headers() -> Dict[str, str]: | |
return refreshed_headers | ||
|
||
|
||
@credentials_provider('google-credentials', ['host', 'google_credentials']) | ||
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. I guess this is the same as with the Go library. But in theory, google_credentials does not even need to be specified, because there is also a default directory that the google-auth library looks in, if I'm not mistaken. We might be able to remove this and allow users to auto-login with their google credentials set up via the default app credentials pathway. 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. Yeah I thought so as well at the beginning but after checking more I think we do need to point it to the service account json file, which is different from the default auth json file as the service account one contains more info about the keys and secrets for signing the jwt etc. So it seems like we need this to make it work |
||
def google_credentials(cfg: 'Config') -> Optional[HeaderFactory]: | ||
if not cfg.is_gcp: | ||
return None | ||
# Reads credentials as JSON. Credentials can be either a path to JSON file, or actual JSON string. | ||
# Obtain the id token by providing the json file path and target audience. | ||
if (os.path.isfile(cfg.google_credentials)): | ||
with io.open(cfg.google_credentials, "r", encoding="utf-8") as json_file: | ||
account_info = json.load(json_file) | ||
else: | ||
# If the file doesn't exist, assume that the config is the actual JSON content. | ||
account_info = json.loads(cfg.google_credentials) | ||
|
||
credentials = service_account.IDTokenCredentials.from_service_account_info(info=account_info, | ||
target_audience=cfg.host) | ||
|
||
request = Request() | ||
|
||
gcp_credentials = service_account.Credentials.from_service_account_info(info=account_info, | ||
scopes=GcpScopes) | ||
|
||
def refreshed_headers() -> Dict[str, str]: | ||
credentials.refresh(request) | ||
headers = {'Authorization': f'Bearer {credentials.token}'} | ||
if cfg.is_account_client: | ||
gcp_credentials.refresh(request) | ||
headers["X-Databricks-GCP-SA-Access-Token"] = gcp_credentials.token | ||
return headers | ||
|
||
return refreshed_headers | ||
|
||
|
||
@credentials_provider('google-id', ['host', 'google_service_account']) | ||
def google_id(cfg: 'Config') -> Optional[HeaderFactory]: | ||
if not cfg.is_gcp: | ||
return None | ||
credentials, _project_id = google.auth.default() | ||
|
||
# Create the impersonated credential. | ||
target_credentials = impersonated_credentials.Credentials(source_credentials=credentials, | ||
target_principal=cfg.google_service_account, | ||
target_scopes=[]) | ||
|
||
# Set the impersonated credential, target audience and token options. | ||
id_creds = impersonated_credentials.IDTokenCredentials(target_credentials, | ||
target_audience=cfg.host, | ||
include_email=True) | ||
|
||
gcp_impersonated_credentials = impersonated_credentials.Credentials( | ||
source_credentials=credentials, target_principal=cfg.google_service_account, target_scopes=GcpScopes) | ||
|
||
request = Request() | ||
|
||
def refreshed_headers() -> Dict[str, str]: | ||
id_creds.refresh(request) | ||
headers = {'Authorization': f'Bearer {id_creds.token}'} | ||
if cfg.is_account_client: | ||
gcp_impersonated_credentials.refresh(request) | ||
headers["X-Databricks-GCP-SA-Access-Token"] = gcp_impersonated_credentials.token | ||
return headers | ||
|
||
return refreshed_headers | ||
|
||
|
||
class CliTokenSource(Refreshable): | ||
|
||
def __init__(self, cmd: List[str], token_type_field: str, access_token_field: str, expiry_field: str): | ||
|
@@ -531,7 +601,8 @@ def auth_type(self) -> str: | |
def __call__(self, cfg: 'Config') -> HeaderFactory: | ||
auth_providers = [ | ||
pat_auth, basic_auth, metadata_service, oauth_service_principal, azure_service_principal, | ||
github_oidc_azure, azure_cli, external_browser, databricks_cli, runtime_native_auth | ||
github_oidc_azure, azure_cli, external_browser, databricks_cli, runtime_native_auth, | ||
google_credentials, google_id | ||
] | ||
for provider in auth_providers: | ||
auth_type = provider.auth_type() | ||
|
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.
@edwardfeng-db Could you take a second pass at this doc? The field names mentioned are the Go SDK field names, not the Python SDK field names (
PascalCase
vssnake_case
).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.
Yeah I see, sorry about this, let me update that. Some copy pasta issues