Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8abe8cb
initial requirements for secrets
dhruv-droid-r2 Mar 4, 2025
25dc500
crud endpoints for the secrets and refactoring of the code
dhruv-droid-r2 Mar 4, 2025
1464039
fixes to URLs and fernet key default
dhruv-droid-r2 Mar 4, 2025
268c9cc
remove potentially sensitive log
dhruv-droid-r2 Mar 4, 2025
abd15ee
Merge branch 'main' of https://github.com/DrDroidLab/PlayBooks into f…
jayeshsadhwani99 Mar 5, 2025
13fdfcf
adds basic page+redux setup
jayeshsadhwani99 Mar 5, 2025
58efda1
fix typo
jayeshsadhwani99 Mar 5, 2025
772d9be
add create/update/delete apis
jayeshsadhwani99 Mar 5, 2025
c0a08d7
fix build issues
jayeshsadhwani99 Mar 5, 2025
4ac105f
change ui icon/name
jayeshsadhwani99 Mar 5, 2025
38b3987
Update Processor pattern used in secret updation
dhruv-droid-r2 Mar 5, 2025
791a63d
back merge with secret management
jayeshsadhwani99 Mar 5, 2025
90cdd8f
adds all apis and secrets
jayeshsadhwani99 Mar 5, 2025
d31e424
remove str from the model
jayeshsadhwani99 Mar 5, 2025
c190586
Merge pull request #508 from DrDroidLab/feature/secret-management-ui
jayeshsadhwani99 Mar 5, 2025
f7e5162
Preliminary Working version of secret management with basic validatio…
dhruv-droid-r2 Mar 5, 2025
26d0117
fixes and clean up
dhruv-droid-r2 Mar 5, 2025
57b9ab8
clean up and removal of unneeded fields
dhruv-droid-r2 Mar 5, 2025
48b3e11
Merge branch 'main' of github.com:DrDroidLab/PlayBooks into feature/s…
dhruv-droid-r2 Mar 10, 2025
b19a4e8
added crud manager for secrets and stricter checks for ownership
dhruv-droid-r2 Mar 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Generated by Django 4.1.13 on 2025-03-05 18:05

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import encrypted_model_fields.fields
import uuid


class Migration(migrations.Migration):

dependencies = [
('accounts', '0003_accountuseroauth2sessioncodestore'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('executor', '0045_upgrade_step_relation_conditions'),
]

operations = [
migrations.CreateModel(
name='Secret',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('key', models.CharField(help_text='Reference key for the secret', max_length=255)),
('value', encrypted_model_fields.fields.EncryptedTextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_active', models.BooleanField(default=True)),
('description', models.TextField(blank=True)),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='accounts.account')),
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_secrets', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddIndex(
model_name='secret',
index=models.Index(fields=['key', 'account', 'is_active'], name='executor_se_key_60cc82_idx'),
),
migrations.AddConstraint(
model_name='secret',
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('key', 'account', 'is_active'), name='unique_active_key_per_account'),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 4.1.13 on 2025-03-12 08:16

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("executor", "0046_secret_secret_executor_se_key_60cc82_idx_and_more"),
]

operations = [
migrations.RemoveConstraint(
model_name="secret",
name="unique_active_key_per_account",
),
migrations.AddIndex(
model_name="secret",
index=models.Index(
fields=["account", "created_by"], name="executor_se_account_02f4c3_idx"
),
),
migrations.AddConstraint(
model_name="secret",
constraint=models.UniqueConstraint(
fields=("key", "account"), name="unique_key_per_account"
),
),
]
69 changes: 68 additions & 1 deletion executor/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@
PlaybookStepExecutionLog as PlaybookStepExecutionLogProto, PlaybookExecution as PlaybookExecutionProto, \
PlaybookStepRelation as PlaybookStepRelationProto, \
PlaybookStepRelationExecutionLog as PlaybookStepRelationExecutionLogProto, PlaybookStepResultCondition
from utils.model_utils import generate_choices
from protos.secrets.api_pb2 import Secret as SecretProto

from accounts.models import Account
from utils.proto_utils import dict_to_proto
from utils.model_utils import generate_choices
from encrypted_model_fields.fields import EncryptedTextField
import uuid


class PlayBookTask(models.Model):
Expand Down Expand Up @@ -670,3 +673,67 @@ def proto(self) -> PlaybookStepRelationExecutionLogProto:
InterpretationProto) if self.interpretation else InterpretationProto(),

)


class Secret(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
key = models.CharField(max_length=255, help_text="Reference key for the secret")
value = EncryptedTextField()
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE)
created_by = models.ForeignKey('accounts.User', on_delete=models.SET_NULL, null=True, related_name='created_secrets')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=True)
description = models.TextField(blank=True)

class Meta:
constraints = [
models.UniqueConstraint(
fields=['key', 'account'],
name='unique_key_per_account'
)
]
indexes = [
models.Index(fields=['key', 'account', 'is_active']),
models.Index(fields=['account', 'created_by']),
]

def deactivate(self):
"""
Deactivate a secret by setting is_active to False and modifying the key
to prevent conflicts with future secrets using the same key
"""
if not self.is_active:
return False

self.is_active = False
# Generate a unique suffix for the key
unique_suffix = str(uuid.uuid4())
self.key = f"{self.key}_inactive_{unique_suffix}"
self.save(update_fields=['is_active', 'key', 'updated_at'])
return True

@staticmethod
def _mask_secret_value(value):
"""Mask secret value for display purposes"""
if not value or len(value) <= 8:
return "••••••••"
return value[:2] + "••••••" + value[-2:]

def to_proto(self, include_masked_value: bool = True) -> 'SecretProto':
"""Convert this Secret model to a Secret proto with full details"""

proto = SecretProto(
id=StringValue(value=str(self.id)),
key=StringValue(value=self.key),
description=StringValue(value=self.description or ""),
created_by=StringValue(value=self.created_by.email if self.created_by else ""),
created_at=int(self.created_at.replace(tzinfo=timezone.utc).timestamp()) if self.created_at else 0,
updated_at=int(self.updated_at.replace(tzinfo=timezone.utc).timestamp()) if self.updated_at else 0,
is_active=self.is_active
)

if include_masked_value:
proto.masked_value.value = self._mask_secret_value(self.value)

return proto
14 changes: 12 additions & 2 deletions executor/playbook_source_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from protos.playbooks.source_task_definitions.lambda_function_task_pb2 import Lambda
from protos.ui_definition_pb2 import FormField
from utils.proto_utils import proto_to_dict, dict_to_proto
from executor.secrets.secret_resolver import SecretResolver


def apply_result_transformer(result_dict, lambda_function: Lambda.Function) -> Dict:
Expand Down Expand Up @@ -191,9 +192,18 @@ def execute_task(self, account_id, time_range: TimeRange, global_variable_set: S

form_fields = self.task_type_callable_map[task_type]['form_fields']

# Resolve global variables in source_type_task_def
# First resolve secrets
resolved_source_type_task_def = SecretResolver.resolve_secrets(
form_fields=form_fields,
account_id=account_id,
source_type_task_def=source_type_task_def
)

# Then resolve global variables
resolved_source_type_task_def, task_local_variable_map = resolve_global_variables(
form_fields, global_variable_set, source_type_task_def)
form_fields, global_variable_set, resolved_source_type_task_def
)

source_task[task_type_name] = resolved_source_type_task_def
resolved_task_def_proto = dict_to_proto(source_task, self.task_proto)

Expand Down
Empty file added executor/secrets/__init__.py
Empty file.
1 change: 1 addition & 0 deletions executor/secrets/crud/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Loading