2022-04-19 12:31:34 +02:00
|
|
|
# Copyright 2021 Collate
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
"""
|
|
|
|
Credentials helper module
|
|
|
|
"""
|
2022-11-08 14:13:49 +05:30
|
|
|
import base64
|
2022-04-19 12:31:34 +02:00
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import tempfile
|
2022-08-19 11:19:20 +02:00
|
|
|
from typing import Dict
|
|
|
|
|
|
|
|
from cryptography.hazmat.primitives import serialization
|
2022-04-19 12:31:34 +02:00
|
|
|
|
|
|
|
from metadata.generated.schema.security.credentials.gcsCredentials import (
|
|
|
|
GCSCredentials,
|
|
|
|
GCSCredentialsPath,
|
|
|
|
GCSValues,
|
|
|
|
)
|
2022-06-01 13:52:55 +05:30
|
|
|
from metadata.utils.logger import utils_logger
|
|
|
|
|
|
|
|
logger = utils_logger()
|
2022-04-19 12:31:34 +02:00
|
|
|
|
|
|
|
GOOGLE_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
|
|
|
|
|
|
|
|
|
|
|
|
class InvalidGcsConfigException(Exception):
|
|
|
|
"""
|
|
|
|
Raised when we have errors trying to set GCS credentials
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2022-08-19 11:19:20 +02:00
|
|
|
class InvalidPrivateKeyException(Exception):
|
|
|
|
"""
|
|
|
|
If the key cannot be serialised
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def validate_private_key(private_key: str) -> None:
|
|
|
|
"""
|
|
|
|
Make sure that a private key can be properly parsed
|
|
|
|
by cryptography backends
|
|
|
|
:param private_key: key to validate
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
serialization.load_pem_private_key(private_key.encode(), password=None)
|
|
|
|
except ValueError as err:
|
2022-08-29 06:46:06 +02:00
|
|
|
msg = f"Cannot serialise key: {err}"
|
2022-10-10 16:23:47 +05:30
|
|
|
raise InvalidPrivateKeyException(msg) from err
|
2022-08-19 11:19:20 +02:00
|
|
|
|
|
|
|
|
2022-04-19 12:31:34 +02:00
|
|
|
def create_credential_tmp_file(credentials: dict) -> str:
|
|
|
|
"""
|
|
|
|
Given a credentials' dict, store it in a tmp file
|
|
|
|
:param credentials: dictionary to store
|
|
|
|
:return: path to find the file
|
|
|
|
"""
|
2022-10-10 16:23:47 +05:30
|
|
|
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
|
2022-04-19 12:31:34 +02:00
|
|
|
cred_json = json.dumps(credentials, indent=4, separators=(",", ": "))
|
2022-10-10 16:23:47 +05:30
|
|
|
temp_file.write(cred_json.encode())
|
2022-04-19 12:31:34 +02:00
|
|
|
|
2022-10-10 16:23:47 +05:30
|
|
|
return temp_file.name
|
2022-04-19 12:31:34 +02:00
|
|
|
|
|
|
|
|
2022-08-19 11:19:20 +02:00
|
|
|
def build_google_credentials_dict(gcs_values: GCSValues) -> Dict[str, str]:
|
|
|
|
"""
|
|
|
|
Given GCSValues, build a dictionary as the JSON file
|
|
|
|
downloaded from GCS with the service_account
|
|
|
|
:param gcs_values: GCS credentials
|
|
|
|
:return: Dictionary with credentials
|
|
|
|
"""
|
|
|
|
private_key_str = gcs_values.privateKey.get_secret_value()
|
2022-11-30 20:07:24 +05:30
|
|
|
# adding the replace string here to escape line break if passed from env
|
|
|
|
private_key_str = private_key_str.replace("\\n", "\n")
|
2022-08-19 11:19:20 +02:00
|
|
|
validate_private_key(private_key_str)
|
|
|
|
|
|
|
|
return {
|
|
|
|
"type": gcs_values.type,
|
2022-11-17 14:26:37 +05:30
|
|
|
"project_id": gcs_values.projectId.__root__,
|
2022-08-19 11:19:20 +02:00
|
|
|
"private_key_id": gcs_values.privateKeyId,
|
|
|
|
"private_key": private_key_str,
|
|
|
|
"client_email": gcs_values.clientEmail,
|
|
|
|
"client_id": gcs_values.clientId,
|
|
|
|
"auth_uri": str(gcs_values.authUri),
|
|
|
|
"token_uri": str(gcs_values.tokenUri),
|
|
|
|
"auth_provider_x509_cert_url": str(gcs_values.authProviderX509CertUrl),
|
|
|
|
"client_x509_cert_url": str(gcs_values.clientX509CertUrl),
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-04-19 12:31:34 +02:00
|
|
|
def set_google_credentials(gcs_credentials: GCSCredentials) -> None:
|
|
|
|
"""
|
|
|
|
Set GCS credentials environment variable
|
|
|
|
:param gcs_credentials: GCSCredentials
|
|
|
|
"""
|
|
|
|
if os.environ.get(GOOGLE_CREDENTIALS):
|
|
|
|
return
|
|
|
|
|
|
|
|
if isinstance(gcs_credentials.gcsConfig, GCSCredentialsPath):
|
|
|
|
os.environ[GOOGLE_CREDENTIALS] = str(gcs_credentials.gcsConfig.__root__)
|
|
|
|
return
|
2022-08-19 11:19:20 +02:00
|
|
|
|
2022-06-01 13:52:55 +05:30
|
|
|
if gcs_credentials.gcsConfig.projectId is None:
|
|
|
|
logger.info(
|
2022-08-19 11:19:20 +02:00
|
|
|
"No credentials available, using the current environment permissions authenticated via gcloud SDK."
|
2022-06-01 13:52:55 +05:30
|
|
|
)
|
|
|
|
return
|
2022-08-19 11:19:20 +02:00
|
|
|
|
2022-04-19 12:31:34 +02:00
|
|
|
if isinstance(gcs_credentials.gcsConfig, GCSValues):
|
2022-09-20 17:36:10 +05:30
|
|
|
if (
|
|
|
|
gcs_credentials.gcsConfig.projectId
|
|
|
|
and not gcs_credentials.gcsConfig.privateKey
|
|
|
|
):
|
|
|
|
logger.info(
|
|
|
|
"Overriding default projectid, using the current environment permissions authenticated via gcloud SDK."
|
|
|
|
)
|
|
|
|
return
|
2022-08-19 11:19:20 +02:00
|
|
|
credentials_dict = build_google_credentials_dict(gcs_credentials.gcsConfig)
|
2022-04-19 12:31:34 +02:00
|
|
|
tmp_credentials_file = create_credential_tmp_file(credentials=credentials_dict)
|
|
|
|
os.environ[GOOGLE_CREDENTIALS] = tmp_credentials_file
|
|
|
|
return
|
|
|
|
|
|
|
|
raise InvalidGcsConfigException(
|
|
|
|
f"Error trying to set GCS credentials with {gcs_credentials}."
|
2022-08-29 19:38:54 +05:30
|
|
|
" Check https://docs.open-metadata.org/openmetadata/connectors/database/bigquery "
|
2022-04-19 12:31:34 +02:00
|
|
|
)
|
2022-11-08 14:13:49 +05:30
|
|
|
|
|
|
|
|
|
|
|
def generate_http_basic_token(username, password):
|
|
|
|
"""
|
|
|
|
Generates a HTTP basic token from username and password
|
|
|
|
Returns a token string (not a byte)
|
|
|
|
"""
|
|
|
|
token = base64.b64encode(f"{username}:{password}".encode("utf-8")).decode("utf-8")
|
|
|
|
return token
|