This repository has been archived by the owner on Nov 1, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 199
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Storing secrets in azure keyvault (#326)
- Loading branch information
Showing
12 changed files
with
359 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
#!/usr/bin/env python | ||
# | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
|
||
|
||
from typing import Tuple, Type, TypeVar, cast | ||
from urllib.parse import urlparse | ||
from uuid import uuid4 | ||
|
||
from azure.keyvault.secrets import KeyVaultSecret | ||
from onefuzztypes.models import SecretAddress, SecretData | ||
from pydantic import BaseModel | ||
|
||
from .azure.creds import get_instance_name, get_keyvault_client | ||
|
||
A = TypeVar("A", bound=BaseModel) | ||
|
||
|
||
def save_to_keyvault(secret_data: SecretData) -> None: | ||
if isinstance(secret_data.secret, SecretAddress): | ||
return | ||
|
||
secret_name = str(uuid4()) | ||
if isinstance(secret_data.secret, str): | ||
secret_value = secret_data.secret | ||
elif isinstance(secret_data.secret, BaseModel): | ||
secret_value = secret_data.secret.json() | ||
else: | ||
raise Exception("invalid secret data") | ||
|
||
kv = store_in_keyvault(get_keyvault_address(), secret_name, secret_value) | ||
secret_data.secret = SecretAddress(url=kv.id) | ||
|
||
|
||
def get_secret_string_value(self: SecretData[str]) -> str: | ||
if isinstance(self.secret, SecretAddress): | ||
secret = get_secret(self.secret.url) | ||
return cast(str, secret.value) | ||
else: | ||
return self.secret | ||
|
||
|
||
def get_keyvault_address() -> str: | ||
# https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#vault-name-and-object-name | ||
return f"https://{get_instance_name()}-vault.vault.azure.net" | ||
|
||
|
||
def store_in_keyvault( | ||
keyvault_url: str, secret_name: str, secret_value: str | ||
) -> KeyVaultSecret: | ||
keyvault_client = get_keyvault_client(keyvault_url) | ||
kvs: KeyVaultSecret = keyvault_client.set_secret(secret_name, secret_value) | ||
return kvs | ||
|
||
|
||
def parse_secret_url(secret_url: str) -> Tuple[str, str]: | ||
# format: https://{vault-name}.vault.azure.net/secrets/{secret-name}/{version} | ||
u = urlparse(secret_url) | ||
vault_url = f"{u.scheme}://{u.netloc}" | ||
secret_name = u.path.split("/")[2] | ||
return (vault_url, secret_name) | ||
|
||
|
||
def get_secret(secret_url: str) -> KeyVaultSecret: | ||
(vault_url, secret_name) = parse_secret_url(secret_url) | ||
keyvault_client = get_keyvault_client(vault_url) | ||
return keyvault_client.get_secret(secret_name) | ||
|
||
|
||
def get_secret_obj(secret_url: str, model: Type[A]) -> A: | ||
secret = get_secret(secret_url) | ||
return model.parse_raw(secret.value) | ||
|
||
|
||
def delete_secret(secret_url: str) -> None: | ||
(vault_url, secret_name) = parse_secret_url(secret_url) | ||
keyvault_client = get_keyvault_client(vault_url) | ||
keyvault_client.begin_delete_secret(secret_name).wait() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#!/usr/bin/env python | ||
# | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
|
||
import json | ||
import unittest | ||
|
||
from onefuzztypes.enums import OS, ContainerType | ||
from onefuzztypes.job_templates import ( | ||
JobTemplate, | ||
JobTemplateIndex, | ||
JobTemplateNotification, | ||
) | ||
from onefuzztypes.models import ( | ||
JobConfig, | ||
Notification, | ||
NotificationConfig, | ||
SecretAddress, | ||
SecretData, | ||
TeamsTemplate, | ||
) | ||
from onefuzztypes.primitives import Container | ||
|
||
from __app__.onefuzzlib.orm import ORMMixin | ||
|
||
|
||
class TestSecret(unittest.TestCase): | ||
def test_hide(self) -> None: | ||
def hider(secret_data: SecretData) -> None: | ||
if not isinstance(secret_data.secret, SecretAddress): | ||
secret_data.secret = SecretAddress(url="blah blah") | ||
|
||
notification = Notification( | ||
container=Container("data"), | ||
config=TeamsTemplate(url=SecretData(secret="http://test")), | ||
) | ||
ORMMixin.hide_secrets(notification, hider) | ||
|
||
if isinstance(notification.config, TeamsTemplate): | ||
self.assertIsInstance(notification.config.url, SecretData) | ||
self.assertIsInstance(notification.config.url.secret, SecretAddress) | ||
else: | ||
self.fail(f"Invalid config type {type(notification.config)}") | ||
|
||
def test_hide_nested_list(self) -> None: | ||
def hider(secret_data: SecretData) -> None: | ||
if not isinstance(secret_data.secret, SecretAddress): | ||
secret_data.secret = SecretAddress(url="blah blah") | ||
|
||
job_template_index = JobTemplateIndex( | ||
name="test", | ||
template=JobTemplate( | ||
os=OS.linux, | ||
job=JobConfig(name="test", build="test", project="test", duration=1), | ||
tasks=[], | ||
notifications=[ | ||
JobTemplateNotification( | ||
container_type=ContainerType.unique_inputs, | ||
notification=NotificationConfig( | ||
config=TeamsTemplate(url=SecretData(secret="http://test")) | ||
), | ||
) | ||
], | ||
user_fields=[], | ||
), | ||
) | ||
ORMMixin.hide_secrets(job_template_index, hider) | ||
notification = job_template_index.template.notifications[0].notification | ||
if isinstance(notification.config, TeamsTemplate): | ||
self.assertIsInstance(notification.config.url, SecretData) | ||
self.assertIsInstance(notification.config.url.secret, SecretAddress) | ||
else: | ||
self.fail(f"Invalid config type {type(notification.config)}") | ||
|
||
def test_read_secret(self) -> None: | ||
json_data = """ | ||
{ | ||
"notification_id": "b52b24d1-eec6-46c9-b06a-818a997da43c", | ||
"container": "data", | ||
"config" : {"url": {"secret": {"url": "http://test"}}} | ||
} | ||
""" | ||
data = json.loads(json_data) | ||
notification = Notification.parse_obj(data) | ||
self.assertIsInstance(notification.config, TeamsTemplate) | ||
if isinstance(notification.config, TeamsTemplate): | ||
self.assertIsInstance(notification.config.url, SecretData) | ||
self.assertIsInstance(notification.config.url.secret, SecretAddress) | ||
else: | ||
self.fail(f"Invalid config type {type(notification.config)}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.