"""Campaign and CampaignTest models. Campaigns group multiple tests into a kill chain sequence, enabling simulation of complete attack chains and APT emulations. """ # Import uuid import uuid # Import from sqlalchemy from sqlalchemy import ( Boolean, Column, DateTime, ForeignKey, Index, Integer, String, Text, func, ) # Import JSONB, UUID from sqlalchemy.dialects.postgresql from sqlalchemy.dialects.postgresql import JSONB, UUID # Import relationship from sqlalchemy.orm from sqlalchemy.orm import relationship # Import Base from app.database from app.database import Base # Define class Campaign class Campaign(Base): """A campaign groups multiple tests into a sequenced attack chain. Types: - custom: manually created campaign - apt_emulation: generated from a threat actor profile - kill_chain: structured around kill chain phases - compliance: targeting specific compliance requirements Status: - draft: being configured - active: tests are being executed - completed: all tests done - archived: historical record """ # Assign __tablename__ = "campaigns" __tablename__ = "campaigns" # Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # Assign name = Column(String, nullable=False) name = Column(String, nullable=False) # Assign description = Column(Text, nullable=True) description = Column(Text, nullable=True) # Assign type = Column(String, nullable=False, default="custom") # custom, ap... type = Column(String, nullable=False, default="custom") # custom, apt_emulation, kill_chain, compliance # Assign threat_actor_id = Column( threat_actor_id = Column( UUID(as_uuid=True), ForeignKey("threat_actors.id", ondelete="SET NULL"), # Keyword argument: nullable nullable=True, ) # Assign status = Column(String, nullable=False, default="draft") # draft, activ... status = Column(String, nullable=False, default="draft") # draft, active, completed, archived # Assign created_by = Column( created_by = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), # Keyword argument: nullable nullable=True, ) # Assign scheduled_at = Column(DateTime, nullable=True) scheduled_at = Column(DateTime, nullable=True) # Assign completed_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True) # Assign target_platform = Column(String, nullable=True) target_platform = Column(String, nullable=True) # Assign tags = Column(JSONB, nullable=True, default=[]) tags = Column(JSONB, nullable=True, default=[]) # Assign created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Assign data_classification = Column(String(20), nullable=False, server_default="internal") data_classification = Column(String(20), nullable=False, server_default="internal") # Recurring scheduling fields is_recurring = Column(Boolean, default=False) # Assign recurrence_pattern = Column(String, nullable=True) # weekly, monthly, quarterly recurrence_pattern = Column(String, nullable=True) # weekly, monthly, quarterly # Assign next_run_at = Column(DateTime, nullable=True) next_run_at = Column(DateTime, nullable=True) # Assign last_run_at = Column(DateTime, nullable=True) last_run_at = Column(DateTime, nullable=True) # Assign parent_campaign_id = Column( parent_campaign_id = Column( UUID(as_uuid=True), ForeignKey("campaigns.id", ondelete="SET NULL"), # Keyword argument: nullable nullable=True, ) # Relationships threat_actor = relationship("ThreatActor") # Assign creator = relationship("User", foreign_keys=[created_by]) creator = relationship("User", foreign_keys=[created_by]) # Assign campaign_tests = relationship( campaign_tests = relationship( # Literal argument value "CampaignTest", # Keyword argument: back_populates back_populates="campaign", # Keyword argument: cascade cascade="all, delete-orphan", # Keyword argument: order_by order_by="CampaignTest.order_index", ) # Assign parent_campaign = relationship( parent_campaign = relationship( # Literal argument value "Campaign", # Keyword argument: remote_side remote_side="Campaign.id", # Keyword argument: foreign_keys foreign_keys=[parent_campaign_id], ) # Assign child_campaigns = relationship( child_campaigns = relationship( # Literal argument value "Campaign", # Keyword argument: foreign_keys foreign_keys=[parent_campaign_id], # Keyword argument: back_populates back_populates="parent_campaign", ) # Assign __table_args__ = ( __table_args__ = ( Index('ix_campaigns_status', 'status'), Index('ix_campaigns_type', 'type'), Index('ix_campaigns_threat_actor', 'threat_actor_id'), Index('ix_campaigns_created_by', 'created_by'), Index('ix_campaigns_next_run', 'next_run_at'), ) # Kill chain phases in order (for sorting and validation) KILL_CHAIN_PHASES = [ # Literal argument value "reconnaissance", # Literal argument value "resource_development", # Literal argument value "initial_access", # Literal argument value "execution", # Literal argument value "persistence", # Literal argument value "privilege_escalation", # Literal argument value "defense_evasion", # Literal argument value "credential_access", # Literal argument value "discovery", # Literal argument value "lateral_movement", # Literal argument value "collection", # Literal argument value "command_and_control", # Literal argument value "exfiltration", # Literal argument value "impact", ] # Define class CampaignTest class CampaignTest(Base): """A test within a campaign, with ordering and dependency information. ``depends_on`` creates a self-referential chain (A -> B -> C). Circular dependencies are validated at the service layer. """ # Assign __tablename__ = "campaign_tests" __tablename__ = "campaign_tests" # Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # Assign campaign_id = Column( campaign_id = Column( UUID(as_uuid=True), ForeignKey("campaigns.id", ondelete="CASCADE"), # Keyword argument: nullable nullable=False, ) # Assign test_id = Column( test_id = Column( UUID(as_uuid=True), ForeignKey("tests.id", ondelete="CASCADE"), # Keyword argument: nullable nullable=False, ) # Assign order_index = Column(Integer, nullable=False, default=0) order_index = Column(Integer, nullable=False, default=0) # Assign depends_on = Column( depends_on = Column( UUID(as_uuid=True), ForeignKey("campaign_tests.id", ondelete="SET NULL"), # Keyword argument: nullable nullable=True, ) # Assign phase = Column(String, nullable=True) # kill chain phase phase = Column(String, nullable=True) # kill chain phase # Relationships campaign = relationship("Campaign", back_populates="campaign_tests") # Assign test = relationship("Test") test = relationship("Test") # Assign dependency = relationship("CampaignTest", remote_side="CampaignTest.id") dependency = relationship("CampaignTest", remote_side="CampaignTest.id") # Assign __table_args__ = ( __table_args__ = ( Index('ix_campaign_tests_campaign', 'campaign_id'), Index('ix_campaign_tests_test', 'test_id'), )