Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion attack_range/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ Abstract base class defining the contract for all cloud providers:
- `delete_backend()`: Delete backend storage
- `import_ssh_key()`: Import SSH key (if applicable)
- `delete_ssh_key()`: Delete SSH key (if applicable)
- `update_backend_config()`: Update backend configuration file
- `write_backend_config()`: Create/update backend configuration file
- `get_backend_params()`: Get backend parameters for the current provider

#### AWSProvider (`cloud_providers/aws_provider.py`)
**Responsibility**: AWS-specific operations
Expand Down
34 changes: 27 additions & 7 deletions attack_range/cloud_providers/aws_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import Optional
import os

from .base_provider import BaseCloudProvider
from .base_provider import BaseCloudProvider, BackendParams


class AWSProvider(BaseCloudProvider):
Expand Down Expand Up @@ -308,17 +308,17 @@ def delete_ssh_key(self, key_name: str, region: str) -> None:
except Exception as e:
self.logger.warning(f"Failed to delete key pair '{key_name}': {e}")

def update_backend_config(self, backend_params: dict, backend_file_path: str) -> None:
def write_backend_config(self, backend_params: BackendParams, backend_file_path: str) -> None:
"""
Update or create backend.tf file with S3 backend configuration.

:param backend_params: Dictionary containing backend parameters
:param backend_params: Backend parameters
:param backend_file_path: Path to the backend.tf file
"""
bucket_name = backend_params['bucket_name']
region = backend_params['region']
attack_range_id = backend_params.get('attack_range_id', 'unknown')
config_source = backend_params.get('config_source', 'template/config file')
bucket_name = backend_params.aws_bucket_name
region = backend_params.region
attack_range_id = backend_params.attack_range_id or 'unknown'
config_source = backend_params.config_source or 'template/config file'

backend_config = f'''# This file is AUTO-GENERATED based on the template/config file.
# DO NOT EDIT MANUALLY - changes will be overwritten.
Expand All @@ -345,3 +345,23 @@ def update_backend_config(self, backend_params: dict, backend_file_path: str) ->
f.write(backend_config)

self.logger.info(f"Backend configuration written to {backend_file_path} (generated from {config_source})")

def get_backend_params(self, attack_range_id: str, config_source: str = "template/config file") -> BackendParams:
"""
Get backend parameters for AWS (S3 bucket).

:param attack_range_id: The attack range ID for naming
:param config_source: Source config file name for backend.tf comments
:return: Backend parameters
"""
backend_name = f"terraform-state-{attack_range_id}"
bucket_name = self.sanitize_name(backend_name)
region = self.get_region(required=True)

return BackendParams(
backend_name=backend_name,
aws_bucket_name=bucket_name,
region=region,
attack_range_id=attack_range_id,
config_source=config_source
)
43 changes: 34 additions & 9 deletions attack_range/cloud_providers/azure_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from typing import Optional
import os

from .base_provider import BaseCloudProvider
from .base_provider import BaseCloudProvider, BackendParams

# Azure imports
try:
Expand Down Expand Up @@ -343,19 +343,19 @@ def delete_ssh_key(self, key_name: str, region: str) -> None:
"""
self.logger.info("Azure: No cloud service keys to delete")

def update_backend_config(self, backend_params: dict, backend_file_path: str) -> None:
def write_backend_config(self, backend_params: BackendParams, backend_file_path: str) -> None:
"""
Update or create backend.tf file with Azure backend configuration.

:param backend_params: Dictionary containing backend parameters
:param backend_params: Backend parameters
:param backend_file_path: Path to the backend.tf file
"""
storage_account_name = backend_params['storage_account_name']
container_name = backend_params['container_name']
resource_group_name = backend_params['resource_group_name']
location = backend_params['location']
attack_range_id = backend_params.get('attack_range_id', 'unknown')
config_source = backend_params.get('config_source', 'template/config file')
storage_account_name = backend_params.azure_storage_account_name
container_name = backend_params.azure_container_name
resource_group_name = backend_params.azure_resource_group_name
location = backend_params.azure_location or backend_params.region
attack_range_id = backend_params.attack_range_id or 'unknown'
config_source = backend_params.config_source or 'template/config file'

backend_config = f'''# This file is AUTO-GENERATED based on the template/config file.
# DO NOT EDIT MANUALLY - changes will be overwritten.
Expand Down Expand Up @@ -383,3 +383,28 @@ def update_backend_config(self, backend_params: dict, backend_file_path: str) ->
f.write(backend_config)

self.logger.info(f"Backend configuration written to {backend_file_path} (generated from {config_source})")

