Skip to content

Commit

Permalink
Add MDM recovery password config Terraform export (#776)
Browse files Browse the repository at this point in the history
  • Loading branch information
np5 authored Aug 5, 2023
1 parent 9f690c0 commit 06bdfb7
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 11 deletions.
22 changes: 18 additions & 4 deletions tests/mdm/test_setup_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
from django.urls import reverse
from django.utils.crypto import get_random_string
from accounts.models import User
from .utils import force_artifact, force_blueprint, force_blueprint_artifact, force_filevault_config
from .utils import (force_artifact, force_blueprint, force_blueprint_artifact,
force_filevault_config, force_recovery_password_config)


@override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
Expand Down Expand Up @@ -83,7 +84,9 @@ def test_terraform_export(self):
self._login("mdm.view_blueprint")
fv_config1 = force_filevault_config()
fv_config2 = force_filevault_config()
blueprint = force_blueprint(filevault_config=fv_config1)
rp_config1 = force_recovery_password_config()
rp_config2 = force_recovery_password_config()
blueprint = force_blueprint(filevault_config=fv_config1, recovery_password_config=rp_config1)
required_artifact, (required_profile_av,) = force_artifact()
rprofile = required_profile_av.profile
rprofile_filename = f"{required_artifact.name.lower()}_{rprofile.pk}_v1.mobileconfig"
Expand All @@ -106,12 +109,23 @@ def test_terraform_export(self):
f' escrow_location_display_name = "{fv_config2.escrow_location_display_name}"\n'
'}\n\n'
)
with zf.open("mdm_recovery_password_configs.tf") as fctf:
self.assertEqual(
fctf.read().decode("utf-8"),
f'resource "zentral_mdm_recovery_password_config" "recoverypasswordconfig{rp_config1.pk}" {{\n'
f' name = "{rp_config1.name}"\n'
'}\n\n'
f'resource "zentral_mdm_recovery_password_config" "recoverypasswordconfig{rp_config2.pk}" {{\n'
f' name = "{rp_config2.name}"\n'
'}\n\n'
)
with zf.open("mdm_blueprints.tf") as btf:
self.assertEqual(
btf.read().decode("utf-8"),
f'resource "zentral_mdm_blueprint" "blueprint{blueprint.pk}" {{\n'
f' name = "{blueprint.name}"\n'
f' filevault_config_id = zentral_mdm_filevault_config.filevaultconfig{fv_config1.pk}.id\n'
f' name = "{blueprint.name}"\n'
f' filevault_config_id = zentral_mdm_filevault_config.filevaultconfig{fv_config1.pk}.id\n'
f' recovery_password_config_id = zentral_mdm_recovery_password_config.recoverypasswordconfig{rp_config1.pk}.id\n' # NOQA
'}\n\n'
f'resource "zentral_mdm_blueprint_artifact" "blueprintartifact{blueprint_artifact.pk}" {{\n'
f' blueprint_id = zentral_mdm_blueprint.blueprint{blueprint.pk}.id\n'
Expand Down
37 changes: 35 additions & 2 deletions tests/mdm/test_terraform.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from zentral.contrib.mdm.terraform import (ArtifactResource,
BlueprintResource, BlueprintArtifactResource,
FileVaultConfigResource,
ProfileResource)
from .utils import force_artifact, force_blueprint, force_blueprint_artifact, force_filevault_config
ProfileResource,
RecoveryPasswordConfigResource)
from .utils import (force_artifact, force_blueprint, force_blueprint_artifact,
force_filevault_config, force_recovery_password_config)


class MDMTerraformTestCase(TestCase):
Expand Down Expand Up @@ -135,6 +137,37 @@ def test_filevault_config_resource_full(self):
'}'
)

# recovery password config

def test_recovery_password_resource_defaults(self):
rp_config = force_recovery_password_config()
resource = RecoveryPasswordConfigResource(rp_config)
self.assertEqual(
resource.to_representation(),
f'resource "zentral_mdm_recovery_password_config" "recoverypasswordconfig{rp_config.pk}" {{\n'
f' name = "{rp_config.name}"\n'
'}'
)

