delete secret on object delete (#1085)

This commit is contained in:
bmc-msft
2021-07-21 16:04:27 -04:00
committed by GitHub
parent 041d3cc204
commit 3269dbb1aa
3 changed files with 98 additions and 2 deletions

View File

@ -40,7 +40,7 @@ from pydantic import BaseModel
from typing_extensions import Protocol from typing_extensions import Protocol
from .azure.table import get_client from .azure.table import get_client
from .secrets import save_to_keyvault from .secrets import delete_remote_secret_data, save_to_keyvault
from .telemetry import track_event_filtered from .telemetry import track_event_filtered
from .updates import queue_update from .updates import queue_update
@ -249,6 +249,28 @@ def hide_secrets(data: B, hider: Callable[[SecretData], SecretData]) -> B:
return data return data
# NOTE: the actual deletion must come from the `deleter` callback function
def delete_secrets(data: B, deleter: Callable[[SecretData], None]) -> None:
for field in data.__fields__:
field_data = getattr(data, field)
if isinstance(field_data, SecretData):
deleter(field_data)
elif isinstance(field_data, BaseModel):
delete_secrets(field_data, deleter)
elif isinstance(field_data, list):
for entry in field_data:
if isinstance(entry, BaseModel):
delete_secrets(entry, deleter)
elif isinstance(entry, SecretData):
deleter(entry)
elif isinstance(field_data, dict):
for value in field_data.values():
if isinstance(value, BaseModel):
delete_secrets(value, deleter)
elif isinstance(value, SecretData):
deleter(value)
# NOTE: if you want to include Timestamp in a model that uses ORMMixin, # NOTE: if you want to include Timestamp in a model that uses ORMMixin,
# it must be maintained as part of the model. # it must be maintained as part of the model.
class ORMMixin(ModelMixin): class ORMMixin(ModelMixin):
@ -363,6 +385,8 @@ class ORMMixin(ModelMixin):
def delete(self) -> None: def delete(self) -> None:
partition_key, row_key = self.get_keys() partition_key, row_key = self.get_keys()
delete_secrets(self, delete_remote_secret_data)
client = get_client() client = get_client()
try: try:
client.delete_entity( client.delete_entity(

View File

@ -80,3 +80,8 @@ def delete_secret(secret_url: str) -> None:
(vault_url, secret_name) = parse_secret_url(secret_url) (vault_url, secret_name) = parse_secret_url(secret_url)
keyvault_client = get_keyvault_client(vault_url) keyvault_client = get_keyvault_client(vault_url)
keyvault_client.begin_delete_secret(secret_name) keyvault_client.begin_delete_secret(secret_name)
def delete_remote_secret_data(data: SecretData) -> None:
if isinstance(data.secret, SecretAddress):
delete_secret(data.secret.url)

View File

@ -6,6 +6,7 @@
import json import json
import pathlib import pathlib
import unittest import unittest
from typing import Dict, List
from onefuzztypes.enums import OS, ContainerType from onefuzztypes.enums import OS, ContainerType
from onefuzztypes.job_templates import ( from onefuzztypes.job_templates import (
@ -26,8 +27,9 @@ from onefuzztypes.models import (
) )
from onefuzztypes.primitives import Container from onefuzztypes.primitives import Container
from onefuzztypes.requests import NotificationCreate from onefuzztypes.requests import NotificationCreate
from pydantic import BaseModel
from __app__.onefuzzlib.orm import hide_secrets from __app__.onefuzzlib.orm import delete_secrets, hide_secrets
def hider(secret_data: SecretData) -> SecretData: def hider(secret_data: SecretData) -> SecretData:
@ -36,7 +38,72 @@ def hider(secret_data: SecretData) -> SecretData:
return secret_data return secret_data
class WithSecret(BaseModel):
a: SecretData[str]
class WithList(BaseModel):
a: List[WithSecret]
class WithDict(BaseModel):
a: Dict[str, SecretData[str]]
b: Dict[str, WithSecret]
class Nested(BaseModel):
a: WithSecret
b: WithDict
c: WithList
class TestSecret(unittest.TestCase): class TestSecret(unittest.TestCase):
def test_delete(self) -> None:
self.count = 0
def deleter(secret_data: SecretData) -> None:
self.count += 1
delete_secrets(WithSecret(a=SecretData("a")), deleter)
self.assertEqual(self.count, 1)
delete_secrets(
WithList(
a=[
WithSecret(a=SecretData("a")),
WithSecret(a=SecretData("b")),
]
),
deleter,
)
self.assertEqual(self.count, 3)
delete_secrets(
WithDict(
a={"a": SecretData("a"), "b": SecretData("b")},
b={
"a": WithSecret(a=SecretData("a")),
"b": WithSecret(a=SecretData("a")),
},
),
deleter,
)
self.assertEqual(self.count, 7)
delete_secrets(
Nested(
a=WithSecret(a=SecretData("a")),
b=WithDict(
a={"a": SecretData("a")}, b={"a": WithSecret(a=SecretData("a"))}
),
c=WithList(
a=[WithSecret(a=SecretData("a")), WithSecret(a=SecretData("b"))]
),
),
deleter,
)
self.assertEqual(self.count, 12)
def test_hide(self) -> None: def test_hide(self) -> None:
notification = Notification( notification = Notification(
container=Container("data"), container=Container("data"),