def get_backend_params(self, attack_range_id: str, config_source: str = "template/config file") -> BackendParams:
"""
Get backend parameters for Azure (Storage Account + Container).

:param attack_range_id: The attack range ID for naming
:param config_source: Source config file name for backend.tf comments
:return: Backend parameters
"""
backend_name = f"terraformstate{attack_range_id.replace('-', '')}"
storage_account_name = self.sanitize_name(backend_name)
container_name = "tfstate"
resource_group_name = f"rg-terraform-state-{attack_range_id}"
location = self.get_region(required=True)

return BackendParams(
backend_name=backend_name,
azure_storage_account_name=storage_account_name,
azure_container_name=container_name,
azure_resource_group_name=resource_group_name,
azure_location=location,
region=location,
attack_range_id=attack_range_id,
config_source=config_source
)
40 changes: 37 additions & 3 deletions attack_range/cloud_providers/base_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,28 @@
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
import logging


@dataclass
class BackendParams:
"""Shared backend parameters for all cloud providers."""

backend_name: str
region: str
attack_range_id: str
config_source: str = "template/config file"
aws_bucket_name: Optional[str] = None
gcp_bucket_name: Optional[str] = None
gcp_project_id: Optional[str] = None
azure_storage_account_name: Optional[str] = None
azure_container_name: Optional[str] = None
azure_resource_group_name: Optional[str] = None
azure_location: Optional[str] = None


class BaseCloudProvider(ABC):
"""Abstract base class for cloud providers."""

Expand Down Expand Up @@ -94,11 +112,27 @@ def delete_ssh_key(self, key_name: str, region: str) -> None:
pass

@abstractmethod
def update_backend_config(self, backend_params: dict, backend_file_path: str) -> None:
def write_backend_config(self, backend_params: BackendParams, backend_file_path: str) -> None:
"""
Update the backend configuration file.
Save on disk the backend configuration file.

:param backend_params: Dictionary containing backend parameters
:param backend_params: Backend parameters for the current provider
:param backend_file_path: Path to the backend.tf file
"""
pass

@abstractmethod
def get_backend_params(self, attack_range_id: str, config_source: str = "template/config file") -> BackendParams:
"""
Get backend parameters for the current provider.

Returns backend parameters with all information needed for backend setup:
- backend_name: Sanitized name for the storage resource
- region: Provider-specific region/location
- All provider-specific parameters needed for write_backend_config()

:param attack_range_id: The attack range ID for naming
:param config_source: Source config file name for backend.tf comments
:return: Backend parameters
"""
pass
36 changes: 29 additions & 7 deletions attack_range/cloud_providers/gcp_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from typing import Optional
import os

from .base_provider import BaseCloudProvider
from .base_provider import BaseCloudProvider, BackendParams

# GCP imports
try:
Expand Down Expand Up @@ -231,17 +231,17 @@ def delete_ssh_key(self, key_name: str, region: str) -> None:
"""
self.logger.info("GCP: No cloud service keys to delete")

def update_backend_config(self, backend_params: dict, backend_file_path: str) -> None:
def write_backend_config(self, backend_params: BackendParams, backend_file_path: str) -> None:
"""
Update or create backend.tf file with GCP backend configuration.

:param backend_params: Dictionary containing backend parameters
:param backend_params: Backend parameters
:param backend_file_path: Path to the backend.tf file
"""
bucket_name = backend_params['bucket_name']
project_id = backend_params['project_id']
attack_range_id = backend_params.get('attack_range_id', 'unknown')
config_source = backend_params.get('config_source', 'template/config file')
bucket_name = backend_params.gcp_bucket_name
project_id = backend_params.gcp_project_id
attack_range_id = backend_params.attack_range_id or 'unknown'
config_source = backend_params.config_source or 'template/config file'

backend_config = f'''# This file is AUTO-GENERATED based on the template/config file.
# DO NOT EDIT MANUALLY - changes will be overwritten.
Expand All @@ -265,3 +265,25 @@ def update_backend_config(self, backend_params: dict, backend_file_path: str) ->
f.write(backend_config)

self.logger.info(f"Backend configuration written to {backend_file_path} (generated from {config_source})")

def get_backend_params(self, attack_range_id: str, config_source: str = "template/config file") -> BackendParams:
"""
Get backend parameters for GCP (GCS bucket).

:param attack_range_id: The attack range ID for naming
:param config_source: Source config file name for backend.tf comments
:return: Backend parameters
"""
backend_name = f"terraform-state-{attack_range_id}"
bucket_name = self.sanitize_name(backend_name)
project_id = self.get_project_id(required=True)
region = self.get_region(required=True)

return BackendParams(
backend_name=backend_name,
gcp_bucket_name=bucket_name,
gcp_project_id=project_id,
region=region,
attack_range_id=attack_range_id,
config_source=config_source
)
Loading