Skip to content

Commit 3c831f8

Browse files
authored
[azure][feat] Improve KeyVault (#2163)
1 parent c06a775 commit 3c831f8

File tree

5 files changed

+77
-32
lines changed

5 files changed

+77
-32
lines changed

plugins/azure/fix_plugin_azure/resource/keyvault.py

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GraphBuilder,
1616
)
1717
from fix_plugin_azure.resource.monitor import AzureMonitorDiagnosticSettings
18+
from fix_plugin_azure.utils import TimestampToIso
1819
from fixlib.baseresources import ModelReference
1920
from fixlib.json_bender import Bender, S, ForallBend, Bend
2021
from fixlib.types import Json
@@ -166,24 +167,24 @@ class AzureKeyVaultPrivateEndpointConnectionItem:
166167

167168

168169
@define(eq=False, slots=False)
169-
class AzureKeyAttributes:
170-
kind: ClassVar[str] = "azure_key_attributes"
170+
class AzureKeyVaultAttributes:
171+
kind: ClassVar[str] = "azure_key_vault_attributes"
171172
mapping: ClassVar[Dict[str, Bender]] = {
172-
"created": S("created"),
173+
"created": S("created") >> TimestampToIso,
173174
"enabled": S("enabled"),
174-
"exp": S("exp"),
175+
"expire": S("exp") >> TimestampToIso,
175176
"exportable": S("exportable"),
176177
"nbf": S("nbf"),
177178
"recovery_level": S("recoveryLevel"),
178-
"updated": S("updated"),
179+
"updated": S("updated") >> TimestampToIso,
179180
}
180-
created: Optional[int] = field(default=None, metadata={'description': 'Creation time in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
181+
created: Optional[datetime] = field(default=None, metadata={'description': 'Creation time in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
181182
enabled: Optional[bool] = field(default=None, metadata={'description': 'Determines whether or not the object is enabled.'}) # fmt: skip
182-
exp: Optional[int] = field(default=None, metadata={'description': 'Expiry date in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
183+
expire: Optional[datetime] = field(default=None, metadata={'description': 'Expiry date in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
183184
exportable: Optional[bool] = field(default=None, metadata={'description': 'Indicates if the private key can be exported.'}) # fmt: skip
184185
nbf: Optional[int] = field(default=None, metadata={'description': 'Not before date in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
185186
recovery_level: Optional[str] = field(default=None, metadata={'description': 'The deletion recovery level currently in effect for the object. If it contains Purgeable , then the object can be permanently deleted by a privileged user; otherwise, only the system can purge the object at the end of the retention interval.'}) # fmt: skip
186-
updated: Optional[int] = field(default=None, metadata={'description': 'Last updated time in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
187+
updated: Optional[datetime] = field(default=None, metadata={'description': 'Last updated time in seconds since 1970-01-01T00:00:00Z.'}) # fmt: skip
187188

188189

189190
@define(eq=False, slots=False)
@@ -222,8 +223,8 @@ class AzureKeyVaultLifetimeAction:
222223

223224

224225
@define(eq=False, slots=False)
225-
class AzureKeyVaultRotationPolicy:
226-
kind: ClassVar[str] = "azure_key_vault_rotation_policy"
226+
class AzureKeyRotationPolicy:
227+
kind: ClassVar[str] = "azure_key_rotation_policy"
227228
mapping: ClassVar[Dict[str, Bender]] = {
228229
"attributes": S("attributes") >> Bend(AzureKeyRotationPolicyAttributes.mapping),
229230
"lifetime_actions": S("lifetimeActions") >> ForallBend(AzureKeyVaultLifetimeAction.mapping),
@@ -240,6 +241,29 @@ class AzureKeyReleasePolicy:
240241
data: Optional[str] = field(default=None, metadata={'description': 'Blob encoding the policy rules under which the key can be released.'}) # fmt: skip
241242

242243

244+
@define(eq=False, slots=False)
245+
class AzureSecret(MicrosoftResource):
246+
kind: ClassVar[str] = "azure_secret"
247+
# collected via AzureKeyVault
248+
mapping: ClassVar[Dict[str, Bender]] = {
249+
"id": S("id"),
250+
"ctime": S("properties", "attributes", "created") >> TimestampToIso,
251+
"mtime": S("properties", "attributes", "updated") >> TimestampToIso,
252+
"tags": S("tags", default={}),
253+
"name": S("name"),
254+
"secret_attributes": S("properties", "attributes") >> Bend(AzureKeyVaultAttributes.mapping),
255+
"content_type": S("properties", "contentType"),
256+
"secret_uri": S("properties", "secretUri"),
257+
"secret_uri_with_version": S("properties", "secretUriWithVersion"),
258+
"value": S("properties", "value"),
259+
}
260+
secret_attributes: Optional[AzureKeyVaultAttributes] = field(default=None, metadata={'description': 'The secret management attributes.'}) # fmt: skip
261+
content_type: Optional[str] = field(default=None, metadata={"description": "The content type of the secret."})
262+
secret_uri: Optional[str] = field(default=None, metadata={'description': 'The URI to retrieve the current version of the secret.'}) # fmt: skip
263+
secret_uri_with_version: Optional[str] = field(default=None, metadata={'description': 'The URI to retrieve the specific version of the secret.'}) # fmt: skip
264+
value: Optional[str] = field(default=None, metadata={'description': 'The value of the secret. NOTE: value will never be returned from the service, as APIs using this model are is intended for internal use in ARM deployments. Users should use the data-plane REST service for interaction with vault secrets.'}) # fmt: skip
265+
266+
243267
@define(eq=False, slots=False)
244268
class AzureManagedHsm(MicrosoftResource):
245269
kind: ClassVar[str] = "azure_managed_hsm"
@@ -308,25 +332,27 @@ class AzureKey(MicrosoftResource):
308332
"id": S("id"),
309333
"tags": S("tags", default={}),
310334
"name": S("name"),
311-
"attributes": S("properties", "attributes") >> Bend(AzureKeyAttributes.mapping),
335+
"ctime": S("properties", "attributes", "created") >> TimestampToIso,
336+
"mtime": S("properties", "attributes", "updated") >> TimestampToIso,
337+
"key_attributes": S("properties", "attributes") >> Bend(AzureKeyVaultAttributes.mapping),
312338
"curve_name": S("properties", "curveName"),
313339
"key_ops": S("properties", "keyOps"),
314340
"key_size": S("properties", "keySize"),
315341
"key_uri": S("properties", "keyUri"),
316342
"key_uri_with_version": S("properties", "keyUriWithVersion"),
317343
"kty": S("properties", "kty"),
318344
"release_policy": S("properties", "release_policy") >> Bend(AzureKeyReleasePolicy.mapping),
319-
"rotation_policy": S("properties", "rotationPolicy") >> Bend(AzureKeyVaultRotationPolicy.mapping),
345+
"rotation_policy": S("properties", "rotationPolicy") >> Bend(AzureKeyRotationPolicy.mapping),
320346
}
321-
attributes: Optional[AzureKeyAttributes] = field(default=None, metadata={'description': 'The object attributes managed by the Azure Key Vault service.'}) # fmt: skip
347+
key_attributes: Optional[AzureKeyVaultAttributes] = field(default=None, metadata={'description': 'The object attributes managed by the Azure Key Vault service.'}) # fmt: skip
322348
curve_name: Optional[str] = field(default=None, metadata={'description': 'The elliptic curve name. For valid values, see JsonWebKeyCurveName.'}) # fmt: skip
323349
key_ops: Optional[List[str]] = field(default=None, metadata={"description": ""})
324350
key_size: Optional[int] = field(default=None, metadata={'description': 'The key size in bits. For example: 2048, 3072, or 4096 for RSA.'}) # fmt: skip
325351
key_uri: Optional[str] = field(default=None, metadata={'description': 'The URI to retrieve the current version of the key.'}) # fmt: skip
326352
key_uri_with_version: Optional[str] = field(default=None, metadata={'description': 'The URI to retrieve the specific version of the key.'}) # fmt: skip
327353
kty: Optional[str] = field(default=None, metadata={'description': 'The type of the key. For valid values, see JsonWebKeyType.'}) # fmt: skip
328354
release_policy: Optional[AzureKeyReleasePolicy] = field(default=None, metadata={"description": ""})
329-
rotation_policy: Optional[AzureKeyVaultRotationPolicy] = field(default=None, metadata={"description": ""})
355+
rotation_policy: Optional[AzureKeyRotationPolicy] = field(default=None, metadata={"description": ""})
330356

331357

332358
@define(eq=False, slots=False)
@@ -352,15 +378,15 @@ class AzureKeyVault(MicrosoftResource):
352378
"mtime": S("systemData", "lastModifiedAt"),
353379
"access_policies": S("properties", "accessPolicies") >> ForallBend(AzureAccessKeyVaultPolicyEntry.mapping),
354380
"create_mode": S("properties", "createMode"),
355-
"enable_purge_protection": S("properties", "enablePurgeProtection"),
356-
"enable_rbac_authorization": S("properties", "enableRbacAuthorization"),
357-
"enable_soft_delete": S("properties", "enableSoftDelete"),
381+
"purge_protection": S("properties", "enablePurgeProtection", default=False),
382+
"rbac_authorization": S("properties", "enableRbacAuthorization", default=False),
383+
"soft_delete": S("properties", "enableSoftDelete", default=False),
358384
"enabled_for_deployment": S("properties", "enabledForDeployment"),
359385
"enabled_for_disk_encryption": S("properties", "enabledForDiskEncryption"),
360386
"enabled_for_template_deployment": S("properties", "enabledForTemplateDeployment"),
361387
"hsm_pool_resource_id": S("properties", "hsmPoolResourceId"),
362388
"network_acl_rules": S("properties", "networkAcls") >> Bend(AzureKeyVaultNetworkRuleSet.mapping),
363-
"private_endpoint_connections": S("properties", "privateEndpointConnections")
389+
"vault_private_endpoint_connections": S("properties", "privateEndpointConnections")
364390
>> ForallBend(AzureKeyVaultPrivateEndpointConnectionItem.mapping),
365391
"provisioning_state": S("properties", "provisioningState"),
366392
"public_network_access": S("properties", "publicNetworkAccess"),
@@ -372,9 +398,9 @@ class AzureKeyVault(MicrosoftResource):
372398
}
373399
access_policies: Optional[List[AzureAccessKeyVaultPolicyEntry]] = field(default=None, metadata={'description': 'An array of 0 to 1024 identities that have access to the key vault. All identities in the array must use the same tenant ID as the key vault s tenant ID. When `createMode` is set to `recover`, access policies are not required. Otherwise, access policies are required.'}) # fmt: skip
374400
create_mode: Optional[str] = field(default=None, metadata={'description': 'The vault s create mode to indicate whether the vault need to be recovered or not.'}) # fmt: skip
375-
enable_purge_protection: Optional[bool] = field(default=None, metadata={'description': 'Property specifying whether protection against purge is enabled for this vault. Setting this property to true activates protection against purge for this vault and its content - only the Key Vault service may initiate a hard, irrecoverable deletion. The setting is effective only if soft delete is also enabled. Enabling this functionality is irreversible - that is, the property does not accept false as its value.'}) # fmt: skip
376-
enable_rbac_authorization: Optional[bool] = field(default=None, metadata={'description': 'Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. If null or not specified, the vault is created with the default value of false. Note that management actions are always authorized with RBAC.'}) # fmt: skip
377-
enable_soft_delete: Optional[bool] = field(default=None, metadata={'description': 'Property to specify whether the soft delete functionality is enabled for this key vault. If it s not set to any value(true or false) when creating new key vault, it will be set to true by default. Once set to true, it cannot be reverted to false.'}) # fmt: skip
401+
purge_protection: Optional[bool] = field(default=None, metadata={'description': 'Property specifying whether protection against purge is enabled for this vault. Setting this property to true activates protection against purge for this vault and its content - only the Key Vault service may initiate a hard, irrecoverable deletion. The setting is effective only if soft delete is also enabled. Enabling this functionality is irreversible - that is, the property does not accept false as its value.'}) # fmt: skip
402+
rbac_authorization: Optional[bool] = field(default=None, metadata={'description': 'Property that controls how data actions are authorized. When true, the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored. When false, the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored. If null or not specified, the vault is created with the default value of false. Note that management actions are always authorized with RBAC.'}) # fmt: skip
403+
soft_delete: Optional[bool] = field(default=None, metadata={'description': 'Property to specify whether the soft delete functionality is enabled for this key vault. If it s not set to any value(true or false) when creating new key vault, it will be set to true by default. Once set to true, it cannot be reverted to false.'}) # fmt: skip
378404
enabled_for_deployment: Optional[bool] = field(default=None, metadata={'description': 'Property to specify whether Azure Virtual Machines are permitted to retrieve certificates stored as secrets from the key vault.'}) # fmt: skip
379405
enabled_for_disk_encryption: Optional[bool] = field(default=None, metadata={'description': 'Property to specify whether Azure Disk Encryption is permitted to retrieve secrets from the vault and unwrap keys.'}) # fmt: skip
380406
enabled_for_template_deployment: Optional[bool] = field(default=None, metadata={'description': 'Property to specify whether Azure Resource Manager is permitted to retrieve secrets from the key vault.'}) # fmt: skip
@@ -389,22 +415,23 @@ class AzureKeyVault(MicrosoftResource):
389415
vault_uri: Optional[str] = field(default=None, metadata={'description': 'The URI of the vault for performing operations on keys and secrets.'}) # fmt: skip
390416

391417
def post_process(self, graph_builder: GraphBuilder, source: Json) -> None:
392-
def collect_keys() -> None:
393-
for key_json in graph_builder.client.list(
418+
def collect_dependant(cls: Type[MicrosoftResource], name: str) -> None:
419+
for dep_json in graph_builder.client.list(
394420
AzureResourceSpec(
395421
service="keyvault",
396422
version="2023-07-01",
397-
path=f"{self.id}/keys",
423+
path=f"{self.id}/{name}",
398424
query_parameters=["api-version"],
399425
access_path="value",
400426
expect_array=True,
401427
)
402428
):
403-
if key := AzureKey.from_api(key_json, graph_builder):
404-
graph_builder.add_node(key)
405-
graph_builder.add_edge(self, node=key)
429+
if dep := cls.from_api(dep_json, graph_builder):
430+
graph_builder.add_node(dep)
431+
graph_builder.add_edge(self, node=dep)
406432

407-
graph_builder.submit_work(service_name, collect_keys)
433+
graph_builder.submit_work(service_name, collect_dependant, AzureKey, "keys")
434+
graph_builder.submit_work(service_name, collect_dependant, AzureSecret, "secrets")
408435
AzureMonitorDiagnosticSettings.fetch_diagnostics(graph_builder, self)
409436

410437

plugins/azure/fix_plugin_azure/utils.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import logging
2+
from datetime import datetime
23
from typing import Callable, Dict, TypeVar, Any
34
from attr import frozen
45
import functools
56

67
from fixlib.baseresources import StatName, MetricName, MetricUnit
7-
8+
from fixlib.json_bender import F
89

910
T = TypeVar("T")
1011
log = logging.getLogger("fix.plugins.azure")
@@ -67,6 +68,9 @@ def set_bool(val: str) -> bool:
6768
return None
6869

6970

71+
TimestampToIso = F(lambda x: datetime.fromtimestamp(x).isoformat())
72+
73+
7074
@frozen(kw_only=True)
7175
class MetricNormalization:
7276
metric_name: MetricName

plugins/azure/test/collector_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def test_collect(
4949
config, Cloud(id="azure"), azure_subscription, credentials, core_feedback
5050
)
5151
subscription_collector.collect()
52-
assert len(subscription_collector.graph.nodes) == 450
53-
assert len(subscription_collector.graph.edges) == 689
52+
assert len(subscription_collector.graph.nodes) == 451
53+
assert len(subscription_collector.graph.edges) == 691
5454

5555
graph_collector = MicrosoftGraphOrganizationCollector(
5656
config, Cloud(id="azure"), MicrosoftGraphOrganization(id="test", name="test"), credentials, core_feedback
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"value": [
3+
{
4+
"contentType": "plainText",
5+
"id": "https://myvault.vault.azure.net/secrets/listsecrettest0",
6+
"attributes": {
7+
"enabled": true,
8+
"created": 1482189047,
9+
"updated": 1482189047
10+
}
11+
}
12+
],
13+
"nextLink": "https://myvault.vault.azure.net:443/secrets?api-version=7.2&$skiptoken=eyJOZXh0TWFya2VyIjoiMiE4OCFNREF3TURJeUlYTmxZM0psZEM5TVNWTlVVMFZEVWtWVVZFVlRWREVoTURBd01ESTRJVEl3TVRZdE1USXRNVGxVTWpNNk1UQTZORFV1T0RneE9ERXhNRm9oIiwiVGFyZ2V0TG9jYXRpb24iOjB9&maxresults=1"
14+
}

plugins/azure/tools/azure_model_gen.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ def safe_idx(seq, index):
627627
)
628628
model = AzureModel(Path(specs_path))
629629
shapes = {spec.name: spec for spec in sorted(model.list_specs({"keyvault"}), key=lambda x: x.name)}
630-
models = classes_from_model(shapes, {"Key"})
630+
models = classes_from_model(shapes)
631631
for model in models.values():
632632
if model.name != "Resource":
633633
print(model.to_class())

0 commit comments

Comments
 (0)