def test_recovery_password_resource_full(self):
rp_config = force_recovery_password_config(
rotation_interval_days=90,
static_password="12345678",
)
rp_config.rotate_firmware_password = True
rp_config.save()
resource = RecoveryPasswordConfigResource(rp_config)
self.assertEqual(
resource.to_representation(),
f'resource "zentral_mdm_recovery_password_config" "recoverypasswordconfig{rp_config.pk}" {{\n'
f' name = "{rp_config.name}"\n'
' dynamic_password = false\n'
f' static_password = var.recoverypasswordconfig{rp_config.pk}_static_password\n'
' rotation_interval_days = 90\n'
' rotate_firmware_password = true\n'
'}'
)

# blueprint

def test_blueprint_resource_defaults(self):
Expand Down
16 changes: 15 additions & 1 deletion zentral/contrib/mdm/terraform.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .models import Artifact, Blueprint, FileVaultConfig
from .models import Artifact, Blueprint, FileVaultConfig, RecoveryPasswordConfig
from zentral.contrib.inventory.terraform import TagResource
from zentral.utils.terraform import BoolAttr, FileBase64Attr, IntAttr, MapAttr, RefAttr, Resource, StringAttr

Expand Down Expand Up @@ -31,6 +31,17 @@ class FileVaultConfigResource(Resource):
prk_rotation_interval_days = IntAttr(default=0)


class RecoveryPasswordConfigResource(Resource):
tf_type = "zentral_mdm_recovery_password_config"
tf_grouping_key = "mdm_recovery_password_configs"

name = StringAttr(required=True)
dynamic_password = BoolAttr(default=True)
static_password = StringAttr(secret=True)
rotation_interval_days = IntAttr(default=0)
rotate_firmware_password = BoolAttr(default=False)


class BlueprintResource(Resource):
tf_type = "zentral_mdm_blueprint"
tf_grouping_key = "mdm_blueprints"
Expand All @@ -44,6 +55,7 @@ class BlueprintResource(Resource):
collect_profiles = StringAttr(default=Blueprint.InventoryItemCollectionOption.NO.name,
source="get_collect_profiles_display")
filevault_config_id = RefAttr(FileVaultConfigResource)
recovery_password_config_id = RefAttr(RecoveryPasswordConfigResource)


# TODO: deduplicate Resource
Expand Down Expand Up @@ -119,6 +131,8 @@ def iter_resources():
yield BlueprintArtifactResource(blueprint_artifact)
for filevault_config in FileVaultConfig.objects.all():
yield FileVaultConfigResource(filevault_config)
for recovery_password_config in RecoveryPasswordConfig.objects.all():
yield RecoveryPasswordConfigResource(recovery_password_config)
for artifact in Artifact.objects.prefetch_related("requires").filter(type=Artifact.Type.PROFILE):
yield ArtifactResource(artifact)
for artifact_version in artifact.artifactversion_set.select_related("profile").order_by("-version"):
Expand Down
15 changes: 11 additions & 4 deletions zentral/utils/terraform.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def quote(i):


class Attr:
def __init__(self, many=False, required=False, source=None, default=None, call_value=True):
def __init__(self, many=False, required=False, source=None, default=None, call_value=True, secret=False):
self.many = many
self.required = required
self.source = source
Expand All @@ -73,6 +73,7 @@ def __init__(self, many=False, required=False, source=None, default=None, call_v
default = default()
self.default = default
self.call_value = call_value
self.secret = secret

def value_representation(self, value):
raise NotImplementedError
Expand All @@ -89,11 +90,17 @@ def get_value(self, instance, attr_name):
raw_value = raw_value()
return raw_value

def get_secret_var(self, instance, attr_name):
return f"{instance._meta.model_name}{instance.pk}_{attr_name}"

def iter_representation_lines(self, instance, attr_name):
value = self.get_value(instance, attr_name)
if not self.required and (value is None or value == self.default):
return
if self.many:
if self.secret:
secret_var = self.get_secret_var(instance, attr_name)
line = f"var.{secret_var}"
elif self.many:
line = "["
line += ", ".join(self.value_representation(i) for i in value)
line += "]"
Expand All @@ -103,10 +110,10 @@ def iter_representation_lines(self, instance, attr_name):


class StringAttr(Attr):
def __init__(self, many=False, required=False, source=None, default=None):
def __init__(self, many=False, required=False, source=None, default=None, secret=False):
if not many and not required and default is None:
default = ""
super().__init__(many=many, required=required, source=source, default=default)
super().__init__(many=many, required=required, source=source, default=default, secret=secret)

def value_representation(self, value):
if not isinstance(value, str):
Expand Down

0 comments on commit 06bdfb7

Please sign in to comment.