From e35d1b4c57e1159259c82aaea5ec205926f711d1 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Tue, 7 Apr 2026 10:44:07 -0600 Subject: [PATCH 1/4] fix: improve handling of AD object lookups and ACL checks for special cases **Added:** - Added fallback logic in PowerShell to resolve gMSA and computer accounts by SamAccountName, Get-ADServiceAccount, and Get-ADComputer if $ was stripped - Introduced WaitForInstanceStopped method to EC2 client for polling stopped state - cli/internal/aws/ec2.go **Changed:** - Enhanced AD object lookup in acl role to robustly handle gMSA/computer accounts whose trailing $ may be stripped, improving idempotency and error handling - Improved ACL permission check logic in validator to support DN paths and handle gMSA/computer accounts by stripping trailing $ and using Get-Acl or Get-ADObject as appropriate - Updated lab command to use DiscoverInstances with "stopped" state for start actions and wait for stopped state before starting instances, improving state synchronization - Modified SSM connect command to ignore SIGINT in parent process so Ctrl+C is forwarded to the SSM session, preventing premature teardown - Clarified project root resolution and fallback logic in lab_config.py for more reliable path handling in plugin environments **Removed:** - Removed unconditional skip of ACLs with DN targets in validator to support more generic ACL verification --- ansible/playbooks/wait5m.yml | 1 + ansible/plugins/vars/lab_config.py | 31 +++++++++++++++---------- ansible/roles/acl/tasks/main.yml | 19 ++++++++++++++-- cli/cmd/lab.go | 13 +++++++++-- cli/cmd/ssm.go | 10 ++++++++- cli/internal/aws/ec2.go | 19 +++++++++++++--- cli/internal/validate/checks.go | 36 ++++++++++++++++++++++++------ 7 files changed, 102 insertions(+), 27 deletions(-) diff --git a/ansible/playbooks/wait5m.yml b/ansible/playbooks/wait5m.yml index bec7bba4..071c49a9 100644 --- a/ansible/playbooks/wait5m.yml +++ b/ansible/playbooks/wait5m.yml @@ -2,6 +2,7 @@ - name: Wait hosts: localhost connection: local + gather_facts: false tasks: - name: Wait 5 minutes to finish ansible.builtin.pause: diff --git a/ansible/plugins/vars/lab_config.py b/ansible/plugins/vars/lab_config.py index ee0bf042..5b5ba480 100644 --- a/ansible/plugins/vars/lab_config.py +++ b/ansible/plugins/vars/lab_config.py @@ -98,21 +98,28 @@ def get_vars(self, loader, path, entities, cache=True): else: return {} - # Resolve from the inventory source path (project root) - # path is the inventory directory passed by Ansible - if path: - base = path if os.path.isdir(path) else os.path.dirname(path) + # Resolve from the project root. Use the inventory source + # path (passed by Ansible) since the plugin may be installed + # in ~/.ansible/collections rather than the project tree. + # The inventory file sits at the project root, so its parent + # directory IS the project root. + if os.path.isfile(path): + project_root = os.path.dirname(os.path.abspath(path)) + elif os.path.isdir(path): + project_root = os.path.abspath(path) else: - base = os.getcwd() - - # The playbook_dir points to ansible/playbooks/, so ../../ goes - # to the project root. Since we're resolving from the project - # root (where the inventory lives), we need to adjust. - # "../../ad/GOAD/data" from ansible/playbooks/ == "ad/GOAD/data" from root - # Normalise by stripping leading "../" segments + # Fallback: try __file__-based resolution (works when + # the plugin lives in the project's ansible/plugins/vars/) + plugin_dir = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.normpath( + os.path.join(plugin_dir, "..", "..", "..", "..") + ) + + # Strip leading "../" segments from the tail since we're + # resolving from the project root directly. while tail.startswith("../"): tail = tail[3:] - data_path = os.path.join(base, tail) + data_path = os.path.join(project_root, tail) config_file = os.path.join(str(data_path), f"{env}-config.json") diff --git a/ansible/roles/acl/tasks/main.yml b/ansible/roles/acl/tasks/main.yml index d78ba2ed..632ff599 100644 --- a/ansible/roles/acl/tasks/main.yml +++ b/ansible/roles/acl/tasks/main.yml @@ -94,8 +94,23 @@ if ($for.StartsWith("NT AUTHORITY")) { $forSID = New-Object System.Security.Principal.NTAccount "$for" } else { - # Get only the objectSID property we need - $forObj = Get-ADObject -Filter "SamAccountName -eq '$for'" -Properties objectSID -ErrorAction Stop + $forObj = Get-ADObject -Filter "SamAccountName -eq '$for'" -Properties objectSID -ErrorAction SilentlyContinue + # Fallback for gMSA/computer accounts whose trailing $ may be + # stripped during parameter serialisation over SSM. Try the + # type-specific cmdlets which resolve by CN (no $ needed). + if (-not $forObj -and $for.EndsWith('$')) { + $baseName = $for.TrimEnd('$') + $forObj = Get-ADServiceAccount -Identity $baseName -Properties objectSID -ErrorAction SilentlyContinue + if (-not $forObj) { + $forObj = Get-ADComputer -Identity $baseName -Properties objectSID -ErrorAction SilentlyContinue + } + } + # If $ was stripped before reaching us, $for won't end with $ + # but the AD object's SamAccountName does — try appending it. + if (-not $forObj) { + $withDollar = $for + '$' + $forObj = Get-ADObject -Filter "SamAccountName -eq '$withDollar'" -Properties objectSID -ErrorAction SilentlyContinue + } if (-not $forObj) { throw "Cannot find object with SamAccountName: $for" } diff --git a/cli/cmd/lab.go b/cli/cmd/lab.go index f7b64a60..1384c92c 100644 --- a/cli/cmd/lab.go +++ b/cli/cmd/lab.go @@ -128,7 +128,12 @@ func runLabAction(action string) func(*cobra.Command, []string) error { return err } - instances, err := client.DiscoverInstances(ctx, cfg.Env) + var instances []daws.Instance + if action == "start" { + instances, err = client.DiscoverInstances(ctx, cfg.Env, "stopped") + } else { + instances, err = client.DiscoverInstances(ctx, cfg.Env) + } if err != nil { return err } @@ -176,7 +181,11 @@ func execVMAction(ctx context.Context, client *daws.Client, inst *daws.Instance, if err := client.StopInstances(ctx, ids); err != nil { return fmt.Errorf("stop VM: %w", err) } - fmt.Printf("Stop initiated for %s, waiting for stop...\n", inst.Name) + fmt.Printf("Stop initiated for %s, waiting for stopped state...\n", inst.Name) + if err := client.WaitForInstanceStopped(ctx, inst.InstanceID); err != nil { + return fmt.Errorf("wait for stop: %w", err) + } + fmt.Printf("%s is now stopped\n", inst.Name) } if err := client.StartInstances(ctx, ids); err != nil { return fmt.Errorf("start VM: %w", err) diff --git a/cli/cmd/ssm.go b/cli/cmd/ssm.go index 27b9044a..007c99ef 100644 --- a/cli/cmd/ssm.go +++ b/cli/cmd/ssm.go @@ -6,7 +6,9 @@ import ( "log/slog" "os" "os/exec" + "os/signal" "strings" + "syscall" "time" daws "github.com/dreadnode/dreadgoad/internal/aws" @@ -157,7 +159,13 @@ func runSSMConnect(cmd *cobra.Command, args []string) error { region := inv.Region() fmt.Printf("Starting SSM session to %s (%s) in %s...\n", host.Name, host.InstanceID, region) - // Exec into aws ssm start-session + // Ignore SIGINT in the parent so Ctrl+C is forwarded to the SSM + // session process (which handles it as a remote command interrupt) + // rather than killing dreadgoad and tearing down the session. + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT) + defer signal.Stop(sigCh) + ssmCmd := exec.Command("aws", "ssm", "start-session", "--target", host.InstanceID, "--region", region) diff --git a/cli/internal/aws/ec2.go b/cli/internal/aws/ec2.go index 1f71ac85..499a6a62 100644 --- a/cli/internal/aws/ec2.go +++ b/cli/internal/aws/ec2.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" @@ -17,13 +18,17 @@ type Instance struct { State string } -// DiscoverInstances finds running GOAD instances by tag pattern. -func (c *Client) DiscoverInstances(ctx context.Context, env string) ([]Instance, error) { +// DiscoverInstances finds GOAD instances by tag pattern. +// By default only running instances are returned. Pass additional states +// (e.g. "stopped") to include them. +func (c *Client) DiscoverInstances(ctx context.Context, env string, extraStates ...string) ([]Instance, error) { pattern := fmt.Sprintf("*%s*dreadgoad*", env) + states := []string{"running"} + states = append(states, extraStates...) out, err := c.EC2.DescribeInstances(ctx, &ec2.DescribeInstancesInput{ Filters: []types.Filter{ {Name: Ptr("tag:Name"), Values: []string{pattern}}, - {Name: Ptr("instance-state-name"), Values: []string{"running"}}, + {Name: Ptr("instance-state-name"), Values: states}, }, }) if err != nil { @@ -133,6 +138,14 @@ func (c *Client) FindInstanceByHostnameAll(ctx context.Context, env, hostname st return nil, fmt.Errorf("instance not found for hostname %s", hostname) } +// WaitForInstanceStopped polls until the given instance reaches the "stopped" state. +func (c *Client) WaitForInstanceStopped(ctx context.Context, instanceID string) error { + waiter := ec2.NewInstanceStoppedWaiter(c.EC2) + return waiter.Wait(ctx, &ec2.DescribeInstancesInput{ + InstanceIds: []string{instanceID}, + }, 5*time.Minute) +} + // TerminateInstances terminates the given EC2 instances. func (c *Client) TerminateInstances(ctx context.Context, instanceIDs []string) error { _, err := c.EC2.TerminateInstances(ctx, &ec2.TerminateInstancesInput{ diff --git a/cli/internal/validate/checks.go b/cli/internal/validate/checks.go index c6ba15d7..da1a6c72 100644 --- a/cli/internal/validate/checks.go +++ b/cli/internal/validate/checks.go @@ -309,10 +309,6 @@ func (v *Validator) checkACLPermissions(ctx context.Context) { } for _, af := range acls { - // Skip ACLs targeting full DN paths (complex to verify generically) - if strings.Contains(af.ACL.To, "CN=") && strings.Contains(af.ACL.To, "DC=") { - continue - } // Skip ACLs targeting computer accounts if strings.HasSuffix(af.ACL.To, "$") { continue @@ -327,9 +323,35 @@ func (v *Validator) checkACLPermissions(ctx context.Context) { target := v.lab.User(af.ACL.To) sourceFirst := strings.SplitN(source, ".", 2)[0] - output := v.runPS(ctx, dcRole, fmt.Sprintf( - `$user = Get-ADUser '%s' -Properties nTSecurityDescriptor -ErrorAction SilentlyContinue; if ($user) { $acl = $user.nTSecurityDescriptor.Access | Where-Object { $_.IdentityReference -like '*%s*' }; if ($acl) { Write-Output 'ACL_FOUND' } else { Write-Output 'ACL_NOT_FOUND' } } else { Write-Output 'USER_NOT_FOUND' }`, - target, sourceFirst)) + // Strip trailing $ for gMSA accounts to match the identity reference + sourceFirst = strings.TrimSuffix(sourceFirst, "$") + + // Build the PowerShell lookup for the target object. + // DN paths (containing = signs) are resolved directly via Get-Acl; + // SamAccountNames are looked up with Get-ADObject which finds + // users, groups, and service accounts alike. + script := fmt.Sprintf(` +$ErrorActionPreference = 'Stop' +Import-Module ActiveDirectory +Set-Location AD: +$target = '%s' +$sourceMatch = '*%s*' +try { + if ($target -match '=') { + $objDN = $target + $objAcl = Get-Acl -Path $objDN -ErrorAction Stop + } else { + $obj = Get-ADObject -Filter "SamAccountName -eq '$target'" -Properties nTSecurityDescriptor -ErrorAction Stop + if (-not $obj) { Write-Output 'TARGET_NOT_FOUND'; exit } + $objAcl = $obj.nTSecurityDescriptor + } + $ace = $objAcl.Access | Where-Object { $_.IdentityReference -like $sourceMatch } + if ($ace) { Write-Output 'ACL_FOUND' } else { Write-Output 'ACL_NOT_FOUND' } +} catch { + Write-Output "CHECK_ERROR: $_" +}`, target, sourceFirst) + + output := v.runPS(ctx, dcRole, script) switch { case strings.Contains(output, "ACL_FOUND"): From 34eb23a05d4a0a28b25a5f3dc6845e9d73259d03 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Tue, 7 Apr 2026 15:07:40 -0600 Subject: [PATCH 2/4] feat: enhance MSSQL, gMSA, LAPS, and validation coverage **Added:** - Extended lab map config to support MSSQL impersonation, linked servers, gMSA and LAPS readers - Added new facts and helper methods for gMSA, LAPS, and MSSQL in lab mapping - Implemented validation checks for scheduled tasks, LLMNR/NBT-NS, GPO abuse, gMSA, LAPS, SID filtering, SMB shares, firewall status, and password policy - Expanded MSSQL validator to check sysadmins, impersonation rights, linked servers, and xp_cmdshell status **Changed:** - Updated MSSQL installation to grant sysadmin to NETWORK SERVICE in both Ansible and PowerShell scripts for consistency - Enhanced gMSA role to update existing accounts with new configuration - Improved documentation and defaults for MSSQL connection types in role README and defaults - Improved error handling in AMI purge command to filter resources correctly - Added search for project root config in CLI config initialization - Clarified and expanded CLI help text and validation descriptions - Updated Terragrunt runner logic to use --no-auto-approve only when needed **Removed:** - Pruned verbose cleanup comments and Write-Host statements from MSSQL base Ansible playbook to streamline AMI creation steps --- ansible/playbooks/base/mssql_base.yml | 17 +- ansible/roles/gmsa/tasks/main.yml | 10 + ansible/roles/mssql/README.md | 4 +- ansible/roles/mssql/defaults/main.yml | 6 +- cli/cmd/ami.go | 30 +- cli/cmd/ssm.go | 1 + cli/cmd/validate.go | 4 +- cli/internal/config/config.go | 5 + cli/internal/labmap/labmap.go | 96 ++++- cli/internal/terragrunt/runner.go | 6 +- cli/internal/validate/checks.go | 370 +++++++++++++++++- cli/internal/validate/validator.go | 10 + .../scripts/04-install-mssql.ps1 | 2 +- 13 files changed, 520 insertions(+), 41 deletions(-) diff --git a/ansible/playbooks/base/mssql_base.yml b/ansible/playbooks/base/mssql_base.yml index d240f930..3828e4a4 100644 --- a/ansible/playbooks/base/mssql_base.yml +++ b/ansible/playbooks/base/mssql_base.yml @@ -1,13 +1,4 @@ --- -# Base AMI playbook for GOAD MSSQL servers -# This provisions a Windows Server base image with: -# - PowerShell DSC modules -# - IIS web server -# - SQL Server Express 2019 -# - RDP enabled -# - Windows Updates applied -# - Cleanup for AMI creation - - name: Provision GOAD MSSQL Server Base Image hosts: all gather_facts: true @@ -146,7 +137,7 @@ FEATURES=SQLENGINE INSTANCENAME="{{ sql_instance_name }}" SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" - SQLSYSADMINACCOUNTS="BUILTIN\Administrators" + SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" AGTSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" TCPENABLED="1" NPENABLED="1" @@ -256,14 +247,11 @@ ansible.windows.win_powershell: script: | $ProgressPreference = 'SilentlyContinue' - Write-Host "Cleaning up for AMI creation..." - # Clear Windows Update download cache Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue Start-Service -Name wuauserv - # Clear temp files Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue @@ -272,10 +260,7 @@ Remove-Item -Path "C:\setup\mssql\media\*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\setup\mssql\extraction\*" -Recurse -Force -ErrorAction SilentlyContinue - # Clear event logs wevtutil cl Application wevtutil cl Security wevtutil cl System - - Write-Host "Cleanup complete" register: cleanup_result diff --git a/ansible/roles/gmsa/tasks/main.yml b/ansible/roles/gmsa/tasks/main.yml index f6abd20f..bfce1221 100644 --- a/ansible/roles/gmsa/tasks/main.yml +++ b/ansible/roles/gmsa/tasks/main.yml @@ -25,6 +25,16 @@ Import-Module ActiveDirectory Set-Location AD: Add-KDSRootKey -EffectiveTime (Get-Date).AddHours(-10) -ErrorAction SilentlyContinue + + $existing = Get-ADServiceAccount -Identity $gMSA_Name -ErrorAction SilentlyContinue + if ($existing) { + $gMSA_HostsGroup = $gMSA_HostNames | ForEach-Object { Get-ADComputer -Identity $_ } + Set-ADServiceAccount -Identity $gMSA_Name -DNSHostName $gMSA_FQDN -ServicePrincipalNames @{Replace=$gMSA_SPNs} -PrincipalsAllowedToRetrieveManagedPassword $gMSA_HostsGroup + $Ansible.Changed = $false + Write-Output "GMSA account $gMSA_Name already exists, updated configuration" + return + } + $gMSA_HostsGroup = $gMSA_HostNames | ForEach-Object { Get-ADComputer -Identity $_ } New-ADServiceAccount -Name $gMSA_Name -DNSHostName $gMSA_FQDN -PrincipalsAllowedToRetrieveManagedPassword $gMSA_HostsGroup -ServicePrincipalNames $gMSA_SPNs $Ansible.Changed = $true diff --git a/ansible/roles/mssql/README.md b/ansible/roles/mssql/README.md index a1c1d869..e9b1cfbf 100644 --- a/ansible/roles/mssql/README.md +++ b/ansible/roles/mssql/README.md @@ -19,8 +19,8 @@ Install and configure Microsoft SQL Server Express | `sql_version` | str | `MSSQL_2019` | No description | | `download_url_2019` | str | `https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe` | No description | | `download_url_2022` | str | `https://download.microsoft.com/download/5/1/4/5145fe04-4d30-4b85-b0d1-39533663a2f1/SQL2022-SSEI-Expr.exe` | No description | -| `connection_type_2019` | str | `-E` | No description | -| `connection_type_2022` | str | `-S 127.0.0.1,1433` | No description | +| `connection_type_2019` | str | `-b -E -S localhost\SQLEXPRESS` | No description | +| `connection_type_2022` | str | `-b -S 127.0.0.1,1433` | No description | ## Tasks diff --git a/ansible/roles/mssql/defaults/main.yml b/ansible/roles/mssql/defaults/main.yml index 26fb3095..ae7eaacc 100644 --- a/ansible/roles/mssql/defaults/main.yml +++ b/ansible/roles/mssql/defaults/main.yml @@ -6,10 +6,8 @@ sql_version: MSSQL_2019 #sql_instance_name: MSSQLSERVER #sql_version: MSSQL_2022 -# SQL2019-SSEI-Expr.exe download_url_2019: https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe -# SQL2022-SSEI-Eval.exe download_url_2022: https://download.microsoft.com/download/5/1/4/5145fe04-4d30-4b85-b0d1-39533663a2f1/SQL2022-SSEI-Expr.exe -connection_type_2019: "-E" -connection_type_2022: "-S 127.0.0.1,1433" +connection_type_2019: "-b -E -S localhost\\SQLEXPRESS" +connection_type_2022: "-b -S 127.0.0.1,1433" diff --git a/cli/cmd/ami.go b/cli/cmd/ami.go index ef2ab7bf..40bb2656 100644 --- a/cli/cmd/ami.go +++ b/cli/cmd/ami.go @@ -364,11 +364,15 @@ func runAMIPurge(cmd *cobra.Command, args []string) error { var resources []ami.ResourceInfo if len(args) > 0 { resources, err = cleaner.ListResourcesForBuild(ctx, args[0]) + if err != nil { + return fmt.Errorf("list resources: %w", err) + } + resources = filterExactBuild(resources, args[0]) } else { resources, err = cleaner.ListWarpgateResources(ctx) - } - if err != nil { - return fmt.Errorf("list resources: %w", err) + if err != nil { + return fmt.Errorf("list resources: %w", err) + } } if len(resources) == 0 { @@ -486,6 +490,26 @@ func loadWarpgateTemplate(path, projectRoot string) (*builder.Config, error) { return &cfg, nil } +// filterExactBuild removes resources that don't belong to the specified build. +// This works around warpgate's HasPrefix matching in ListResourcesForBuild +// which incorrectly includes e.g. "goad-dc-base-2016" when filtering for "goad-dc-base". +func filterExactBuild(resources []ami.ResourceInfo, buildName string) []ami.ResourceInfo { + var exact []ami.ResourceInfo + for _, r := range resources { + if r.BuildName == buildName { + exact = append(exact, r) + continue + } + // Also match resources that have no BuildName tag but whose name + // matches exactly (e.g. infra configs named just "goad-dc-base"). + if r.BuildName == "" && r.Name == buildName { + exact = append(exact, r) + continue + } + } + return exact +} + func printBuildSummary(results []amiBuildResult) { for _, r := range results { name := filepath.Base(filepath.Dir(r.template)) diff --git a/cli/cmd/ssm.go b/cli/cmd/ssm.go index 007c99ef..a9ce7fad 100644 --- a/cli/cmd/ssm.go +++ b/cli/cmd/ssm.go @@ -37,6 +37,7 @@ var ssmCleanupCmd = &cobra.Command{ var ssmConnectCmd = &cobra.Command{ Use: "connect ", Short: "Start interactive SSM session to a host", + Long: `Opens an interactive SSM session. Type "exit" to disconnect. Ctrl+C interrupts the running remote command without closing the session.`, Args: cobra.ExactArgs(1), RunE: runSSMConnect, } diff --git a/cli/cmd/validate.go b/cli/cmd/validate.go index 0d9aa022..101076ce 100644 --- a/cli/cmd/validate.go +++ b/cli/cmd/validate.go @@ -17,7 +17,9 @@ var validateCmd = &cobra.Command{ Long: `Validates that all GOAD vulnerabilities are properly configured by running checks via SSM PowerShell commands against live instances. -Checks credentials, Kerberos, SMB, delegation, MSSQL, ADCS, ACLs, trusts, and services.`, +Checks credentials, Kerberos, SMB, delegation, MSSQL (linked servers, impersonation, +xp_cmdshell, sysadmins), ADCS (templates), ACLs, trusts, SID filtering, scheduled tasks, +LLMNR/NBT-NS, GPO abuse, gMSA, LAPS, and services.`, Example: ` dreadgoad validate dreadgoad validate --env staging --verbose dreadgoad validate --format json --output /tmp/results.json diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index a2963dc0..35b166b6 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -67,6 +67,11 @@ func Init() error { return fmt.Errorf("resolving home directory: %w", err) } viper.AddConfigPath(filepath.Join(home, ".config", "dreadgoad")) + // Search project root (walk up from cwd looking for ansible/ dir) + // so the config is found regardless of which subdirectory we run from. + if root, err := findProjectRoot(); err == nil { + viper.AddConfigPath(root) + } viper.AddConfigPath(".") viper.SetConfigName("dreadgoad") viper.SetConfigType("yaml") diff --git a/cli/internal/labmap/labmap.go b/cli/internal/labmap/labmap.go index 340106fa..735e6413 100644 --- a/cli/internal/labmap/labmap.go +++ b/cli/internal/labmap/labmap.go @@ -30,14 +30,20 @@ type HostConfig struct { Vulns []string `json:"vulns"` VulnsVars map[string]json.RawMessage `json:"vulns_vars"` Security []string `json:"security"` + UseLAPS bool `json:"use_laps"` MSSQL *MSSQLConfig `json:"mssql"` } -// MSSQLConfig holds MSSQL configuration for a host. +type MSSQLLinkedServer struct { + DataSrc string `json:"data_src"` +} + type MSSQLConfig struct { - SAPassword string `json:"sa_password"` - ServiceAccount string `json:"svcaccount"` - SysAdmins []string `json:"sysadmins"` + SAPassword string `json:"sa_password"` + ServiceAccount string `json:"svcaccount"` + SysAdmins []string `json:"sysadmins"` + ExecuteAsLogin map[string]string `json:"executeaslogin"` + LinkedServers map[string]MSSQLLinkedServer `json:"linked_servers"` } // UserConfig represents a user from config.json domains[*].users[*]. @@ -60,6 +66,13 @@ type ACLConfig struct { Inheritance string `json:"inheritance"` } +type GMSAConfig struct { + Name string `json:"gMSA_Name"` + FQDN string `json:"gMSA_FQDN"` + SPNs []string `json:"gMSA_SPNs"` + HostNames []string `json:"gMSA_HostNames"` +} + // DomainConfig represents a domain from config.json lab.domains. type DomainConfig struct { DC string `json:"dc"` // host role key @@ -67,6 +80,8 @@ type DomainConfig struct { Trust string `json:"trust"` CAServer string `json:"ca_server"` CAWebEnrollment *bool `json:"ca_web_enrollment"` + LAPSReaders []string `json:"laps_readers"` + GMSA map[string]GMSAConfig `json:"gmsa"` Users map[string]UserConfig `json:"users"` ACLs map[string]ACLConfig `json:"acls"` } @@ -381,6 +396,79 @@ func (m *LabMap) AllACLs() []ACLFact { return facts } +type GMSAFact struct { + Domain string + DCRole string + GMSA GMSAConfig +} + +type LAPSFact struct { + Domain string + DCRole string + Readers []string +} + +type MSSQLFact struct { + HostRole string + Hostname string + MSSQL *MSSQLConfig +} + +func (m *LabMap) DomainsWithGMSA() []GMSAFact { + var facts []GMSAFact + for domain, dc := range m.DomainConfigs { + for _, gmsa := range dc.GMSA { + if gmsa.Name != "" { + facts = append(facts, GMSAFact{ + Domain: domain, + DCRole: dc.DC, + GMSA: gmsa, + }) + } + } + } + return facts +} + +func (m *LabMap) DomainsWithLAPSReaders() []LAPSFact { + var facts []LAPSFact + for domain, dc := range m.DomainConfigs { + if len(dc.LAPSReaders) > 0 { + facts = append(facts, LAPSFact{ + Domain: domain, + DCRole: dc.DC, + Readers: dc.LAPSReaders, + }) + } + } + return facts +} + +func (m *LabMap) HostsWithMSSQLConfig() []MSSQLFact { + var facts []MSSQLFact + roles := m.HostsWithMSSQL() + for _, role := range roles { + hc := m.HostConfigs[role] + facts = append(facts, MSSQLFact{ + HostRole: role, + Hostname: hc.Hostname, + MSSQL: hc.MSSQL, + }) + } + return facts +} + +func (m *LabMap) HostsWithLAPS() []string { + var hosts []string + for role, hc := range m.HostConfigs { + if hc.UseLAPS { + hosts = append(hosts, role) + } + } + sort.Strings(hosts) + return hosts +} + // --- Config parsing --- // rawLabConfig mirrors the full config.json structure. diff --git a/cli/internal/terragrunt/runner.go b/cli/internal/terragrunt/runner.go index fb91bd8a..40f3ffd0 100644 --- a/cli/internal/terragrunt/runner.go +++ b/cli/internal/terragrunt/runner.go @@ -60,8 +60,10 @@ func Run(ctx context.Context, opts Options) error { func RunAll(ctx context.Context, opts Options) error { args := []string{"run", "--all", opts.Action} - if opts.AutoApprove && (opts.Action == "apply" || opts.Action == "destroy") { - args = append(args, "-auto-approve") + // terragrunt v0.97+ auto-appends -auto-approve for run --all. + // Only add --no-auto-approve when the caller explicitly wants a prompt. + if !opts.AutoApprove && (opts.Action == "apply" || opts.Action == "destroy") { + args = append(args, "--no-auto-approve") } if opts.NonInteractive { args = append(args, "--non-interactive") diff --git a/cli/internal/validate/checks.go b/cli/internal/validate/checks.go index da1a6c72..20358c7d 100644 --- a/cli/internal/validate/checks.go +++ b/cli/internal/validate/checks.go @@ -241,24 +241,72 @@ func (v *Validator) checkMachineAccountQuota(ctx context.Context) { func (v *Validator) checkMSSQL(ctx context.Context) { fmt.Println("\n== MSSQL Configurations ==") - mssqlHosts := v.lab.HostsWithMSSQL() - if len(mssqlHosts) == 0 { + mssqlFacts := v.lab.HostsWithMSSQLConfig() + if len(mssqlFacts) == 0 { v.addResult("SKIP", "MSSQL", "No MSSQL configured for this lab", "") return } - for _, role := range mssqlHosts { - host := strings.ToUpper(role) + for _, mf := range mssqlFacts { + host := strings.ToUpper(mf.HostRole) if !v.hasHost(host) { continue } - hostLabel := strings.ToUpper(v.lab.Hostname(role)) + hostLabel := strings.ToUpper(mf.Hostname) + output := v.runPS(ctx, host, `Get-Service 'MSSQL$SQLEXPRESS','MSSQLSERVER' -ErrorAction SilentlyContinue | Where-Object {$_.Status -eq 'Running'} | Select-Object -ExpandProperty Name`) - if strings.TrimSpace(output) != "" { - v.addResult("PASS", "MSSQL", fmt.Sprintf("MSSQL running on %s", hostLabel), "") - } else { + if strings.TrimSpace(output) == "" { v.addResult("FAIL", "MSSQL", fmt.Sprintf("MSSQL NOT running on %s", hostLabel), "") + continue + } + v.addResult("PASS", "MSSQL", fmt.Sprintf("MSSQL running on %s", hostLabel), "") + + sqlQuery := func(query string) string { + return v.runPS(ctx, host, fmt.Sprintf( + `$c = New-Object System.Data.SqlClient.SqlConnection 'Server=localhost;Integrated Security=True;TrustServerCertificate=True'; `+ + `$c.Open(); $cmd = $c.CreateCommand(); $cmd.CommandText = '%s'; `+ + `$r = $cmd.ExecuteReader(); while ($r.Read()) { Write-Output $r[0].ToString() }; $r.Close(); $c.Close()`, + query)) + } + + for _, admin := range mf.MSSQL.SysAdmins { + output = sqlQuery(fmt.Sprintf( + "SELECT m.name FROM sys.server_role_members srm JOIN sys.server_principals r ON srm.role_principal_id = r.principal_id JOIN sys.server_principals m ON srm.member_principal_id = m.principal_id WHERE r.name = ''sysadmin'' AND m.name = ''%s''", + admin)) + if strings.TrimSpace(output) != "" { + v.addResult("PASS", "MSSQL", fmt.Sprintf("%s is sysadmin on %s", admin, hostLabel), "") + } else { + v.addResult("FAIL", "MSSQL", fmt.Sprintf("%s is NOT sysadmin on %s", admin, hostLabel), "") + } + } + + for grantee, target := range mf.MSSQL.ExecuteAsLogin { + output = sqlQuery(fmt.Sprintf( + "SELECT pr.name FROM sys.server_permissions sp JOIN sys.server_principals pr ON sp.grantee_principal_id = pr.principal_id JOIN sys.server_principals pr2 ON sp.major_id = pr2.principal_id WHERE sp.permission_name = ''IMPERSONATE'' AND pr.name = ''%s'' AND pr2.name = ''%s''", + grantee, target)) + if strings.TrimSpace(output) != "" { + v.addResult("PASS", "MSSQL", fmt.Sprintf("%s can impersonate %s on %s", grantee, target, hostLabel), "") + } else { + v.addResult("FAIL", "MSSQL", fmt.Sprintf("%s CANNOT impersonate %s on %s", grantee, target, hostLabel), "") + } + } + + for name, ls := range mf.MSSQL.LinkedServers { + output = sqlQuery(fmt.Sprintf( + "SELECT name FROM sys.servers WHERE is_linked = 1 AND name = ''%s''", name)) + if strings.TrimSpace(output) != "" { + v.addResult("PASS", "MSSQL", fmt.Sprintf("Linked server %s -> %s on %s", name, ls.DataSrc, hostLabel), "") + } else { + v.addResult("FAIL", "MSSQL", fmt.Sprintf("Linked server %s NOT found on %s", name, hostLabel), "") + } + } + + output = sqlQuery("SELECT CONVERT(INT, ISNULL(value, value_in_use)) FROM sys.configurations WHERE name = ''xp_cmdshell''") + if strings.TrimSpace(output) == "1" { + v.addResult("PASS", "MSSQL", fmt.Sprintf("xp_cmdshell enabled on %s", hostLabel), "") + } else { + v.addResult("WARN", "MSSQL", fmt.Sprintf("xp_cmdshell NOT enabled on %s", hostLabel), "") } } } @@ -296,6 +344,28 @@ func (v *Validator) checkADCS(ctx context.Context) { v.addResult("WARN", "ADCS", "ADCS Web Enrollment not installed", "") } } + + output = v.runPS(ctx, host, + `Get-ADObject -Filter {objectClass -eq 'pKIEnrollmentService'} -SearchBase ("CN=Enrollment Services,CN=Public Key Services,CN=Services," + (Get-ADRootDSE).configurationNamingContext) -Properties certificateTemplates | Select-Object -ExpandProperty certificateTemplates`) + if strings.TrimSpace(output) == "" { + continue + } + publishedTemplates := parseOutputLines(output) + escTemplates := []string{"ESC1", "ESC2", "ESC3", "ESC3-CRA", "ESC4"} + for _, tmpl := range escTemplates { + found := false + for _, pub := range publishedTemplates { + if strings.EqualFold(strings.TrimSpace(pub), tmpl) { + found = true + break + } + } + if found { + v.addResult("PASS", "ADCS", fmt.Sprintf("Template %s published on %s CA", tmpl, hostLabel), "") + } else { + v.addResult("FAIL", "ADCS", fmt.Sprintf("Template %s NOT published on %s CA", tmpl, hostLabel), "") + } + } } } @@ -441,6 +511,290 @@ func (v *Validator) checkServices(ctx context.Context) { } } +func (v *Validator) checkScheduledTasks(ctx context.Context) { + fmt.Println("\n== Scheduled Tasks (Bots) ==") + + botScripts := map[string]string{ + "rdp_scheduler": "connect_bot", + "ntlm_relay": "ntlm_bot", + "responder": "responder_bot", + } + + found := false + for pattern, taskName := range botScripts { + hosts := v.lab.HostsWithScript(pattern) + for _, role := range hosts { + found = true + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + output := v.runPS(ctx, host, fmt.Sprintf( + `Get-ScheduledTask -TaskName '%s' -ErrorAction SilentlyContinue | Select-Object -ExpandProperty State`, taskName)) + state := strings.TrimSpace(output) + switch { + case strings.EqualFold(state, "Running") || strings.EqualFold(state, "Ready"): + v.addResult("PASS", "ScheduledTasks", fmt.Sprintf("%s is %s on %s", taskName, state, host), "") + case state != "": + v.addResult("WARN", "ScheduledTasks", fmt.Sprintf("%s state is %s on %s", taskName, state, host), "") + default: + v.addResult("FAIL", "ScheduledTasks", fmt.Sprintf("%s NOT found on %s", taskName, host), "") + } + } + } + if !found { + v.addResult("SKIP", "ScheduledTasks", "No bot scripts configured", "") + } +} + +func (v *Validator) checkLLMNR(ctx context.Context) { + fmt.Println("\n== LLMNR / NBT-NS ==") + + llmnrHosts := v.lab.HostsWithVuln("enable_llmnr") + for _, role := range llmnrHosts { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + hostLabel := strings.ToUpper(v.lab.Hostname(role)) + output := v.runPS(ctx, host, + `$v = Get-ItemProperty -Path 'HKLM:\Software\policies\Microsoft\Windows NT\DNSClient' -Name EnableMulticast -ErrorAction SilentlyContinue; if ($v) { $v.EnableMulticast } else { 'NOT_SET' }`) + val := strings.TrimSpace(output) + if val == "1" || val == "NOT_SET" { + v.addResult("PASS", "LLMNR", fmt.Sprintf("LLMNR enabled on %s", hostLabel), "") + } else { + v.addResult("FAIL", "LLMNR", fmt.Sprintf("LLMNR disabled on %s (value=%s)", hostLabel, val), "") + } + } + + nbtHosts := v.lab.HostsWithVuln("enable_nbt_ns") + for _, role := range nbtHosts { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + hostLabel := strings.ToUpper(v.lab.Hostname(role)) + output := v.runPS(ctx, host, + `Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\NetBT\Parameters\Interfaces\*' -Name NetbiosOptions -ErrorAction SilentlyContinue | Select-Object -ExpandProperty NetbiosOptions`) + lines := parseOutputLines(output) + allZero := len(lines) > 0 + for _, l := range lines { + if strings.TrimSpace(l) != "0" { + allZero = false + break + } + } + switch { + case allZero: + v.addResult("PASS", "LLMNR", fmt.Sprintf("NBT-NS enabled on %s", hostLabel), "") + case len(lines) == 0: + v.addResult("WARN", "LLMNR", fmt.Sprintf("NBT-NS status unknown on %s", hostLabel), "") + default: + v.addResult("FAIL", "LLMNR", fmt.Sprintf("NBT-NS disabled on %s", hostLabel), "") + } + } + + if len(llmnrHosts) == 0 && len(nbtHosts) == 0 { + v.addResult("SKIP", "LLMNR", "No LLMNR/NBT-NS vulns configured", "") + } +} + +func (v *Validator) checkGPOAbuse(ctx context.Context) { + fmt.Println("\n== GPO Abuse ==") + + hosts := v.lab.HostsWithScript("gpo_abuse") + if len(hosts) == 0 { + v.addResult("SKIP", "GPO", "No GPO abuse scripts configured", "") + return + } + + for _, role := range hosts { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + output := v.runPS(ctx, host, + `Get-GPO -All | Where-Object { $_.DisplayName -notmatch 'Default Domain' } | Select-Object -ExpandProperty DisplayName`) + gpos := parseOutputLines(output) + if len(gpos) > 0 { + v.addResult("PASS", "GPO", fmt.Sprintf("Custom GPOs on %s: %s", host, strings.Join(gpos, ", ")), "") + } else { + v.addResult("FAIL", "GPO", fmt.Sprintf("No custom GPOs found on %s", host), "") + } + } +} + +func (v *Validator) checkGMSA(ctx context.Context) { + fmt.Println("\n== gMSA Accounts ==") + + facts := v.lab.DomainsWithGMSA() + if len(facts) == 0 { + v.addResult("SKIP", "gMSA", "No gMSA configured for this lab", "") + return + } + + for _, gf := range facts { + host := strings.ToUpper(gf.DCRole) + if !v.hasHost(host) { + continue + } + output := v.runPS(ctx, host, fmt.Sprintf( + `Get-ADServiceAccount -Identity '%s' -Properties Enabled | Select-Object -ExpandProperty Enabled`, gf.GMSA.Name)) + if strings.Contains(strings.ToLower(output), "true") { + v.addResult("PASS", "gMSA", fmt.Sprintf("gMSA %s exists and enabled in %s", gf.GMSA.Name, gf.Domain), "") + } else { + v.addResult("FAIL", "gMSA", fmt.Sprintf("gMSA %s NOT found or disabled in %s", gf.GMSA.Name, gf.Domain), "") + } + } +} + +func (v *Validator) checkLAPS(ctx context.Context) { + fmt.Println("\n== LAPS ==") + + lapsHosts := v.lab.HostsWithLAPS() + if len(lapsHosts) == 0 { + v.addResult("SKIP", "LAPS", "No LAPS hosts configured", "") + return + } + + for _, role := range lapsHosts { + hc := v.lab.HostConfigs[role] + hostname := strings.ToUpper(hc.Hostname) + dcRole := v.lab.DCForDomain(hc.Domain) + if dcRole == "" { + continue + } + dc := strings.ToUpper(dcRole) + if !v.hasHost(dc) { + continue + } + output := v.runPS(ctx, dc, fmt.Sprintf( + `Get-ADComputer -Identity '%s' -Properties ms-Mcs-AdmPwd | Select-Object -ExpandProperty ms-Mcs-AdmPwd`, hostname)) + if strings.TrimSpace(output) != "" { + v.addResult("PASS", "LAPS", fmt.Sprintf("LAPS password set for %s", hostname), "") + } else { + v.addResult("FAIL", "LAPS", fmt.Sprintf("LAPS password NOT set for %s", hostname), "") + } + } +} + +func (v *Validator) checkSIDFiltering(ctx context.Context) { + fmt.Println("\n== SID Filtering ==") + + trusts := v.lab.DomainTrusts() + if len(trusts) == 0 { + v.addResult("SKIP", "SIDFiltering", "No domain trusts configured", "") + return + } + + for _, tf := range trusts { + if tf.SourceDCRole == "" { + continue + } + host := strings.ToUpper(tf.SourceDCRole) + if !v.hasHost(host) { + continue + } + output := v.runPS(ctx, host, fmt.Sprintf( + `netdom trust %s /d:%s /quarantine 2>&1`, tf.SourceDomain, tf.TargetDomain)) + lower := strings.ToLower(output) + switch { + case strings.Contains(lower, "not enabled"): + v.addResult("PASS", "SIDFiltering", fmt.Sprintf("SID filtering disabled on %s -> %s (exploitation possible)", tf.SourceDomain, tf.TargetDomain), "") + case strings.Contains(lower, "enabled"): + v.addResult("WARN", "SIDFiltering", fmt.Sprintf("SID filtering enabled on %s -> %s", tf.SourceDomain, tf.TargetDomain), "") + default: + v.addResult("INFO", "SIDFiltering", fmt.Sprintf("Could not determine SID filtering: %s -> %s", tf.SourceDomain, tf.TargetDomain), "") + } + } +} + +func (v *Validator) checkSMBShares(ctx context.Context) { + fmt.Println("\n== SMB Shares ==") + + shareHosts := v.lab.HostsWithVuln("openshares") + if len(shareHosts) == 0 { + v.addResult("SKIP", "Shares", "No openshares vulns configured", "") + return + } + + for _, role := range shareHosts { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + hostLabel := strings.ToUpper(v.lab.Hostname(role)) + output := v.runPS(ctx, host, + `Get-SmbShare | Where-Object { $_.Name -notmatch 'ADMIN\$|C\$|IPC\$' } | Select-Object -ExpandProperty Name`) + shares := parseOutputLines(output) + if len(shares) > 0 { + v.addResult("PASS", "Shares", fmt.Sprintf("Custom shares on %s: %s", hostLabel, strings.Join(shares, ", ")), "") + } else { + v.addResult("FAIL", "Shares", fmt.Sprintf("No custom shares found on %s", hostLabel), "") + } + } +} + +func (v *Validator) checkFirewallDisabled(ctx context.Context) { + fmt.Println("\n== Firewall ==") + + fwHosts := v.lab.HostsWithVuln("disable_firewall") + if len(fwHosts) == 0 { + v.addResult("SKIP", "Firewall", "No disable_firewall vulns configured", "") + return + } + + for _, role := range fwHosts { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + hostLabel := strings.ToUpper(v.lab.Hostname(role)) + output := v.runPS(ctx, host, + `Get-NetFirewallProfile | Where-Object { $_.Enabled -eq $true } | Select-Object -ExpandProperty Name`) + enabledProfiles := parseOutputLines(output) + if len(enabledProfiles) == 0 { + v.addResult("PASS", "Firewall", fmt.Sprintf("Firewall disabled on %s", hostLabel), "") + } else { + v.addResult("FAIL", "Firewall", fmt.Sprintf("Firewall still enabled on %s (profiles: %s)", hostLabel, strings.Join(enabledProfiles, ", ")), "") + } + } +} + +func (v *Validator) checkPasswordPolicy(ctx context.Context) { + fmt.Println("\n== Password Policy ==") + + for _, role := range v.lab.DCs() { + host := strings.ToUpper(role) + if !v.hasHost(host) { + continue + } + output := v.runPS(ctx, host, + `$p = Get-ADDefaultDomainPasswordPolicy; Write-Output "$($p.ComplexityEnabled)|$($p.MinPasswordLength)|$($p.LockoutThreshold)"`) + parts := strings.Split(strings.TrimSpace(output), "|") + if len(parts) < 3 { + v.addResult("WARN", "PasswordPolicy", fmt.Sprintf("Could not read password policy on %s", host), "") + continue + } + domain := v.lab.DomainForHost(strings.ToLower(host)) + if domain == "" { + domain = host + } + complexity := parts[0] + minLen := parts[1] + if strings.EqualFold(complexity, "false") { + v.addResult("PASS", "PasswordPolicy", fmt.Sprintf("Password complexity disabled in %s (weak policy)", domain), "") + } else { + v.addResult("INFO", "PasswordPolicy", fmt.Sprintf("Password complexity enabled in %s", domain), "") + } + if minLen == "0" || minLen == "1" || minLen == "2" || minLen == "3" { + v.addResult("PASS", "PasswordPolicy", fmt.Sprintf("Min password length is %s in %s (weak)", minLen, domain), "") + } else { + v.addResult("INFO", "PasswordPolicy", fmt.Sprintf("Min password length is %s in %s", minLen, domain), "") + } + } +} + // parseOutputLines splits PowerShell output into non-empty trimmed lines. func parseOutputLines(output string) []string { var lines []string diff --git a/cli/internal/validate/validator.go b/cli/internal/validate/validator.go index fc931e06..fb296d1d 100644 --- a/cli/internal/validate/validator.go +++ b/cli/internal/validate/validator.go @@ -102,6 +102,7 @@ func (v *Validator) RunQuickChecks(ctx context.Context) { v.checkADCS(ctx) v.checkDomainTrusts(ctx) v.checkServices(ctx) + v.checkScheduledTasks(ctx) } // RunAllChecks executes all vulnerability validation checks. @@ -116,7 +117,16 @@ func (v *Validator) RunAllChecks(ctx context.Context) { v.checkADCS(ctx) v.checkACLPermissions(ctx) v.checkDomainTrusts(ctx) + v.checkSIDFiltering(ctx) v.checkServices(ctx) + v.checkScheduledTasks(ctx) + v.checkLLMNR(ctx) + v.checkGPOAbuse(ctx) + v.checkGMSA(ctx) + v.checkLAPS(ctx) + v.checkSMBShares(ctx) + v.checkFirewallDisabled(ctx) + v.checkPasswordPolicy(ctx) } // GetReport returns the current report. diff --git a/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 b/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 index 4fb4c25a..8fd8a4e2 100644 --- a/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 +++ b/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 @@ -11,7 +11,7 @@ FEATURES=SQLENGINE INSTANCENAME="$sqlInstanceName" INSTANCEID="$sqlInstanceName" SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" -SQLSYSADMINACCOUNTS="BUILTIN\Administrators" +SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" AGTSVCSTARTUPTYPE="Automatic" SQLSVCSTARTUPTYPE="Automatic" BROWSERSVCSTARTUPTYPE="Automatic" From 89f2a14f7884d1e8e06816df546709a051741703 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Tue, 7 Apr 2026 15:48:32 -0600 Subject: [PATCH 3/4] feat: add Windows Server 2025 MSSQL/DC templates and refactor 2016 member to mssql **Added:** - Introduced `goad-dc-base-2025` template for Windows Server 2025 domain controllers, including documentation and provisioning for the DRACARYS lab - Added `goad-mssql-base-2025` template for Windows Server 2025 with MSSQL Express 2022 for member servers, including all setup scripts and documentation - Created `goad-mssql-base-2016` template by refactoring the old member server base, adding MSSQL Express 2019 installation and configuration scripts - Updated SQL Server detection and configuration in `mssql_base.yml` playbook to support dynamic selection of SQL version/installer based on Windows version **Changed:** - Refactored all references from `goad-member-base-2016` to `goad-mssql-base-2016` across documentation, Terragrunt configs, and warpgate templates to clarify purpose and ensure MSSQL is always pre-installed - Updated `host-registry.yaml` to include a "test" environment for deployments - Modified `terragrunt.hcl` for both staging and test to use `goad-mssql-base-2016` instead of the deprecated member-only base - Improved documentation in `warpgate-templates/README.md` and related files to cover new 2025 templates and clarify template usage for both GOAD and DRACARYS labs - Enhanced Ansible MSSQL configuration to use `SYSTEM` for privilege escalation, ensuring reliable sysadmin configuration regardless of SSM session user - Updated registry and SQL Server configuration paths in `mssql_base.yml` to support dynamic SQL versioning - Corrected example build commands and documentation to match new template names and structure **Removed:** - Deleted the `goad-member-base-2016` template and all related documentation and scripts, as all member servers are now built from MSSQL-enabled bases --- ansible/playbooks/base/mssql_base.yml | 32 ++++++- ansible/roles/mssql/tasks/config.yml | 23 ++--- infra/goad-deployment/host-registry.yaml | 2 +- .../us-west-1/goad/srv03/terragrunt.hcl | 4 +- .../test/us-east-2/goad/srv03/terragrunt.hcl | 4 +- warpgate-templates/README.md | 26 +++-- .../goad-dc-base-2025/README.md | 55 +++++++++++ .../goad-dc-base-2025/warpgate.yaml | 51 ++++++++++ warpgate-templates/goad-dc-base/README.md | 4 +- .../goad-member-base-2016/README.md | 94 ------------------- .../goad-mssql-base-2016/README.md | 54 +++++++++++ .../scripts/01-install-modules.ps1 | 0 .../scripts/02-install-iis.ps1 | 0 .../scripts/03-download-mssql.ps1 | 19 ++++ .../scripts/04-install-mssql.ps1 | 49 ++++++++++ .../scripts/05-configure-mssql.ps1 | 48 ++++++++++ .../scripts/06-enable-rdp.ps1} | 0 .../scripts/07-windows-updates.ps1} | 0 .../scripts/08-cleanup.ps1} | 3 + .../warpgate.yaml | 17 ++-- .../goad-mssql-base-2025/README.md | 56 +++++++++++ .../goad-mssql-base-2025/warpgate.yaml | 52 ++++++++++ warpgate-templates/goad-mssql-base/README.md | 4 +- 23 files changed, 460 insertions(+), 137 deletions(-) create mode 100644 warpgate-templates/goad-dc-base-2025/README.md create mode 100644 warpgate-templates/goad-dc-base-2025/warpgate.yaml delete mode 100644 warpgate-templates/goad-member-base-2016/README.md create mode 100644 warpgate-templates/goad-mssql-base-2016/README.md rename warpgate-templates/{goad-member-base-2016 => goad-mssql-base-2016}/scripts/01-install-modules.ps1 (100%) rename warpgate-templates/{goad-member-base-2016 => goad-mssql-base-2016}/scripts/02-install-iis.ps1 (100%) create mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 create mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 create mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 rename warpgate-templates/{goad-member-base-2016/scripts/03-enable-rdp.ps1 => goad-mssql-base-2016/scripts/06-enable-rdp.ps1} (100%) rename warpgate-templates/{goad-member-base-2016/scripts/04-windows-updates.ps1 => goad-mssql-base-2016/scripts/07-windows-updates.ps1} (100%) rename warpgate-templates/{goad-member-base-2016/scripts/05-cleanup.ps1 => goad-mssql-base-2016/scripts/08-cleanup.ps1} (81%) rename warpgate-templates/{goad-member-base-2016 => goad-mssql-base-2016}/warpgate.yaml (78%) create mode 100644 warpgate-templates/goad-mssql-base-2025/README.md create mode 100644 warpgate-templates/goad-mssql-base-2025/warpgate.yaml diff --git a/ansible/playbooks/base/mssql_base.yml b/ansible/playbooks/base/mssql_base.yml index 3828e4a4..71c7c297 100644 --- a/ansible/playbooks/base/mssql_base.yml +++ b/ansible/playbooks/base/mssql_base.yml @@ -4,10 +4,34 @@ gather_facts: true vars: - sql_download_url: "https://go.microsoft.com/fwlink/p/?linkid=866658" sql_instance_name: "SQLEXPRESS" tasks: + - name: Detect Windows Server version for SQL Server compatibility + ansible.windows.win_powershell: + script: | + $build = [System.Environment]::OSVersion.Version.Build + # Windows Server 2025 build >= 26100 + if ($build -ge 26100) { + $Ansible.Result = "2025" + } else { + $Ansible.Result = "pre2025" + } + register: os_detect + changed_when: false + + - name: Set SQL Server variables for Windows Server 2025+ + ansible.builtin.set_fact: + sql_download_url: "https://download.microsoft.com/download/5/1/4/5145fe04-4d30-4b85-b0d1-39c332a9d2be/SQL2022-SSEI-Expr.exe" + sql_version_id: "MSSQL16" + when: os_detect.result == "2025" + + - name: Set SQL Server variables for Windows Server 2016/2019 + ansible.builtin.set_fact: + sql_download_url: "https://go.microsoft.com/fwlink/p/?linkid=866658" + sql_version_id: "MSSQL15" + when: os_detect.result != "2025" + - name: Install PowerShellGet and NuGet provider ansible.windows.win_shell: | $ProgressPreference = 'SilentlyContinue' @@ -130,7 +154,7 @@ ansible.windows.win_copy: dest: C:\setup\mssql\sql_conf.ini content: | - ;SQL Server 2019 Express Configuration File + ;SQL Server Express Configuration File [OPTIONS] ACTION="Install" QUIET="True" @@ -151,7 +175,7 @@ - name: Check if SQL Server is already installed ansible.windows.win_reg_stat: - path: "HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\MSSQL15.{{ sql_instance_name }}" + path: "HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\{{ sql_version_id }}.{{ sql_instance_name }}" register: mssql_installed failed_when: false @@ -167,7 +191,7 @@ - name: Configure SQL Server TCP port ansible.windows.win_regedit: - path: 'HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL15.{{ sql_instance_name }}\MSSQLServer\SuperSocketNetLib\Tcp\IPAll' + path: 'HKLM:\Software\Microsoft\Microsoft SQL Server\{{ sql_version_id }}.{{ sql_instance_name }}\MSSQLServer\SuperSocketNetLib\Tcp\IPAll' name: TcpPort data: "1433" type: string diff --git a/ansible/roles/mssql/tasks/config.yml b/ansible/roles/mssql/tasks/config.yml index a7bc616c..0cb245fb 100644 --- a/ansible/roles/mssql/tasks/config.yml +++ b/ansible/roles/mssql/tasks/config.yml @@ -2,6 +2,9 @@ # MSSQL Configuration Tasks # These tasks configure SQL Server after installation # They should ALWAYS run to ensure proper configuration (idempotent) +# +# Note: SSM sessions run as ssm-user (not SYSTEM), so we must use +# become to escalate to SYSTEM which has SQL sysadmin privileges. - name: Add MSSQL admin ansible.windows.win_shell: | @@ -23,9 +26,7 @@ Write-Output "Successfully configured sysadmin: {{ item }}" become: true become_method: ansible.builtin.runas - become_user: "{{ SQLSVCACCOUNT }}" - vars: - ansible_become_pass: "{{ SQLSVCPASSWORD }}" + become_user: SYSTEM loop: "{{ sql_sysadmins }}" register: admin_result @@ -57,9 +58,7 @@ Write-Output "Successfully granted IMPERSONATE ON LOGIN::[{{ item.value }}] TO [{{ item.key }}]" become: true become_method: ansible.builtin.runas - become_user: "{{ SQLSVCACCOUNT }}" - vars: - ansible_become_pass: "{{ SQLSVCPASSWORD }}" + become_user: SYSTEM with_dict: "{{ executeaslogin }}" register: impersonate_login_result @@ -95,9 +94,7 @@ Write-Output "Successfully granted IMPERSONATE ON USER::[{{ item.value.impersonate }}] TO [{{ item.value.user }}] in {{ item.value.db }}" become: true become_method: ansible.builtin.runas - become_user: "{{ SQLSVCACCOUNT }}" - vars: - ansible_become_pass: "{{ SQLSVCPASSWORD }}" + become_user: SYSTEM with_dict: "{{ executeasuser }}" register: impersonate_user_result @@ -129,9 +126,7 @@ Write-Output "Successfully enabled sa account" become: true become_method: ansible.builtin.runas - become_user: "{{ SQLSVCACCOUNT }}" - vars: - ansible_become_pass: "{{ SQLSVCPASSWORD }}" + become_user: SYSTEM register: sa_result - name: Log sa account errors @@ -149,9 +144,7 @@ Write-Output "Successfully enabled mixed mode authentication (LoginMode=2)" become: true become_method: ansible.builtin.runas - become_user: "{{ SQLSVCACCOUNT }}" - vars: - ansible_become_pass: "{{ SQLSVCPASSWORD }}" + become_user: SYSTEM register: auth_mode_result - name: Restart service MSSQL diff --git a/infra/goad-deployment/host-registry.yaml b/infra/goad-deployment/host-registry.yaml index 240dd663..099f9041 100644 --- a/infra/goad-deployment/host-registry.yaml +++ b/infra/goad-deployment/host-registry.yaml @@ -7,7 +7,7 @@ version: "1.0" deployment: "goad" -environments: ["dev", "staging"] +environments: ["dev", "staging", "test"] default_region: "us-west-1" hosts: diff --git a/infra/goad-deployment/staging/us-west-1/goad/srv03/terragrunt.hcl b/infra/goad-deployment/staging/us-west-1/goad/srv03/terragrunt.hcl index a29025e1..fbb4ca15 100644 --- a/infra/goad-deployment/staging/us-west-1/goad/srv03/terragrunt.hcl +++ b/infra/goad-deployment/staging/us-west-1/goad/srv03/terragrunt.hcl @@ -1,6 +1,6 @@ # ============================================================================= # SRV03 - Member Server in Essos Domain -# AMI: Built from warpgate-templates/goad-member-base-2016 (Windows Server 2016) +# AMI: Built from warpgate-templates/goad-mssql-base-2016 (Windows Server 2016) # ============================================================================= include "host" { @@ -70,7 +70,7 @@ inputs = { additional_windows_ami_filters = [ { name = "tag:Name" - values = ["goad-member-base-2016"] + values = ["goad-mssql-base-2016"] } ] diff --git a/infra/goad-deployment/test/us-east-2/goad/srv03/terragrunt.hcl b/infra/goad-deployment/test/us-east-2/goad/srv03/terragrunt.hcl index a29025e1..fbb4ca15 100644 --- a/infra/goad-deployment/test/us-east-2/goad/srv03/terragrunt.hcl +++ b/infra/goad-deployment/test/us-east-2/goad/srv03/terragrunt.hcl @@ -1,6 +1,6 @@ # ============================================================================= # SRV03 - Member Server in Essos Domain -# AMI: Built from warpgate-templates/goad-member-base-2016 (Windows Server 2016) +# AMI: Built from warpgate-templates/goad-mssql-base-2016 (Windows Server 2016) # ============================================================================= include "host" { @@ -70,7 +70,7 @@ inputs = { additional_windows_ami_filters = [ { name = "tag:Name" - values = ["goad-member-base-2016"] + values = ["goad-mssql-base-2016"] } ] diff --git a/warpgate-templates/README.md b/warpgate-templates/README.md index 2aefca30..6f0bf286 100644 --- a/warpgate-templates/README.md +++ b/warpgate-templates/README.md @@ -4,14 +4,23 @@ Pre-baked AMI templates for DreadGOAD, built with [warpgate](https://github.com/ ## Templates +### GOAD Lab + | Template | OS | Pre-installed Software | Target Hosts | Time Saved | |----------|-----|----------------------|--------------|------------| | [goad-dc-base](goad-dc-base/) | Windows Server 2019 | AD DS, DNS, RSAT, DSC modules, SSM | DC01, DC02 | ~25 min/host | | [goad-dc-base-2016](goad-dc-base-2016/) | Windows Server 2016 | AD DS, DNS, RSAT, DSC modules, SSM | DC03 | ~25 min/host | | [goad-mssql-base](goad-mssql-base/) | Windows Server 2019 | MSSQL Express 2019, IIS/WebDAV, DSC modules | SRV02 | ~48 min/host | -| [goad-member-base-2016](goad-member-base-2016/) | Windows Server 2016 | IIS/WebDAV, DSC modules | SRV03 (optional) | ~20 min/host | +| [goad-mssql-base-2016](goad-mssql-base-2016/) | Windows Server 2016 | MSSQL Express 2019, IIS/WebDAV, DSC modules | SRV03 | ~48 min/host | + +### DRACARYS Lab + +| Template | OS | Pre-installed Software | Target Hosts | Time Saved | +|----------|-----|----------------------|--------------|------------| +| [goad-dc-base-2025](goad-dc-base-2025/) | Windows Server 2025 | AD DS, DNS, RSAT, DSC modules, SSM | DC01 | ~25 min/host | +| [goad-mssql-base-2025](goad-mssql-base-2025/) | Windows Server 2025 | MSSQL Express 2022, IIS/WebDAV, DSC modules | SRV01 | ~48 min/host | -Total time savings: **~170 minutes** per full GOAD deployment. +Total time savings: **~171 minutes** per full GOAD deployment, **~73 minutes** per DRACARYS deployment. ## Prerequisites @@ -25,11 +34,15 @@ Total time savings: **~170 minutes** per full GOAD deployment. # Set the repo path for Ansible playbook references export PROVISION_REPO_PATH=/path/to/DreadGOAD -# Build all templates +# Build GOAD templates warpgate build goad-dc-base --target ami warpgate build goad-dc-base-2016 --target ami warpgate build goad-mssql-base --target ami -warpgate build goad-member-base-2016 --target ami +warpgate build goad-mssql-base-2016 --target ami + +# Build DRACARYS templates +warpgate build goad-dc-base-2025 --target ami +warpgate build goad-mssql-base-2025 --target ami # Build for a specific region warpgate build goad-dc-base --target ami --region us-east-1 @@ -70,9 +83,8 @@ goad-{template-name}/ The `warpgate.yaml` files reference Ansible playbooks under `ansible/playbooks/base/`: -- `dc_base.yml` -- used by `goad-dc-base` and `goad-dc-base-2016` -- `mssql_base.yml` -- used by `goad-mssql-base` -- `member_base.yml` -- used by `goad-member-base-2016` +- `dc_base.yml` -- used by `goad-dc-base`, `goad-dc-base-2016`, and `goad-dc-base-2025` +- `mssql_base.yml` -- used by `goad-mssql-base`, `goad-mssql-base-2016`, and `goad-mssql-base-2025` ## Using Built AMIs diff --git a/warpgate-templates/goad-dc-base-2025/README.md b/warpgate-templates/goad-dc-base-2025/README.md new file mode 100644 index 00000000..db4d9720 --- /dev/null +++ b/warpgate-templates/goad-dc-base-2025/README.md @@ -0,0 +1,55 @@ +# goad-dc-base-2025 + +Pre-baked Windows Server 2025 AMI with AD DS role and Windows Updates pre-installed for GOAD domain controllers (DRACARYS lab). + +## Purpose + +This template creates a "golden" AMI that significantly reduces GOAD deployment time by pre-installing: + +- Windows Updates (saves ~15 minutes per instance) +- AD-Domain-Services role (NOT promoted - promotion happens at runtime) +- DNS Server role +- RSAT tools (RSAT-AD-Tools, RSAT-DNS-Server, RSAT-ADDS) +- Group Policy Management Console (GPMC) +- Required PowerShell DSC modules +- SSM agent configuration for post-DC-promotion survival + +**Note**: The AD DS role is installed but NOT promoted to a domain controller. Domain promotion with domain-specific settings happens at runtime via Ansible. + +## Usage + +### Build the AMI + +```bash +warpgate build goad-dc-base-2025 --target ami +``` + +### Update Terragrunt + +For DRACARYS DC01: + +```hcl +inputs = { + windows_os = "Windows_Server" + windows_os_version = "2025-English-Full-Base" + + additional_windows_ami_filters = [ + { + name = "tag:Name" + values = ["goad-dc-base-2025"] + } + ] + + windows_ami_owners = ["self"] +} +``` + +## Tags + +The AMI is tagged with: + +- `Name`: goad-dc-base-2025 +- `Lab`: GOAD +- `Role`: DomainController +- `ManagedBy`: warpgate +- `BaseOS`: WindowsServer2025 diff --git a/warpgate-templates/goad-dc-base-2025/warpgate.yaml b/warpgate-templates/goad-dc-base-2025/warpgate.yaml new file mode 100644 index 00000000..fdc179da --- /dev/null +++ b/warpgate-templates/goad-dc-base-2025/warpgate.yaml @@ -0,0 +1,51 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/cowdogmoo/warpgate/main/schema/warpgate-template.json +metadata: + name: goad-dc-base-2025 + version: 1.0.0 + description: Windows Server 2025 with updates and AD DS role pre-installed for GOAD domain controllers + author: Dreadnode + license: MIT + tags: + - goad + - windows + - windows-2025 + - domain-controller + - active-directory + - dracarys + requires: + warpgate: ">=1.0.0" + +name: goad-dc-base-2025 +version: latest + +base: + image: "arn:aws:ssm:us-west-1::parameter/aws/service/ami-windows-latest/Windows_Server-2025-English-Full-Base" + +variables: + aws_region: us-west-1 + instance_type: t3.medium + ami_owner: "801119661308" + ami_name_filter: "Windows_Server-2025-English-Full-Base-*" + +provisioners: + # Provision with Ansible via AWS SSM + - type: ansible + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/dc_base.yml + galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml + extra_vars: + ansible_connection: aws_ssm + ansible_shell_type: powershell + ansible_aws_ssm_bucket_name: "" + ansible_aws_ssm_region: "${aws_region}" +targets: + - type: ami + region: "${aws_region}" + instance_type: "${instance_type}" + ami_name: "goad-dc-base-2025-{{timestamp}}" + volume_size: 100 + ami_tags: + Name: goad-dc-base-2025 + Lab: GOAD + Role: DomainController + ManagedBy: warpgate + BaseOS: WindowsServer2025 diff --git a/warpgate-templates/goad-dc-base/README.md b/warpgate-templates/goad-dc-base/README.md index b789f0ec..cb7c32ed 100644 --- a/warpgate-templates/goad-dc-base/README.md +++ b/warpgate-templates/goad-dc-base/README.md @@ -37,12 +37,12 @@ With 3 domain controllers in GOAD, this saves approximately **75 minutes** per d warpgate build goad-dc-base --target ami # Or with custom region -warpgate build goad-dc-base --target ami --region us-east-1 +warpgate build goad-dc-base --target ami --vars aws_region=us-east-1 ``` ### Use in Terragrunt -Update your GOAD terragrunt.hcl for DC01/DC02/DC03: +Update your GOAD terragrunt.hcl for DC01/DC02: ```hcl inputs = { diff --git a/warpgate-templates/goad-member-base-2016/README.md b/warpgate-templates/goad-member-base-2016/README.md deleted file mode 100644 index b9f803fb..00000000 --- a/warpgate-templates/goad-member-base-2016/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# goad-member-base-2016 - -Pre-baked Windows Server 2016 AMI with IIS/WebDAV and Windows Updates pre-installed for GOAD member servers. - -## Purpose - -This template creates a "golden" AMI for member servers (non-domain-controllers) running Windows Server 2016. It pre-installs: - -- Windows Updates (saves ~15 minutes per instance) -- IIS web server with WebDAV -- Required PowerShell DSC modules -- RDP enabled - -## Time Savings - -| Component | Vanilla AMI | Pre-baked AMI | -| --------- | ----------- | ------------- | -| Windows Updates | ~15 min | 0 min | -| IIS/WebDAV Install | ~3 min | 0 min | -| DSC Modules | ~2 min | 0 min | -| **Total per host** | **~20 min** | **0 min** | - -## Usage - -### Build the AMI - -```bash -# Build using warpgate CLI -warpgate build goad-member-base-2016 --target ami - -# Or with custom region -warpgate build goad-member-base-2016 --target ami --region us-east-1 -``` - -### Use in Terragrunt - -Update your GOAD terragrunt.hcl for SRV03: - -```hcl -inputs = { - # Windows AMI configuration - using pre-baked goad-member-base-2016 AMI - windows_os = "Windows_Server" - windows_os_version = "2016-English-Full-Base" - - additional_windows_ami_filters = [ - { - name = "image-id" - values = ["ami-xxxxxxxxxxxx"] # Your goad-member-base-2016 AMI ID - } - ] - - windows_ami_owners = ["self"] -} -``` - -## What's Pre-installed - -### Windows Features - -- IIS web server -- WebDAV - -### PowerShell Modules - -- ComputerManagementDsc -- ActiveDirectoryDsc -- xNetworking -- NetworkingDsc -- PSWindowsUpdate - -### Configuration - -- RDP enabled -- Windows Updates applied - -## What Still Needs to Run at Deployment - -The following must still be configured at deployment time (domain-specific): - -1. Domain join (`ad-members.yml`) -2. IIS application configuration (`servers.yml`) -3. GPO configuration -4. ADCS roles (if applicable) -5. Vulnerability injection - -## Tags - -The AMI is tagged with: - -- `Name`: goad-member-base-2016 -- `Lab`: GOAD -- `Role`: MemberServer -- `ManagedBy`: warpgate -- `BaseOS`: WindowsServer2016 diff --git a/warpgate-templates/goad-mssql-base-2016/README.md b/warpgate-templates/goad-mssql-base-2016/README.md new file mode 100644 index 00000000..4e6b4f6d --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2016/README.md @@ -0,0 +1,54 @@ +# goad-mssql-base-2016 + +Pre-baked Windows Server 2016 AMI with MSSQL Express 2019, IIS, and Windows Updates pre-installed for GOAD member servers. + +## Purpose + +This template creates a "golden" AMI for **Windows Server 2016** that significantly reduces GOAD deployment time by pre-installing: + +- Windows Updates (saves ~15 minutes per instance) +- MSSQL Express 2019 (saves ~25 minutes per instance) +- IIS with WebDAV (saves ~5 minutes per instance) +- Required PowerShell DSC modules +- SQL Server firewall rules + +**Note**: MSSQL is installed with a temporary sa password. Domain-specific configuration (sysadmins, linked servers, impersonation) happens at runtime. + +## Usage + +### Build the AMI + +```bash +warpgate build goad-mssql-base-2016 --target ami +``` + +### Update Terragrunt + +For SRV03 (uses 2016): + +```hcl +inputs = { + windows_os = "Windows_Server" + windows_os_version = "2016-English-Full-Base" + + additional_windows_ami_filters = [ + { + name = "image-id" + values = ["ami-xxxxxxxxxxxx"] # Your goad-mssql-base-2016 AMI ID + } + ] + + windows_ami_owners = ["self"] +} +``` + +## Tags + +The AMI is tagged with: + +- `Name`: goad-mssql-base-2016 +- `Lab`: GOAD +- `Role`: MemberServer +- `Software`: MSSQL-Express-2019 +- `ManagedBy`: warpgate +- `BaseOS`: WindowsServer2016 diff --git a/warpgate-templates/goad-member-base-2016/scripts/01-install-modules.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 similarity index 100% rename from warpgate-templates/goad-member-base-2016/scripts/01-install-modules.ps1 rename to warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 diff --git a/warpgate-templates/goad-member-base-2016/scripts/02-install-iis.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 similarity index 100% rename from warpgate-templates/goad-member-base-2016/scripts/02-install-iis.ps1 rename to warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 new file mode 100644 index 00000000..97c3402e --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 @@ -0,0 +1,19 @@ +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' + +$downloadUrl = "https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe" + +Write-Host "Creating installation directories..." +New-Item -Path "C:\setup\mssql\media" -ItemType Directory -Force | Out-Null +New-Item -Path "C:\setup\mssql\extraction" -ItemType Directory -Force | Out-Null + +Write-Host "Downloading SQL Server Express 2019 installer..." +Invoke-WebRequest -Uri $downloadUrl -OutFile "C:\setup\mssql\sql_installer.exe" -UseBasicParsing + +Write-Host "Downloading SQL Server installation media (this may take 5-10 minutes)..." +Start-Process -FilePath "C:\setup\mssql\sql_installer.exe" -ArgumentList "/ACTION=Download", "/MEDIAPATH=C:\setup\mssql\media", "/Q" -Wait -NoNewWindow + +Write-Host "Extracting SQL Server installation files..." +Start-Process -FilePath "C:\setup\mssql\media\SQLEXPR_x64_ENU.exe" -ArgumentList "/x:C:\setup\mssql\extraction", "/q" -Wait -NoNewWindow + +Write-Host "SQL Server media download complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 new file mode 100644 index 00000000..8fd8a4e2 --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 @@ -0,0 +1,49 @@ +$ProgressPreference = 'SilentlyContinue' +$ErrorActionPreference = 'Stop' + +$sqlInstanceName = "SQLEXPRESS" + +# Create configuration file +$configContent = @" +[OPTIONS] +ACTION="Install" +FEATURES=SQLENGINE +INSTANCENAME="$sqlInstanceName" +INSTANCEID="$sqlInstanceName" +SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" +SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" +AGTSVCSTARTUPTYPE="Automatic" +SQLSVCSTARTUPTYPE="Automatic" +BROWSERSVCSTARTUPTYPE="Automatic" +SECURITYMODE="SQL" +SAPWD="TempSaPassword123!" +TCPENABLED="1" +NPENABLED="1" +IACCEPTSQLSERVERLICENSETERMS="True" +QUIET="True" +QUIETSIMPLE="False" +UpdateEnabled="False" +ERRORREPORTING="False" +SQMREPORTING="False" +"@ + +Write-Host "Creating SQL Server configuration file..." +$configContent | Out-File -FilePath "C:\setup\mssql\sql_conf.ini" -Encoding ASCII + +Write-Host "Installing SQL Server Express 2019 (this may take 15-25 minutes)..." + +$process = Start-Process -FilePath "C:\setup\mssql\extraction\SETUP.EXE" ` + -ArgumentList "/ConfigurationFile=C:\setup\mssql\sql_conf.ini" ` + -Wait -NoNewWindow -PassThru + +if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { + Write-Host "SQL Server Express installation completed successfully" +} else { + # Check if SQL is actually installed despite exit code + $sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue + if ($sqlService) { + Write-Host "SQL Server Express installation completed (service exists)" + } else { + Write-Error "SQL Server installation failed with exit code: $($process.ExitCode)" + } +} diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 new file mode 100644 index 00000000..b58e3fd9 --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 @@ -0,0 +1,48 @@ +$ProgressPreference = 'SilentlyContinue' + +Write-Host "Configuring SQL Server TCP port..." + +# Set TCP port to 1433 +$regPath = "HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll" +if (Test-Path $regPath) { + Set-ItemProperty -Path $regPath -Name "TcpPort" -Value "1433" + Set-ItemProperty -Path $regPath -Name "TcpDynamicPorts" -Value "" + Write-Host "TCP port configured to 1433" +} else { + Write-Host "Registry path not found - SQL Server may need a restart" +} + +Write-Host "Configuring firewall rules for SQL Server..." + +# Allow SQL Server through firewall +New-NetFirewallRule -DisplayName "MSSQL TCP 1433" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow -Profile Domain -ErrorAction SilentlyContinue +New-NetFirewallRule -DisplayName "MSSQL UDP 1434" -Direction Inbound -Protocol UDP -LocalPort 1434 -Action Allow -Profile Domain -ErrorAction SilentlyContinue + +Write-Host "Firewall rules configured" + +Write-Host "Verifying SQL Server installation..." + +$sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue +if ($sqlService) { + Write-Host "SQL Server service found: $($sqlService.Status)" + + # Ensure service is running + if ($sqlService.Status -ne 'Running') { + Start-Service -Name "MSSQL`$SQLEXPRESS" + Write-Host "SQL Server service started" + } +} else { + Write-Error "SQL Server service not found!" +} + +# Configure SQL Browser service +$browserService = Get-Service -Name "SQLBrowser" -ErrorAction SilentlyContinue +if ($browserService) { + Set-Service -Name "SQLBrowser" -StartupType Automatic + if ($browserService.Status -ne 'Running') { + Start-Service -Name "SQLBrowser" + } + Write-Host "SQL Browser service running" +} + +Write-Host "SQL Server configuration complete" diff --git a/warpgate-templates/goad-member-base-2016/scripts/03-enable-rdp.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 similarity index 100% rename from warpgate-templates/goad-member-base-2016/scripts/03-enable-rdp.ps1 rename to warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 diff --git a/warpgate-templates/goad-member-base-2016/scripts/04-windows-updates.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 similarity index 100% rename from warpgate-templates/goad-member-base-2016/scripts/04-windows-updates.ps1 rename to warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 diff --git a/warpgate-templates/goad-member-base-2016/scripts/05-cleanup.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 similarity index 81% rename from warpgate-templates/goad-member-base-2016/scripts/05-cleanup.ps1 rename to warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 index 9f7a8c1c..00c98077 100644 --- a/warpgate-templates/goad-member-base-2016/scripts/05-cleanup.ps1 +++ b/warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 @@ -5,6 +5,9 @@ Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue Start-Service -Name wuauserv +# Keep SQL installer files (in case reinstall needed) +# Remove-Item -Path "C:\setup" -Recurse -Force -ErrorAction SilentlyContinue + # Clear temp files Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue diff --git a/warpgate-templates/goad-member-base-2016/warpgate.yaml b/warpgate-templates/goad-mssql-base-2016/warpgate.yaml similarity index 78% rename from warpgate-templates/goad-member-base-2016/warpgate.yaml rename to warpgate-templates/goad-mssql-base-2016/warpgate.yaml index 7ac478d4..ccafe611 100644 --- a/warpgate-templates/goad-member-base-2016/warpgate.yaml +++ b/warpgate-templates/goad-mssql-base-2016/warpgate.yaml @@ -1,20 +1,20 @@ # yaml-language-server: $schema=https://raw.githubusercontent.com/cowdogmoo/warpgate/main/schema/warpgate-template.json metadata: - name: goad-member-base-2016 + name: goad-mssql-base-2016 version: 1.0.0 - description: Windows Server 2016 with updates and IIS pre-installed for GOAD member servers + description: Windows Server 2016 with updates and MSSQL Express 2019 pre-installed for GOAD member servers author: Dreadnode license: MIT tags: - goad - windows - windows-2016 - - member-server - - iis + - mssql + - sql-server requires: warpgate: ">=1.0.0" -name: goad-member-base-2016 +name: goad-mssql-base-2016 version: latest base: @@ -29,7 +29,7 @@ variables: provisioners: # Provision with Ansible via AWS SSM - type: ansible - playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/member_base.yml + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base.yml galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml extra_vars: ansible_connection: aws_ssm @@ -40,11 +40,12 @@ targets: - type: ami region: "${aws_region}" instance_type: "${instance_type}" - ami_name: "goad-member-base-2016-{{timestamp}}" + ami_name: "goad-mssql-base-2016-{{timestamp}}" volume_size: 100 ami_tags: - Name: goad-member-base-2016 + Name: goad-mssql-base-2016 Lab: GOAD Role: MemberServer + Software: MSSQL-Express-2019 ManagedBy: warpgate BaseOS: WindowsServer2016 diff --git a/warpgate-templates/goad-mssql-base-2025/README.md b/warpgate-templates/goad-mssql-base-2025/README.md new file mode 100644 index 00000000..e166c94b --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2025/README.md @@ -0,0 +1,56 @@ +# goad-mssql-base-2025 + +Pre-baked Windows Server 2025 AMI with MSSQL Express 2022, IIS, and Windows Updates pre-installed for GOAD member servers (DRACARYS lab). + +## Purpose + +This template creates a "golden" AMI for **Windows Server 2025** that significantly reduces GOAD deployment time by pre-installing: + +- Windows Updates (saves ~15 minutes per instance) +- MSSQL Express 2022 (saves ~25 minutes per instance) +- IIS with WebDAV (saves ~5 minutes per instance) +- Required PowerShell DSC modules +- SQL Server firewall rules + +Uses SQL Server Express **2022** (not 2019) because SQL Server 2019 does not support Windows Server 2025. + +**Note**: MSSQL is installed with a temporary sa password. Domain-specific configuration (sysadmins, linked servers, impersonation) happens at runtime. + +## Usage + +### Build the AMI + +```bash +warpgate build goad-mssql-base-2025 --target ami +``` + +### Update Terragrunt + +For DRACARYS SRV01: + +```hcl +inputs = { + windows_os = "Windows_Server" + windows_os_version = "2025-English-Full-Base" + + additional_windows_ami_filters = [ + { + name = "tag:Name" + values = ["goad-mssql-base-2025"] + } + ] + + windows_ami_owners = ["self"] +} +``` + +## Tags + +The AMI is tagged with: + +- `Name`: goad-mssql-base-2025 +- `Lab`: GOAD +- `Role`: MemberServer +- `Software`: MSSQL-Express-2022 +- `ManagedBy`: warpgate +- `BaseOS`: WindowsServer2025 diff --git a/warpgate-templates/goad-mssql-base-2025/warpgate.yaml b/warpgate-templates/goad-mssql-base-2025/warpgate.yaml new file mode 100644 index 00000000..3ab6492e --- /dev/null +++ b/warpgate-templates/goad-mssql-base-2025/warpgate.yaml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/cowdogmoo/warpgate/main/schema/warpgate-template.json +metadata: + name: goad-mssql-base-2025 + version: 1.0.0 + description: Windows Server 2025 with updates and MSSQL Express 2022 pre-installed for GOAD member servers + author: Dreadnode + license: MIT + tags: + - goad + - windows + - windows-2025 + - mssql + - sql-server + - dracarys + requires: + warpgate: ">=1.0.0" + +name: goad-mssql-base-2025 +version: latest + +base: + image: "arn:aws:ssm:us-west-1::parameter/aws/service/ami-windows-latest/Windows_Server-2025-English-Full-Base" + +variables: + aws_region: us-west-1 + instance_type: t3.medium + ami_owner: "801119661308" + ami_name_filter: "Windows_Server-2025-English-Full-Base-*" + +provisioners: + # Provision with Ansible via AWS SSM + - type: ansible + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base.yml + galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml + extra_vars: + ansible_connection: aws_ssm + ansible_shell_type: powershell + ansible_aws_ssm_bucket_name: "" + ansible_aws_ssm_region: "${aws_region}" +targets: + - type: ami + region: "${aws_region}" + instance_type: "${instance_type}" + ami_name: "goad-mssql-base-2025-{{timestamp}}" + volume_size: 100 + ami_tags: + Name: goad-mssql-base-2025 + Lab: GOAD + Role: MemberServer + Software: MSSQL-Express-2022 + ManagedBy: warpgate + BaseOS: WindowsServer2025 diff --git a/warpgate-templates/goad-mssql-base/README.md b/warpgate-templates/goad-mssql-base/README.md index 0fc96213..325afba6 100644 --- a/warpgate-templates/goad-mssql-base/README.md +++ b/warpgate-templates/goad-mssql-base/README.md @@ -35,12 +35,12 @@ With 2 member servers in GOAD running MSSQL, this saves approximately **96 minut warpgate build goad-mssql-base --target ami # Or with custom region -warpgate build goad-mssql-base --target ami --region us-east-1 +warpgate build goad-mssql-base --target ami --vars aws_region=us-east-1 ``` ### Use in Terragrunt -Update your GOAD terragrunt.hcl for SRV02/SRV03: +Update your GOAD terragrunt.hcl for SRV02: ```hcl inputs = { From f4dc2538a37d6f2c0a142b316b2b79aa279e3af6 Mon Sep 17 00:00:00 2001 From: Jayson Grace Date: Tue, 7 Apr 2026 23:44:38 -0600 Subject: [PATCH 4/4] refactor: split mssql base provisioning, document VPC CIDR config, unify CLI docs **Added:** - New `mssql_base_setup.yml` playbook to handle system setup for MSSQL base images, including DSC modules, IIS, WebDAV, and RDP enablement (Ansible) - CLI reference documentation (`docs/mkdocs/docs/cli-reference.md`) with detailed command and flag listing for the dreadgoad CLI - Blog post announcing DreadGOAD open-source release (`docs/blog-open-source-release.md`) **Changed:** - Split MSSQL provisioning into two playbooks (`mssql_base_setup.yml` for system setup, `mssql_base_sql.yml` for SQL Server install and config) to stay under AWS Image Builder 16K limit; updated documentation and warpgate templates to reflect this split - `mssql_base_sql.yml` refactored to remove system setup tasks and add explicit ssm-user SQL sysadmin grant for SSM access; SQL sysadmin for ssm-user is now bracketed during role config for least privilege - Role `mssql` config tasks updated to bracket ssm-user SQL sysadmin (grant at start, revoke at end) for idempotency and privilege minimization - Documentation (`docs/mkdocs/docs/installation`, `docs/cli.md`, `infra/README.md`, provider docs, usage docs) updated to reflect new CLI workflows, VPC CIDR config, and removal of legacy interactive console in favor of CLI commands - DreadGOAD configuration and CLI (`cli/internal/config/`, `cli/cmd/`, `dreadgoad.yaml`) updated to support per-environment `vpc_cidr` with deterministic fallback and explicit documentation/examples - Terragrunt/infra documentation now references CLI-based environment scaffolding and region creation, superseding manual directory duplication - Removed legacy PowerShell install scripts from warpgate MSSQL/DC templates; all provisioning now occurs via Ansible playbooks - Updated references and migration guides to clarify DreadGOAD workflows and migration from GOAD interactive shell **Removed:** - Legacy monolithic `mssql_base.yml` playbook (replaced by split setup/sql playbooks) - All PowerShell provisioning scripts from warpgate-templates for MSSQL/DC (now handled by Ansible only) - Obsolete interactive console documentation and references; replaced with CLI migration and reference guides --- ansible/playbooks/base/README.md | 27 +- ansible/playbooks/base/mssql_base_setup.yml | 118 ++++ .../{mssql_base.yml => mssql_base_sql.yml} | 131 +---- ansible/roles/mssql/README.md | 2 + ansible/roles/mssql/tasks/config.yml | 49 +- cli/cmd/config_cmd.go | 6 + cli/cmd/env_cmd.go | 27 +- cli/cmd/inventory.go | 6 +- cli/cmd/verify_trusts.go | 3 +- cli/internal/config/config.go | 16 + cli/internal/config/defaults.go | 10 +- cli/internal/terragrunt/validate.go | 2 +- docs/blog-open-source-release.md | 104 ++++ docs/cli.md | 17 + docs/mkdocs/docs/cli-reference.md | 517 ++++++++++++++++++ docs/mkdocs/docs/developers/add_lab.md | 112 +++- docs/mkdocs/docs/developers/add_provider.md | 138 ++--- docs/mkdocs/docs/installation/index.md | 215 +++----- docs/mkdocs/docs/installation/linux.md | 118 ++-- docs/mkdocs/docs/installation/windows.md | 71 +-- docs/mkdocs/docs/labs/NHA.md | 23 +- docs/mkdocs/docs/labs/SCCM.md | 6 +- .../mkdocs/docs/providers/aws-ami-workflow.md | 134 ++--- docs/mkdocs/docs/providers/aws.md | 66 +-- docs/mkdocs/docs/providers/azure.md | 60 +- docs/mkdocs/docs/providers/ludus.md | 44 +- docs/mkdocs/docs/providers/proxmox.md | 10 +- docs/mkdocs/docs/providers/virtualbox.md | 21 +- docs/mkdocs/docs/providers/vmware.md | 21 +- docs/mkdocs/docs/providers/vmware_esxi.md | 26 +- docs/mkdocs/docs/providers/vmware_windows.md | 13 +- docs/mkdocs/docs/provisioning.md | 2 +- docs/mkdocs/docs/references.md | 2 +- docs/mkdocs/docs/troobleshoot.md | 8 +- docs/mkdocs/docs/usage/goad_args.md | 142 ++++- docs/mkdocs/docs/usage/goad_console.md | 469 ++-------------- docs/mkdocs/docs/usage/index.md | 39 +- docs/mkdocs/mkdocs.yml | 5 +- dreadgoad.yaml | 6 + infra/README.md | 38 +- warpgate-templates/README.md | 2 +- .../scripts/01-install-modules.ps1 | 18 - .../scripts/02-install-adds-role.ps1 | 16 - .../scripts/03-enable-rdp.ps1 | 5 - .../scripts/04-windows-updates.ps1 | 23 - .../scripts/05-configure-ssm.ps1 | 27 - .../goad-dc-base-2016/scripts/06-cleanup.ps1 | 17 - .../scripts/01-install-modules.ps1 | 18 - .../scripts/02-install-adds-role.ps1 | 16 - .../goad-dc-base/scripts/03-enable-rdp.ps1 | 5 - .../scripts/04-windows-updates.ps1 | 23 - .../goad-dc-base/scripts/05-configure-ssm.ps1 | 27 - .../goad-dc-base/scripts/06-cleanup.ps1 | 17 - .../scripts/01-install-modules.ps1 | 18 - .../scripts/02-install-iis.ps1 | 10 - .../scripts/03-download-mssql.ps1 | 19 - .../scripts/04-install-mssql.ps1 | 49 -- .../scripts/05-configure-mssql.ps1 | 48 -- .../scripts/06-enable-rdp.ps1 | 5 - .../scripts/07-windows-updates.ps1 | 23 - .../scripts/08-cleanup.ps1 | 20 - .../goad-mssql-base-2016/warpgate.yaml | 11 +- .../goad-mssql-base-2025/warpgate.yaml | 11 +- .../scripts/01-install-modules.ps1 | 18 - .../scripts/02-install-iis.ps1 | 10 - .../scripts/03-download-mssql.ps1 | 19 - .../scripts/04-install-mssql.ps1 | 49 -- .../scripts/05-configure-mssql.ps1 | 48 -- .../goad-mssql-base/scripts/06-enable-rdp.ps1 | 5 - .../scripts/07-windows-updates.ps1 | 23 - .../goad-mssql-base/scripts/08-cleanup.ps1 | 20 - .../goad-mssql-base/warpgate.yaml | 11 +- 72 files changed, 1583 insertions(+), 1872 deletions(-) create mode 100644 ansible/playbooks/base/mssql_base_setup.yml rename ansible/playbooks/base/{mssql_base.yml => mssql_base_sql.yml} (64%) create mode 100644 docs/blog-open-source-release.md create mode 100644 docs/mkdocs/docs/cli-reference.md delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/01-install-modules.ps1 delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/02-install-adds-role.ps1 delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/03-enable-rdp.ps1 delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/04-windows-updates.ps1 delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/05-configure-ssm.ps1 delete mode 100644 warpgate-templates/goad-dc-base-2016/scripts/06-cleanup.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/01-install-modules.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/02-install-adds-role.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/03-enable-rdp.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/04-windows-updates.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/05-configure-ssm.ps1 delete mode 100644 warpgate-templates/goad-dc-base/scripts/06-cleanup.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 delete mode 100644 warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/01-install-modules.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/02-install-iis.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/03-download-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/05-configure-mssql.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/06-enable-rdp.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/07-windows-updates.ps1 delete mode 100644 warpgate-templates/goad-mssql-base/scripts/08-cleanup.ps1 diff --git a/ansible/playbooks/base/README.md b/ansible/playbooks/base/README.md index 494e7cae..342e0ee5 100644 --- a/ansible/playbooks/base/README.md +++ b/ansible/playbooks/base/README.md @@ -37,16 +37,24 @@ Provisions Windows Server with: - Trust relationships - User/Group/OU creation -### `mssql_base.yml` - SQL Server Base +### `mssql_base_setup.yml` + `mssql_base_sql.yml` - SQL Server Base -Provisions Windows Server with: +Split into two playbooks to stay under the AWS Image Builder 16K component data limit. + +**`mssql_base_setup.yml`** provisions Windows Server with: + +- PowerShell DSC modules +- IIS Web Server and WebDAV Publishing +- RDP enabled with firewall rule + +**`mssql_base_sql.yml`** provisions SQL Server: -- All of member_base.yml content -- SQL Server Express 2019 +- SQL Server Express (version auto-detected by OS) - SQL Server firewall rules (TCP 1433, UDP 1434) -- Basic SQL Server configuration +- SQL Server configuration and ssm-user access +- Windows Updates and cleanup -**Used by**: `goad-mssql-base` and `goad-mssql-base-2016` warpgate templates +**Used by**: `goad-mssql-base`, `goad-mssql-base-2016`, and `goad-mssql-base-2025` warpgate templates **Runtime tasks still needed**: @@ -136,20 +144,21 @@ These playbooks expect: - **Network connectivity** to download: - PowerShell modules from PowerShell Gallery - Windows Updates from Microsoft - - SQL Server Express installer (for mssql_base.yml) + - SQL Server Express installer (for mssql_base_sql.yml) ## Variables These playbooks intentionally avoid requiring variables. They use sensible defaults for base image provisioning. -The only configurable variable is in `mssql_base.yml`: +The only configurable variable is in `mssql_base_sql.yml`: ```yaml vars: - sql_download_url: "https://go.microsoft.com/fwlink/p/?linkid=866658" # SQL Server Express 2019 sql_instance_name: "SQLEXPRESS" ``` +The SQL Server download URL is auto-detected based on the Windows Server version. + ## Design Decisions ### Why Not Domain Join in Base Images? diff --git a/ansible/playbooks/base/mssql_base_setup.yml b/ansible/playbooks/base/mssql_base_setup.yml new file mode 100644 index 00000000..ca4a71cf --- /dev/null +++ b/ansible/playbooks/base/mssql_base_setup.yml @@ -0,0 +1,118 @@ +--- +- name: Provision GOAD MSSQL Server Base Image - System Setup + hosts: all + gather_facts: true + + tasks: + - name: Install PowerShellGet and NuGet provider + ansible.windows.win_shell: | + $ProgressPreference = 'SilentlyContinue' + $ErrorActionPreference = 'Stop' + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Install-PackageProvider -Name NuGet -Force -Confirm:$false + Install-Module PowerShellGet -Force -Confirm:$false + register: powershellget_install + retries: 3 + delay: 10 + until: powershellget_install is not failed + + - name: Install required DSC modules + community.windows.win_psmodule: + name: "{{ item }}" + state: present + accept_license: true + skip_publisher_check: true + loop: + - ComputerManagementDsc + - ActiveDirectoryDsc + - xNetworking + - NetworkingDsc + environment: + LOCALAPPDATA: 'C:\Windows\system32\config\systemprofile\AppData\Local' + register: dsc_install + retries: 3 + delay: 10 + until: dsc_install is not failed + + - name: Verify DSC LCM is ready + ansible.windows.win_powershell: + script: | + $ProgressPreference = 'SilentlyContinue' + $ErrorActionPreference = "Stop" + try { + $lcm = Get-DscLocalConfigurationManager + if ($lcm.LCMState -ne 'Idle') { + throw "LCM not ready, state: $($lcm.LCMState)" + } + Write-Output "DSC LCM is ready (state: Idle)" + $Ansible.Changed = $false + } catch { + $Ansible.Failed = $true + throw $_ + } + register: dsc_ready + retries: 5 + delay: 5 + until: dsc_ready is not failed + changed_when: false + + - name: Install IIS Web Server + ansible.windows.win_feature: + name: + - Web-Server + - Web-WebServer + - Web-Common-Http + - Web-Default-Doc + - Web-Dir-Browsing + - Web-Http-Errors + - Web-Static-Content + - Web-Health + - Web-Http-Logging + - Web-Performance + - Web-Stat-Compression + - Web-Security + - Web-Filtering + - Web-Mgmt-Tools + - Web-Mgmt-Console + state: present + include_management_tools: true + register: iis_install + + - name: Install WebDAV Publishing + ansible.windows.win_feature: + name: Web-DAV-Publishing + state: present + register: webdav_install + + - name: Reboot if IIS installation requires it + ansible.windows.win_reboot: + reboot_timeout: 600 + post_reboot_delay: 60 + when: iis_install.reboot_required or webdav_install.reboot_required + + - name: Enable Remote Desktop + ansible.windows.win_dsc: + resource_name: RemoteDesktopAdmin + IsSingleInstance: 'Yes' + Ensure: present + UserAuthentication: Secure + register: rdp_result + retries: 3 + delay: 10 + until: rdp_result is not failed + + - name: Allow RDP through Windows Firewall + ansible.windows.win_dsc: + resource_name: xFirewall + Name: "Administrator access for RDP (TCP-In)" + Ensure: present + Enabled: true + Profile: "Domain" + Direction: "Inbound" + Localport: "3389" + Protocol: "TCP" + Description: "Opens the listener port for RDP" + register: firewall_result + retries: 3 + delay: 10 + until: firewall_result is not failed diff --git a/ansible/playbooks/base/mssql_base.yml b/ansible/playbooks/base/mssql_base_sql.yml similarity index 64% rename from ansible/playbooks/base/mssql_base.yml rename to ansible/playbooks/base/mssql_base_sql.yml index 71c7c297..da5918a8 100644 --- a/ansible/playbooks/base/mssql_base.yml +++ b/ansible/playbooks/base/mssql_base_sql.yml @@ -1,5 +1,5 @@ --- -- name: Provision GOAD MSSQL Server Base Image +- name: Provision GOAD MSSQL Server Base Image - SQL Server hosts: all gather_facts: true @@ -32,92 +32,6 @@ sql_version_id: "MSSQL15" when: os_detect.result != "2025" - - name: Install PowerShellGet and NuGet provider - ansible.windows.win_shell: | - $ProgressPreference = 'SilentlyContinue' - $ErrorActionPreference = 'Stop' - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 - Install-PackageProvider -Name NuGet -Force -Confirm:$false - Install-Module PowerShellGet -Force -Confirm:$false - register: powershellget_install - retries: 3 - delay: 10 - until: powershellget_install is not failed - - - name: Install required DSC modules - community.windows.win_psmodule: - name: "{{ item }}" - state: present - accept_license: true - skip_publisher_check: true - loop: - - ComputerManagementDsc - - ActiveDirectoryDsc - - xNetworking - - NetworkingDsc - environment: - LOCALAPPDATA: 'C:\Windows\system32\config\systemprofile\AppData\Local' - register: dsc_install - retries: 3 - delay: 10 - until: dsc_install is not failed - - - name: Verify DSC LCM is ready - ansible.windows.win_powershell: - script: | - $ProgressPreference = 'SilentlyContinue' - $ErrorActionPreference = "Stop" - try { - $lcm = Get-DscLocalConfigurationManager - if ($lcm.LCMState -ne 'Idle') { - throw "LCM not ready, state: $($lcm.LCMState)" - } - Write-Output "DSC LCM is ready (state: Idle)" - $Ansible.Changed = $false - } catch { - $Ansible.Failed = $true - throw $_ - } - register: dsc_ready - retries: 5 - delay: 5 - until: dsc_ready is not failed - changed_when: false - - - name: Install IIS Web Server - ansible.windows.win_feature: - name: - - Web-Server - - Web-WebServer - - Web-Common-Http - - Web-Default-Doc - - Web-Dir-Browsing - - Web-Http-Errors - - Web-Static-Content - - Web-Health - - Web-Http-Logging - - Web-Performance - - Web-Stat-Compression - - Web-Security - - Web-Filtering - - Web-Mgmt-Tools - - Web-Mgmt-Console - state: present - include_management_tools: true - register: iis_install - - - name: Install WebDAV Publishing - ansible.windows.win_feature: - name: Web-DAV-Publishing - state: present - register: webdav_install - - - name: Reboot if IIS installation requires it - ansible.windows.win_reboot: - reboot_timeout: 600 - post_reboot_delay: 60 - when: iis_install.reboot_required or webdav_install.reboot_required - - name: Create SQL Server setup directories ansible.windows.win_file: path: "{{ item }}" @@ -161,7 +75,7 @@ FEATURES=SQLENGINE INSTANCENAME="{{ sql_instance_name }}" SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" - SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" + SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" "NT AUTHORITY\SYSTEM" AGTSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" TCPENABLED="1" NPENABLED="1" @@ -203,32 +117,21 @@ post_reboot_delay: 60 when: sql_tcp_port.changed - - name: Enable Remote Desktop - ansible.windows.win_dsc: - resource_name: RemoteDesktopAdmin - IsSingleInstance: 'Yes' - Ensure: present - UserAuthentication: Secure - register: rdp_result - retries: 3 - delay: 10 - until: rdp_result is not failed - - - name: Allow RDP through Windows Firewall - ansible.windows.win_dsc: - resource_name: xFirewall - Name: "Administrator access for RDP (TCP-In)" - Ensure: present - Enabled: true - Profile: "Domain" - Direction: "Inbound" - Localport: "3389" - Protocol: "TCP" - Description: "Opens the listener port for RDP" - register: firewall_result - retries: 3 - delay: 10 - until: firewall_result is not failed + - name: Grant ssm-user SQL sysadmin for Ansible SSM connection + ansible.windows.win_powershell: + script: | + $instance = "localhost\{{ sql_instance_name }}" + # ssm-user is in local Administrators, but UAC token filtering strips + # the Administrators SID from non-elevated SSM sessions. Grant ssm-user + # an explicit SQL login so Ansible over SSM can run SqlCmd. + $result1 = SqlCmd -S $instance -E -Q "IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'ssm-user') CREATE LOGIN [ssm-user] FROM WINDOWS WITH DEFAULT_DATABASE=[master]" 2>&1 + if ($LASTEXITCODE -ne 0) { throw "CREATE LOGIN failed: $result1" } + + $result2 = SqlCmd -S $instance -E -Q "IF IS_SRVROLEMEMBER('sysadmin', 'ssm-user') = 0 EXEC sp_addsrvrolemember 'ssm-user', 'sysadmin'" 2>&1 + if ($LASTEXITCODE -ne 0) { throw "sp_addsrvrolemember failed: $result2" } + + Write-Output "ssm-user granted SQL sysadmin on $instance" + register: ssm_sql_grant - name: Allow SQL Server through Windows Firewall (TCP 1433) ansible.windows.win_dsc: diff --git a/ansible/roles/mssql/README.md b/ansible/roles/mssql/README.md index e9b1cfbf..7d562bad 100644 --- a/ansible/roles/mssql/README.md +++ b/ansible/roles/mssql/README.md @@ -26,6 +26,7 @@ Install and configure Microsoft SQL Server Express ### config.yml +- **Grant ssm-user SQL sysadmin for config run** (ansible.windows.win_shell) - **Add MSSQL admin** (ansible.windows.win_shell) - **Log MSSQL admin errors** (ansible.builtin.debug) - Conditional - **Add IMPERSONATE on login** (ansible.windows.win_shell) @@ -35,6 +36,7 @@ Install and configure Microsoft SQL Server Express - **Enable sa account** (ansible.windows.win_shell) - **Log sa account errors** (ansible.builtin.debug) - Conditional - **Enable MSSQL authentication and windows authent** (ansible.windows.win_shell) +- **Revoke ssm-user SQL sysadmin after config** (ansible.windows.win_shell) - **Restart service MSSQL** (ansible.windows.win_service) - Conditional ### install.yml diff --git a/ansible/roles/mssql/tasks/config.yml b/ansible/roles/mssql/tasks/config.yml index 0cb245fb..492ee33d 100644 --- a/ansible/roles/mssql/tasks/config.yml +++ b/ansible/roles/mssql/tasks/config.yml @@ -3,8 +3,33 @@ # These tasks configure SQL Server after installation # They should ALWAYS run to ensure proper configuration (idempotent) # -# Note: SSM sessions run as ssm-user (not SYSTEM), so we must use -# become to escalate to SYSTEM which has SQL sysadmin privileges. +# Note: Tasks use become/runas SYSTEM for SqlCmd. The golden AMI build also +# grants ssm-user an explicit SQL sysadmin login as defense-in-depth +# (BUILTIN\Administrators alone is not sufficient due to UAC token filtering +# on non-elevated SSM sessions). +# +# ssm-user sysadmin is bracketed: granted at the start of config, revoked at +# the end. This keeps re-runs idempotent while minimizing residual privilege. + +- name: Grant ssm-user SQL sysadmin for config run + ansible.windows.win_shell: | + $ErrorActionPreference = "Continue" + $errors = @() + + $result1 = SqlCmd {{ connection_type }} -Q "IF NOT EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'ssm-user') CREATE LOGIN [ssm-user] FROM WINDOWS WITH DEFAULT_DATABASE=[master]" 2>&1 + if ($LASTEXITCODE -ne 0) { $errors += "CREATE LOGIN failed: $result1" } + + $result2 = SqlCmd {{ connection_type }} -Q "IF IS_SRVROLEMEMBER('sysadmin', 'ssm-user') = 0 EXEC sp_addsrvrolemember 'ssm-user', 'sysadmin'" 2>&1 + if ($LASTEXITCODE -ne 0) { $errors += "sp_addsrvrolemember failed: $result2" } + + if ($errors.Count -gt 0) { + Write-Error ($errors -join "`n") + exit 1 + } + Write-Output "ssm-user granted SQL sysadmin" + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM - name: Add MSSQL admin ansible.windows.win_shell: | @@ -147,6 +172,26 @@ become_user: SYSTEM register: auth_mode_result +- name: Revoke ssm-user SQL sysadmin after config + ansible.windows.win_shell: | + $ErrorActionPreference = "Continue" + $errors = @() + + $result1 = SqlCmd {{ connection_type }} -Q "IF IS_SRVROLEMEMBER('sysadmin', 'ssm-user') = 1 EXEC sp_dropsrvrolemember 'ssm-user', 'sysadmin'" 2>&1 + if ($LASTEXITCODE -ne 0) { $errors += "sp_dropsrvrolemember failed: $result1" } + + $result2 = SqlCmd {{ connection_type }} -Q "IF EXISTS (SELECT 1 FROM sys.server_principals WHERE name = 'ssm-user') DROP LOGIN [ssm-user]" 2>&1 + if ($LASTEXITCODE -ne 0) { $errors += "DROP LOGIN failed: $result2" } + + if ($errors.Count -gt 0) { + Write-Error ($errors -join "`n") + exit 1 + } + Write-Output "ssm-user SQL sysadmin revoked" + become: true + become_method: ansible.builtin.runas + become_user: SYSTEM + - name: Restart service MSSQL ansible.windows.win_service: name: "{{ mssql_service_name }}" diff --git a/cli/cmd/config_cmd.go b/cli/cmd/config_cmd.go index ac4b5042..45c419eb 100644 --- a/cli/cmd/config_cmd.go +++ b/cli/cmd/config_cmd.go @@ -99,8 +99,14 @@ environments: variant_source: ad/GOAD variant_target: ad/GOAD-variant-1 variant_name: variant-1 + vpc_cidr: "10.0.0.0/16" staging: variant: false + vpc_cidr: "10.1.0.0/16" + prod: + vpc_cidr: "10.2.0.0/16" + test: + vpc_cidr: "10.8.0.0/16" ` if err := os.WriteFile(cfgPath, []byte(content), 0o644); err != nil { return err diff --git a/cli/cmd/env_cmd.go b/cli/cmd/env_cmd.go index 26c13aa1..728ae1f7 100644 --- a/cli/cmd/env_cmd.go +++ b/cli/cmd/env_cmd.go @@ -58,25 +58,6 @@ func init() { envCreateCmd.Flags().Bool("force", false, "Overwrite existing environment") } -func vpcCIDRForEnv(envName string) string { - knownCIDRs := map[string]string{ - "dev": "10.0.0.0/16", - "staging": "10.1.0.0/16", - "prod": "10.2.0.0/16", - "test": "10.8.0.0/16", - } - if cidr, ok := knownCIDRs[envName]; ok { - return cidr - } - // Generate a deterministic second octet from env name (range 10-250) - var hash byte - for _, c := range envName { - hash = hash*31 + byte(c) - } - octet := int(hash)%240 + 10 - return fmt.Sprintf("10.%d.0.0/16", octet) -} - func runEnvCreate(cmd *cobra.Command, args []string) error { envName := strings.TrimSpace(args[0]) if envName == "" { @@ -94,12 +75,12 @@ func runEnvCreate(cmd *cobra.Command, args []string) error { useVariant, _ := cmd.Flags().GetBool("variant") force, _ := cmd.Flags().GetBool("force") - if vpcCIDR == "" { - vpcCIDR = vpcCIDRForEnv(envName) - } - deployment := cfg.Infra.Deployment infraBase := filepath.Join(cfg.ProjectRoot, "infra", deployment) + + if vpcCIDR == "" { + vpcCIDR = cfg.VpcCIDR(envName) + } envDir := filepath.Join(infraBase, envName) regionDir := filepath.Join(envDir, region) diff --git a/cli/cmd/inventory.go b/cli/cmd/inventory.go index 4ef9cbff..ebf2a9f6 100644 --- a/cli/cmd/inventory.go +++ b/cli/cmd/inventory.go @@ -228,7 +228,11 @@ func generateInstanceMapping(ctx context.Context, outputPath string) error { } if outputPath == "" { - outputPath = filepath.Join(os.TempDir(), fmt.Sprintf("aws_instance_mapping_%s.json", cfg.Env)) + // Use /tmp explicitly to match the hardcoded path in + // ansible/roles/network_discovery/tasks/aws_mapping.yml. + // os.TempDir() on macOS returns a per-user dir under /var/folders/ + // which would not match Ansible's expectation. + outputPath = filepath.Join("/tmp", fmt.Sprintf("aws_instance_mapping_%s.json", cfg.Env)) } client, err := daws.NewClient(ctx, parsed.Region()) diff --git a/cli/cmd/verify_trusts.go b/cli/cmd/verify_trusts.go index 8b2d24f2..b1790600 100644 --- a/cli/cmd/verify_trusts.go +++ b/cli/cmd/verify_trusts.go @@ -86,7 +86,8 @@ func runVerifyTrusts(cmd *cobra.Command, args []string) error { script.WriteString("\nWrite-Host ''\nWrite-Host '=== Trust Status ==='\n") script.WriteString("$trusts = Get-ADTrust -Filter *\n") script.WriteString("foreach ($t in $trusts) {\n") - script.WriteString(" Write-Host \"$($t.Name): $(if (Test-ComputerSecureChannel -Server $t.Name -ErrorAction SilentlyContinue) { 'HEALTHY' } else { 'Check manually' })\"\n") + script.WriteString(" $null = nltest /sc_verify:$($t.Name) 2>&1\n") + script.WriteString(" if ($LASTEXITCODE -eq 0) { Write-Host \"$($t.Name): HEALTHY\" } else { Write-Host \"$($t.Name): Check manually\" }\n") script.WriteString("}\n") result, err := infra.Client.RunPowerShellCommand(ctx, srcID, script.String(), 2*time.Minute) diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index 35b166b6..f26c1db8 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -27,6 +27,7 @@ type EnvironmentConfig struct { VariantTarget string `mapstructure:"variant_target"` VariantName string `mapstructure:"variant_name"` EnabledExtensions []string `mapstructure:"enabled_extensions"` + VpcCidr string `mapstructure:"vpc_cidr"` } // InfraConfig holds infrastructure/terragrunt settings. @@ -223,6 +224,21 @@ func (c *Config) EnabledExtensionsForEnv() []string { return c.ActiveEnvironment().EnabledExtensions } +// VpcCIDR returns the VPC CIDR for the given environment. It checks the +// environment config first, falling back to deterministic generation. +func (c *Config) VpcCIDR(envName string) string { + if ec, ok := c.Environments[envName]; ok && ec.VpcCidr != "" { + return ec.VpcCidr + } + // Generate a deterministic second octet from env name (range 10-250) + var hash byte + for _, ch := range envName { + hash = hash*31 + byte(ch) + } + octet := int(hash)%240 + 10 + return fmt.Sprintf("10.%d.0.0/16", octet) +} + // InfraBasePath returns the base path for a deployment's infra directory. func (c *Config) InfraBasePath() string { return filepath.Join(c.ProjectRoot, "infra", c.Infra.Deployment) diff --git a/cli/internal/config/defaults.go b/cli/internal/config/defaults.go index a0cf34d5..90f84e59 100644 --- a/cli/internal/config/defaults.go +++ b/cli/internal/config/defaults.go @@ -90,9 +90,17 @@ func setDefaults() { "variant_source": "ad/GOAD", "variant_target": "ad/GOAD-variant-1", "variant_name": "variant-1", + "vpc_cidr": "10.0.0.0/16", }, "staging": map[string]interface{}{ - "variant": false, + "variant": false, + "vpc_cidr": "10.1.0.0/16", + }, + "prod": map[string]interface{}{ + "vpc_cidr": "10.2.0.0/16", + }, + "test": map[string]interface{}{ + "vpc_cidr": "10.8.0.0/16", }, }) } diff --git a/cli/internal/terragrunt/validate.go b/cli/internal/terragrunt/validate.go index 45361f47..6c329ee3 100644 --- a/cli/internal/terragrunt/validate.go +++ b/cli/internal/terragrunt/validate.go @@ -94,7 +94,7 @@ func ValidateEnvironment(basePath, env, region string) *ValidationResult { if content, err := os.ReadFile(hclPath); err == nil { if strings.Contains(string(content), "CHANGE_ME") { result.Warnings = append(result.Warnings, - fmt.Sprintf("goad/%s/terragrunt.hcl has CHANGE_ME placeholder(s) - update AMI IDs and passwords", host)) + fmt.Sprintf("goad/%s/terragrunt.hcl has CHANGE_ME placeholder(s)", host)) } } } diff --git a/docs/blog-open-source-release.md b/docs/blog-open-source-release.md new file mode 100644 index 00000000..4aa14780 --- /dev/null +++ b/docs/blog-open-source-release.md @@ -0,0 +1,104 @@ +# DreadGOAD: Open-Sourcing the Infrastructure Behind Our Offensive Security Research + +By Jayson Grace · April 7, 2026 + +--- + +Many of the Active Directory evaluations we publish at Dreadnode - from [PentestJudge](https://arxiv.org/abs/2508.02921) to [Kerberoasting agents](https://dreadnode.io/research/evaluating-offensive-cyber-agents-kerberoasting) to [training an 8B model to pop Domain Admin](https://dreadnode.io/research/worlds-a-simulation-engine-for-agentic-pentesting) - start the same way: spin up a vulnerable AD network, point an agent at it, and measure what happens. That network is GOAD, the [Game of Active Directory](https://github.com/Orange-Cyberdefense/GOAD) by Orange Cyberdefense. Over the past year we've rebuilt the tooling around it into something we could run reliably at scale. Today we're open-sourcing the result: [**DreadGOAD**](https://github.com/dreadnode/DreadGOAD). + +## Why GOAD, and Why Fork It + +GOAD is one of the best open-source resources in AD security training. Mayfly277 and the Orange Cyberdefense team built a multi-domain, multi-forest Active Directory environment packed with over 50 real-world vulnerabilities - Kerberoasting, AS-REP roasting, ACL abuse chains, ADCS misconfigurations (ESC1 through ESC8), delegation abuse, MSSQL attacks, and more. It's what a messy corporate AD actually looks like, compressed into a handful of VMs. + +We adopted GOAD early as the target environment for our offensive agent evaluations. It didn't take long to hit the walls. We needed to deploy labs in AWS without exposing management ports, tear them down and rebuild programmatically between eval runs, and validate that all 50+ vulnerabilities were actually configured correctly after provisioning - because a misconfigured lab silently invalidates every result you collect against it. On top of that, we needed to generate structurally identical variants of the lab with different entity names, so agents couldn't memorize their way to Domain Admin and Golden Tickets. + +None of that existed in upstream GOAD. So we built it. + +## What DreadGOAD Adds + +DreadGOAD is a downstream fork that preserves GOAD's core lab designs while wrapping them in infrastructure automation we can actually run unattended. The major additions: + +### A Single Go Binary + +The `dreadgoad` CLI replaces a collection of Python scripts and shell commands with one binary that handles the full lab lifecycle: + +```bash +# Deploy infrastructure, provision the lab, validate everything works +dreadgoad infra init +dreadgoad infra apply +dreadgoad provision +dreadgoad validate --quick + +# Check health, manage instances +dreadgoad health-check +dreadgoad lab status +dreadgoad lab stop-vm winterfell + +# Open an interactive shell - no SSH keys, no open ports +dreadgoad ssm connect kingslanding +``` + +The CLI covers infrastructure deployment, Ansible provisioning with retry logic, health checks, vulnerability validation, SSM sessions, multi-environment management, extensions, and variant generation. Configuration is Viper-based - YAML files, environment variables, and CLI flags all merge cleanly. + +### AWS Infrastructure as Code + +Terragrunt and Terraform modules deploy the full lab into AWS with a design built around SSM Session Manager. Lab instances don't need public IPs, and management access doesn't depend on exposed SSH or RDP ports. Management traffic flows through VPC endpoints, and state is stored in S3 with DynamoDB locking. Golden AMIs built via warpgate pre-bake Windows Updates, AD DS roles, and MSSQL, cutting per-deployment provisioning time substantially. + +### Automated Vulnerability Validation + +This is the feature we wished existed from day one. `dreadgoad validate` executes PowerShell checks over SSM against every configured vulnerability in the lab - Kerberoastable SPNs, AS-REP roastable accounts, ACL misconfigurations, ADCS template abuse, constrained and unconstrained delegation, LAPS configurations, the works. Output is a structured report (table or JSON) telling you exactly what's correctly configured and what isn't, before you waste compute on an eval run against a broken lab. + +### Variant Generator + +For evaluations, you don't want agents that have memorized that `joffrey.baratheon` is Kerberoastable. The variant generator, built by Michael Kouremetis, creates graph-isomorphic copies of any lab. Entity names are randomized where possible while structural relationships and attack paths stay intact. It transforms 96+ files across JSON, YAML, PowerShell, Terraform, and Vagrant configurations, and produces a mapping file so you can trace back to the original topology. Same vulnerabilities, different surface. + +### Modular Extensions + +Plug-in extensions add optional components without modifying core lab definitions: ELK for log aggregation, Wazuh for EDR monitoring, Exchange for email-based attack scenarios, Guacamole for browser-based access, and additional workstation and Linux host configurations. Extensions include provider-specific configurations across VirtualBox, VMware, Proxmox, AWS, Azure, and Ludus where applicable. + +### Seven Lab Environments + +Beyond the original GOAD and GOAD-Light, DreadGOAD includes GOAD-Mini (single DC, fastest deployment), MINILAB (one DC plus one workstation), SCCM (Microsoft Endpoint Configuration Manager scenarios), NHA (Ninja Hacker Academy challenge mode), and DRACARYS (training challenges). A template is included for building custom labs. + +## How We Use It + +DreadGOAD is the infrastructure layer for the kind of AD-focused evaluation work we publish: reproducible deployments, resettable environments, automated validation, and controlled variants. + +That pattern shows up across recent research: + +[PentestJudge](https://dreadnode.io/research/pentestjudge-judging-agent-behavior-against-operational-requirements) is the clearest example. Grading agent behavior against live enterprise targets means you need the same target environment every time - same vulnerabilities, same topology, same attack surface. A lab that drifts between runs poisons your ground truth. DreadGOAD's validation step catches that before we burn compute on trajectories we can't trust. + +The [Kerberoasting benchmark](https://dreadnode.io/research/evaluating-offensive-cyber-agents-kerberoasting) pushed us hardest on the rapid teardown-rebuild cycle. [Worlds](https://dreadnode.io/research/worlds-a-simulation-engine-for-agentic-pentesting) - our sim2real transfer work - needed a concrete AD environment to check whether synthetic trajectories actually hold up against real targets. Our [evals foundation](https://dreadnode.io/research/evals-the-foundation-for-autonomous-offensive-security) work treats GOAD-style labs as canonical targets, and DreadGOAD is what makes those targets practical to run repeatedly. The [LLM-powered AMSI provider](https://dreadnode.io/research/llm-powered-amsi-provider-vs-red-team-agent) research was a reminder that host provisioning and repeatability matter just as much as the detection logic you're testing. + +In short: when we need a realistic AD target that can be programmatically deployed, validated, and torn down, this is what we reach for. + +## Getting Started + +```bash +git clone https://github.com/dreadnode/DreadGOAD.git +cd DreadGOAD + +# Install Ansible dependencies +ansible-galaxy collection install -r ansible/requirements.yml + +# Build the CLI +cd cli && go build -o dreadgoad . && cd .. + +# Deploy and validate +./cli/dreadgoad provision +./cli/dreadgoad validate --quick +``` + +DreadGOAD supports six infrastructure providers: VirtualBox and VMware for local deployment, Proxmox for on-premises hypervisors, AWS and Azure for cloud, and Ludus for range-based environments. Provider-specific guides are in the [GitHub documentation](https://github.com/dreadnode/DreadGOAD/tree/main/docs/mkdocs/docs/providers). + +For AWS deployments, the [AMI build and deploy workflow](https://github.com/dreadnode/DreadGOAD/blob/main/docs/mkdocs/docs/providers/aws-ami-workflow.md) walks through the full pipeline from golden AMI creation through Terragrunt deployment to Ansible provisioning. + +## Acknowledgments + +DreadGOAD wouldn't exist without [Mayfly277](https://github.com/Mayfly277) and the [Orange Cyberdefense](https://github.com/Orange-Cyberdefense) team. They built and maintain the upstream GOAD project, and everything here is downstream of their work. If you find DreadGOAD useful, consider [sponsoring the original creator](https://github.com/sponsors/Mayfly277). + +## What's Next + +We're actively building on DreadGOAD - new lab configurations for additional attack scenarios, better provisioning reliability across providers, and closer integration with our eval toolchain. Contributions are welcome - see the [contribution guidelines](https://github.com/dreadnode/DreadGOAD/blob/main/CONTRIBUTING.md). + +DreadGOAD is available now at [github.com/dreadnode/DreadGOAD](https://github.com/dreadnode/DreadGOAD). diff --git a/docs/cli.md b/docs/cli.md index 89630895..79b5a7a6 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -64,8 +64,14 @@ environments: variant_source: ad/GOAD # Source directory to clone from variant_target: ad/GOAD-variant-1 # Output directory for generated variant variant_name: variant-1 # Variant identifier + vpc_cidr: "10.0.0.0/16" # VPC CIDR block for this environment staging: variant: false + vpc_cidr: "10.1.0.0/16" + prod: + vpc_cidr: "10.2.0.0/16" + test: + vpc_cidr: "10.8.0.0/16" ``` ## Per-Environment Settings @@ -73,6 +79,16 @@ environments: The `environments` map lets you configure behavior per environment. The active environment is selected by the top-level `env` key. +### VPC CIDR + +Each environment needs a unique VPC CIDR block. Set `vpc_cidr` in the +environment config -- this value is used by `dreadgoad env create` when +scaffolding Terragrunt files and must match the `vpc_cidr` in the +corresponding `env.hcl`. + +If `vpc_cidr` is not set and no `--vpc-cidr` flag is passed, the CLI +generates a deterministic CIDR from the environment name. + ### Variant Support When `variant: true`, the environment uses a randomized GOAD variant @@ -82,6 +98,7 @@ that preserve all structural relationships and vulnerabilities. | Key | Description | Default | |------------------|--------------------------------------|----------------------| +| `vpc_cidr` | VPC CIDR block for this environment | Auto-generated | | `variant` | Enable randomized variant | `false` | | `variant_source` | Source GOAD directory to clone from | `ad/GOAD` | | `variant_target` | Output directory for the variant | `ad/GOAD-variant-1` | diff --git a/docs/mkdocs/docs/cli-reference.md b/docs/mkdocs/docs/cli-reference.md new file mode 100644 index 00000000..c2b3a01d --- /dev/null +++ b/docs/mkdocs/docs/cli-reference.md @@ -0,0 +1,517 @@ +# CLI Reference + +Complete reference for the `dreadgoad` command-line interface. + +DreadGOAD orchestrates the deployment and management of intentionally +vulnerable Active Directory environments for security research and testing. + +## Global Flags + +| Flag | Description | +|------|-------------| +| `--config string` | Config file path | +| `--debug` | Enable debug/verbose output | +| `-e, --env string` | Target environment: dev, staging, prod (default `"staging"`) | +| `--region string` | AWS region (default: from inventory) | +| `-v, --version` | Print version information | + +--- + +## Getting Started + +### config + +Manage CLI configuration. + +#### `config init` + +Create default configuration file. + +```bash +dreadgoad config init +``` + +#### `config show` + +Display current effective configuration. + +```bash +dreadgoad config show +``` + +#### `config set` + +Set a configuration value. + +```bash +dreadgoad config set +``` + +### doctor + +Run pre-flight system checks. + +Verifies that all required tools and configurations are in place: + +- ansible-core version +- AWS CLI +- Python +- Ansible collections +- Credentials +- Inventory + +```bash +dreadgoad doctor +``` + +### env + +Manage deployment environments. + +#### `env create` + +Create a new deployment environment. + +```bash +dreadgoad env create +``` + +#### `env list` + +List available environments. + +```bash +dreadgoad env list +``` + +--- + +## Infrastructure + +### infra + +Manage DreadGOAD infrastructure via Terragrunt. Operates on the `infra/` directory. By default, commands operate on all modules (`run-all`). Use `--module` to target a specific module. + +| Flag | Description | +|------|-------------| +| `-d, --deployment string` | Deployment name | + +#### `infra init` + +Initialize Terragrunt modules. + +```bash +dreadgoad infra init +``` + +#### `infra plan` + +Plan infrastructure changes. + +```bash +dreadgoad infra plan +``` + +#### `infra apply` + +Apply infrastructure changes. + +```bash +dreadgoad infra apply +``` + +#### `infra destroy` + +Destroy infrastructure. + +```bash +dreadgoad infra destroy +``` + +#### `infra output` + +Show Terragrunt outputs (JSON). + +```bash +dreadgoad infra output +``` + +#### `infra validate` + +Validate environment configuration. + +```bash +dreadgoad infra validate +``` + +### ami + +AMI image management. + +#### `ami build` + +Build an AMI from a warpgate template. + +```bash +dreadgoad ami build [template] +``` + +#### `ami list-resources` + +List Image Builder pipeline resources created by warpgate. + +```bash +dreadgoad ami list-resources +``` + +#### `ami purge` + +Remove Image Builder pipeline resources (not AMIs). + +```bash +dreadgoad ami purge [template] +``` + +--- + +## Provisioning + +### provision + +Run GOAD provisioning playbooks with retry logic. + +Runs Ansible playbooks to provision Active Directory infrastructure with error-specific retry strategies, SSM session management, and idle timeout monitoring. + +| Flag | Description | +|------|-------------| +| `--from string` | Resume provisioning from this playbook onward | +| `--limit string` | Limit execution to specific hosts | +| `--max-retries int` | Max retry attempts | +| `--plays string` | Comma-separated playbooks to run (default: all) | +| `--retry-delay int` | Delay between retries in seconds | + +```bash +# Run all provisioning playbooks +dreadgoad provision + +# Resume from a specific playbook +dreadgoad provision --from vulnerabilities.yml + +# Run specific playbooks only +dreadgoad provision --plays "ad-groups.yml,ad-acl.yml" + +# Limit to specific hosts with retries +dreadgoad provision --limit dc01 --max-retries 5 +``` + +### ad-users + +Ensure AD users exist (runs `ad-data.yml`). + +Shortcut for `provision --plays ad-data.yml`. + +| Flag | Description | +|------|-------------| +| `--limit string` | Limit execution to specific hosts | +| `--max-retries int` | Max retry attempts | +| `--plays string` | Comma-separated playbooks to run | +| `--retry-delay int` | Delay between retries in seconds | + +```bash +dreadgoad ad-users +``` + +### variant + +Generate GOAD variants with randomized entity names. Creates a graph-isomorphic copy of GOAD with randomized names while preserving structure, relationships, vulnerabilities, and attack paths. + +#### `variant generate` + +Generate a new GOAD variant. + +```bash +dreadgoad variant generate +``` + +--- + +## Lab Operations + +### lab + +Manage DreadGOAD lab lifecycle. + +#### `lab status` + +Show lab instance states. + +```bash +dreadgoad lab status +``` + +#### `lab start` + +Start stopped lab instances. + +```bash +dreadgoad lab start +``` + +#### `lab stop` + +Stop running lab instances. + +```bash +dreadgoad lab stop +``` + +#### `lab start-vm` + +Start a specific lab VM. + +```bash +dreadgoad lab start-vm +``` + +#### `lab stop-vm` + +Stop a specific lab VM. + +```bash +dreadgoad lab stop-vm +``` + +#### `lab restart-vm` + +Restart a specific lab VM. + +```bash +dreadgoad lab restart-vm +``` + +#### `lab destroy-vm` + +Terminate a specific lab VM. + +```bash +dreadgoad lab destroy-vm +``` + +#### `lab list` + +List available DreadGOAD labs and their providers. + +```bash +dreadgoad lab list +``` + +### ssm + +Manage AWS SSM sessions. + +#### `ssm status` + +Show active SSM sessions. + +```bash +dreadgoad ssm status +``` + +#### `ssm cleanup` + +Terminate stale SSM sessions. + +```bash +dreadgoad ssm cleanup +``` + +#### `ssm connect` + +Start interactive SSM session. + +```bash +dreadgoad ssm connect +``` + +#### `ssm run` + +Run PowerShell commands across instances via SSM. + +```bash +dreadgoad ssm run +``` + +### health-check + +Verify all lab instances are healthy. + +Runs health checks across all lab instances via SSM to verify: + +- Domain controllers responding +- AD replication +- Domain trusts +- DNS resolution +- Member server connectivity +- Critical services (IIS, MSSQL) + +```bash +dreadgoad health-check +``` + +### diagnose + +Run diagnostic checks against domain controllers. + +Runs the `diagnose-dc01` playbook from an independent host to verify network connectivity, LDAP, WinRM, and DNS for the primary domain controller. + +| Flag | Description | +|------|-------------| +| `--dc01-ip string` | Override dc01 IP address (skips AWS lookup) | + +```bash +# Auto-detect dc01 IP from AWS +dreadgoad diagnose + +# Specify dc01 IP manually +dreadgoad diagnose --dc01-ip 10.0.10.10 +``` + +### verify-trusts + +Verify domain trust relationships. + +Validates parent-child trusts, forest trusts, and cross-domain authentication. + +```bash +dreadgoad verify-trusts +``` + +--- + +## Validation + +### validate + +Validate GOAD vulnerability configurations. + +Checks credentials, Kerberos, SMB, delegation, MSSQL, ADCS, ACLs, trusts, SID filtering, scheduled tasks, LLMNR/NBT-NS, GPO abuse, gMSA, LAPS, and services. + +| Flag | Description | +|------|-------------| +| `--format string` | Output format: `table` or `json` (default `"table"`) | +| `--no-fail` | Don't exit with error on failed checks | +| `--output string` | JSON report output path | +| `--quick` | Quick validation of critical vulnerabilities only | +| `--verbose` | Enable verbose output | + +```bash +# Full validation with table output +dreadgoad validate + +# Quick check of critical vulnerabilities +dreadgoad validate --quick + +# Export JSON report +dreadgoad validate --format json --output report.json + +# Verbose output, don't fail on errors +dreadgoad validate --verbose --no-fail +``` + +--- + +## Extensions + +### extension + +Manage lab extensions. List, inspect, and provision lab extensions such as ELK, Exchange, Guacamole, and more. + +Alias: `ext` + +#### `extension list` + +List available extensions. + +```bash +dreadgoad extension list +``` + +#### `extension provision` + +Provision a specific extension. + +```bash +dreadgoad extension provision +``` + +#### `extension provision-all` + +Provision all enabled extensions for the active environment. + +```bash +dreadgoad extension provision-all +``` + +--- + +## Inventory + +### inventory + +Manage Ansible inventory. + +#### `inventory sync` + +Synchronize inventory with AWS instance IDs. + +```bash +dreadgoad inventory sync +``` + +#### `inventory show` + +Display current inventory. + +```bash +dreadgoad inventory show +``` + +#### `inventory mapping` + +Generate instance-to-IP mapping for Ansible optimization. + +```bash +dreadgoad inventory mapping +``` + +--- + +## Shell Completion + +### completion + +Generate shell autocompletion scripts. + +```bash +# Bash +dreadgoad completion bash + +# Zsh +dreadgoad completion zsh + +# Fish +dreadgoad completion fish + +# PowerShell +dreadgoad completion powershell +``` + +To load completions in your current shell session: + +```bash +# Bash +source <(dreadgoad completion bash) + +# Zsh +source <(dreadgoad completion zsh) +``` diff --git a/docs/mkdocs/docs/developers/add_lab.md b/docs/mkdocs/docs/developers/add_lab.md index 6ebfa994..4efd129b 100644 --- a/docs/mkdocs/docs/developers/add_lab.md +++ b/docs/mkdocs/docs/developers/add_lab.md @@ -1,28 +1,88 @@ # Add a new lab -🚧 TODO TO BE COMPLETED - -- To create a new lab: - - Create a new folder in `ad/` with the name of the lab - - Create the following structure: - - ```bash - ad// - data/ - config.json # json containing all the lab information - inventory # global lab inventory file with the vm groups and the main variables - inventory_disable_vagrant # inventory to disable/enable vagrant - files/ - providers/ - aws|azure|proxmox/ # terraform based providers - inventory # inventory specific to the provider - linux.tf # linux vms - windows.tf # windows vms - ludus/ # ludus provider - inventory # inventory specific to the provider - config.yml # ludus configuration file - virtualbox|vmware/ # vagrant based provider - inventory # inventory specific to the provider - Vagrantfile # vms - scripts/ - ``` +To create a new lab, create a folder in `ad/` with the lab name. The `dreadgoad` CLI automatically discovers labs by scanning the `ad/` directory (use `dreadgoad lab list` to verify your lab is detected). + +## Directory structure + +Create the following structure inside `ad//`: + +```text +ad// + data/ + config.json # JSON containing all the lab information + inventory # global lab inventory file with the VM groups and the main variables + inventory_disable_vagrant # inventory to disable/enable vagrant user + files/ # extra files needed during provisioning (scripts, templates, etc.) + providers/ + aws|azure|proxmox/ # Terraform/Terragrunt based providers + inventory # inventory specific to the provider + linux.tf # linux VMs + windows.tf # windows VMs + ludus/ # Ludus provider + inventory # inventory specific to the provider + config.yml # Ludus configuration file + virtualbox|vmware/ # Vagrant based providers + inventory # inventory specific to the provider + Vagrantfile # VM definitions + scripts/ # PowerShell or other scripts used by Ansible roles +``` + +## config.json format + +The `config.json` file in `data/` defines all lab hosts, their domains, users, groups, vulnerabilities, and security settings. Required top-level structure: + +```json +{ + "lab": { + "hosts": { + "": { + "hostname": "short hostname", + "type": "dc|server", + "local_admin_password": "strong password", + "domain": "example.local", + "path": "DC=example,DC=local", + "local_groups": { + "Administrators": ["domain\\user"], + "Remote Desktop Users": ["domain\\group"] + }, + "scripts": ["script.ps1"], + "vulns": ["vuln_name"], + "security": ["security_feature"], + "security_vars": {} + } + } + } +} +``` + +Key fields per host: + +| Field | Required | Description | +|-------|----------|-------------| +| `hostname` | Yes | Short hostname for the VM | +| `type` | Yes | `dc` for domain controller, `server` for member server | +| `domain` | Yes | FQDN of the AD domain this host belongs to | +| `path` | Yes | LDAP distinguished name path | +| `local_admin_password` | Yes | Local administrator password | +| `local_groups` | No | Local group memberships | +| `scripts` | No | PowerShell scripts to execute on the host | +| `vulns` | No | Vulnerability configurations to apply | +| `security` | No | Security hardening features to enable | + +See `ad/GOAD/data/config.json` for a complete reference example. + +## Inventory files + +The `data/inventory` file is an Ansible inventory that defines host groups and connection variables (WinRM settings, credentials). Each provider also has its own `inventory` file under `providers//` that overrides connection-specific values (IP addresses, ports) for that provider. + +The `data/inventory_disable_vagrant` inventory is used by the `disable_vagrant.yml` and `enable_vagrant.yml` playbooks to manage the vagrant user on VMs. + +## Provider-specific files + +- **Terraform/Terragrunt providers** (aws, azure, proxmox): Include `windows.tf` and optionally `linux.tf` files that define the VMs as Terraform resources. Infrastructure modules live in `infra/` and are invoked via Terragrunt. +- **Vagrant providers** (virtualbox, vmware): Include a `Vagrantfile` that defines VM resources, networking, and linked clones. +- **Ludus provider**: Uses a `config.yml` that describes the VMs in Ludus format. + +## Lab discovery + +The CLI discovers labs automatically by scanning the `ad/` directory. No additional registration step is needed. Run `dreadgoad lab list` to confirm your lab appears. The lab name is derived from the directory name. diff --git a/docs/mkdocs/docs/developers/add_provider.md b/docs/mkdocs/docs/developers/add_provider.md index 27c35776..754818cb 100644 --- a/docs/mkdocs/docs/developers/add_provider.md +++ b/docs/mkdocs/docs/developers/add_provider.md @@ -1,123 +1,69 @@ # Add a new provider -🚧 TODO TO BE COMPLETED +Adding a new provider involves creating provider-specific files for each lab and updating the DreadGOAD CLI and infrastructure modules. ## Provider files -- Add the new provider files in each lab location : `ad//providers/` -- Add the new provider files in each extension location : `extensions//providers/` -- Create the provider templates file in : `template/provider/` +- Add the new provider files in each lab location: `ad//providers/` +- Add the new provider files in each extension location: `extensions//providers/` +- Create the provider templates file in: `template/provider/` -## Provider python class +## Provider architecture -- Create the new provider class in `goad/provider/` +DreadGOAD uses a Go CLI (`dreadgoad`) for all provider orchestration. The provider logic lives in `cli/cmd/` and infrastructure is managed through Terragrunt modules in `infra/`. -- If you use vagrant : - - create the new provider in `goad/provider/vagrant/myprovider.py` +There are two main provider patterns: -```python -from goad.provider.vagrant.vagrant import VagrantProvider -from goad.utils import * +### Vagrant-based providers (virtualbox, vmware) +Vagrant-based providers use a `Vagrantfile` in `ad//providers//` to define and manage VMs. The Vagrantfile specifies: -class MyProviderProvider(VagrantProvider): - provider_name = MYPROVIDER - default_provisioner = PROVISIONING_LOCAL - # define the provisioner allowed - allowed_provisioners = [PROVISIONING_LOCAL, PROVISIONING_RUNNER, PROVISIONING_DOCKER, PROVISIONING_VM] +- VM names, memory, and CPU allocations +- Network interfaces and IP assignments +- Linked clones from base boxes +- WinRM communicator settings - def check(self): - checks = [ - super().check(), - self.command.check_myprovider(), - # self.command.check_vagrant_plugin('myvagrant_plugin', False) - ] - return all(checks) -``` - -- add constants in `goad/utils.py` - -```python -MYPROVIDER = "myprovider" -ALLOWED_PROVIDERS = [AWS, VIRTUALBOX, AZURE, VMWARE, PROXMOX, LUDUS, MYPROVIDER] -``` +Each lab's Vagrantfile is self-contained. The CLI invokes Vagrant commands (`vagrant up`, `vagrant halt`, `vagrant destroy`, etc.) to manage the VM lifecycle. -- add the check in the command class: +Provider directory structure: -```python -# goad/command/cmd.py -def check_myprovider(self): - pass +```text +ad//providers// + Vagrantfile # VM definitions + inventory # Ansible inventory with provider-specific IPs and connection settings ``` -- add the check in the inherited classes : linux.py/ windows.py / wsl.py -- add the new provider in the provider_factory.py file - -- If you use Terraform : - - create the new provider in `goad/provider/terraform/myprovider.py` - -```python -from goad.provider.terraform.terraform import TerraformProvider -from goad.utils import * -from goad.log import Log +### Terraform/Terragrunt-based providers (aws, azure, proxmox) +Terraform-based providers define infrastructure as `.tf` files in `ad//providers//` and use shared Terragrunt modules from `infra/` for deployment orchestration. -class MyProviderProvider(TerraformProvider): +Each lab's provider directory contains: - provider_name = MYPROVIDER - default_provisioner = PROVISIONING_REMOTE - allowed_provisioners = [PROVISIONING_REMOTE] - - def __init__(self, lab_name): - super().__init__(lab_name) - self.resource_group = lab_name - self.jumpbox_setup_script = 'setup_script.sh' - - def check(self): - check = super().check() - myproviders_checks = [ - self.command.mycheck() - ] - return check and all(myproviders_checks) - - def start(self): - # TODO - pass - - def stop(self): - # TODO - pass +```text +ad//providers// + windows.tf # Windows VM resource definitions + linux.tf # Linux VM resource definitions (if needed) + inventory # Ansible inventory with provider-specific connection settings +``` - def status(self): - # TODO - pass +The `infra/` directory contains Terragrunt root configuration (`root.hcl`) and deployment modules (`infra/goad-deployment/`) that handle: - def start_vm(self, vm_name): - # TODO - pass +- VPC/network setup +- Security groups and firewall rules +- VM provisioning from AMIs or cloud images +- Jumpbox/provisioning host setup - def stop_vm(self, vm_name): - # TODO - pass +## Adding a new provider - def destroy_vm(self, vm_name): - # TODO - pass +1. **Create provider directories** in each lab under `ad//providers//` with the appropriate files (Vagrantfile or .tf files) and a provider-specific `inventory` file. - def ssh_jumpbox(self): - # TODO - pass +2. **Update CLI support** in `cli/cmd/` to handle the new provider: + - Add provider name to the allowed providers list in the configuration (`cli/internal/config/`) + - Add any provider-specific commands or flags + - Implement health checks in `doctor.go` for provider prerequisites - def get_jumpbox_ip(self, ip_range=''): - # TODO - pass -``` +3. **For Terragrunt providers**: add or extend modules in `infra/` to support the new cloud platform. -- add constants in `goad/utils.py` -- add the check commands in the cmd.py and the inherited classes : linux.py/ windows.py / wsl.py -- add the new provider in the provider_factory.py file +4. **Add provider templates** in `template/provider/` so new labs can be scaffolded with the correct provider files. -- next adapt the menu if needed in menu.py -- add dependencies if needed in the requirements files, in the dependencies.py files and in the config.py files -- add a provider color if you want in instances.py -- define if is_terraform or is_vagrant in instance.py +5. **Test** with `dreadgoad doctor` to verify prerequisites, then `dreadgoad provision` to run a full deployment. diff --git a/docs/mkdocs/docs/installation/index.md b/docs/mkdocs/docs/installation/index.md index 91c8e5b2..9198e18c 100644 --- a/docs/mkdocs/docs/installation/index.md +++ b/docs/mkdocs/docs/installation/index.md @@ -1,14 +1,13 @@ -# 🚀 Installation +# Installation -In the last version, GOAD use no more bash for the installation/management script. -The goad management script is now written in :simple-python: python to permit more flexibility and cover the needs to create a Windows WSL support. +DreadGOAD uses a self-contained Go CLI binary (`dreadgoad`) for all management operations. The CLI handles infrastructure provisioning, Ansible orchestration, inventory management, and environment validation. The only external dependencies are Ansible (for provisioning) and your chosen provider's tools (e.g., AWS CLI, Terraform). -- First prepare you system for GOAD execution: +- First prepare your system for DreadGOAD execution: - :material-linux: [Linux](linux.md) - :material-microsoft-windows: [Windows](windows.md) -- Installation depend of the provider you use, please follow the appropriate guide : +- Installation depends on the provider you use, please follow the appropriate guide: - :simple-virtualbox: [Install with Virtualbox](../providers/virtualbox.md) - :simple-vmware: [Install with VmWare](../providers/vmware.md) - :simple-proxmox: [Install with Proxmox](../providers/proxmox.md) @@ -18,184 +17,94 @@ The goad management script is now written in :simple-python: python to permit mo ## TLDR - quick install -??? info "TLDR : :material-ubuntu: ubuntu 22.04 quick install" +??? info "TLDR : :simple-amazon: AWS quick install" ```bash - # Install vbox - sudo apt install virtualbox + # Install dreadgoad CLI (download from releases or build from source) + # See https://github.com/dreadnode/DreadGOAD/releases for latest binaries + # Or build from source: + git clone https://github.com/dreadnode/DreadGOAD.git + cd DreadGOAD + cd cli && go build -o dreadgoad . && sudo mv dreadgoad /usr/local/bin/ - # Install vagrant - wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg - echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list - sudo apt update && sudo apt install vagrant + # Initialize configuration + dreadgoad config init - # Install Vagrant plugins - vagrant plugin install vagrant-reload vagrant-vbguest winrm winrm-fs winrm-elevated + # Check dependencies (ansible-core, AWS CLI, Python for Ansible, collections) + dreadgoad doctor - # Add some dependencies - sudo apt install sshpass lftp rsync openssh-client python3.10-venv + # Create a deployment environment + dreadgoad env create dev - git clone https://github.com/Orange-Cyberdefense/GOAD.git - cd GOAD - # verify installation - ./goad.sh -t check -l GOAD -p virtualbox + # Provision infrastructure + dreadgoad infra init && dreadgoad infra apply - # install - ./goad.sh -t install -l GOAD -p virtualbox + # Sync inventory + dreadgoad inventory sync - # launch goad in interactive mode - ./goad.sh - ``` - -## Installation - -- Installation is in three parts : - - Templating : this will create the template to use (needed only for proxmox and ludus) - - Providing : this will instantiate the virtual machines depending on your provider - - Provisioning : it is always made with ansible, it will install all the stuff to create the lab - -- GOAD script cover the providing and provisioning part - -- The install script take multiple parameters: - - `-p` : the provider to use (vmware/virtualbox/proxmox/ludus/azure/aws) - - `-l` : the lab to install (GOAD/GOAD-Light/SCCM/NHA/MINILAB) - - `-m` : the method of installation (local/runner/docker/remote), most of the time don't change it - - `-ip` : the ip range to use - -- The easy way is just launch `./goad.sh` and use help `?`in the interactive prompt + # Run Ansible provisioning + dreadgoad provision + # Validate the lab + dreadgoad validate + ``` -### Python Dependencies - -- Goad in :simple-python: python come with a lot of dependencies as you can see in the `requirements.yml` file on the root of the project. -- If you don't want to run the provisioning from your python venv but only from docker you can use `goad_docker.sh` script instead of `goad.sh`. This will run the ansible with the docker method instead of local or runner. +## Installation Steps -This are the python dependencies used by goad : +- Installation is in three parts: + - Templating: this will create the template to use (needed only for proxmox and ludus) + - Providing: this will instantiate the virtual machines depending on your provider + - Provisioning: it is always made with ansible, it will install all the stuff to create the lab -- Mandatory for :simple-python: goad.py: +- The `dreadgoad` CLI covers the providing and provisioning parts through subcommands: + - `dreadgoad infra init` / `dreadgoad infra apply` - provision infrastructure + - `dreadgoad provision` - run Ansible provisioning + - `dreadgoad validate` - validate the deployment -```bash -rich -psutil -Jinja2 -pyyaml -``` +### Dependencies -- Mandatory for :material-ansible: ansible inside goad (for provisioning method local or runner) : - - python < 3.11 +`dreadgoad` is a self-contained Go binary with no runtime dependencies of its own. However, the following external tools are required depending on your workflow: - ```bash - # Ansible - ansible_runner - ansible-core==2.12.6 - pywinrm - ``` +- **Always required**: `ansible-core`, Python (for Ansible) +- **For AWS provider**: AWS CLI, Terraform/Terragrunt +- **For Azure provider**: Azure CLI, Terraform +- **For Virtualbox/VMware**: Vagrant and appropriate plugins +- **For Proxmox**: Proxmox API access - - python >= 3.11 +Run `dreadgoad doctor` to check that all required dependencies are installed and configured. - ```bash - # Ansible - setuptools - ansible_runner - ansible-core==2.18.0 - pywinrm - ``` - -- Mandatory for :material-microsoft-azure: azure provider : - -```bash -# AZURE -azure-identity -azure-mgmt-compute -azure-mgmt-network -``` +## Configuration files -- Mandatory for :simple-amazon: aws provider : +### dreadgoad.yaml -```bash -# AWS -boto3 -``` +- On first setup, run `dreadgoad config init` to create a configuration file at `~/.config/dreadgoad/dreadgoad.yaml`. +- View the current effective configuration with `dreadgoad config show`. +- Set individual values with `dreadgoad config set `. -- Mandatory for :simple-proxmox: proxmox provider: +The config file uses YAML format and supports the following resolution order: -```bash -# Proxmox -proxmoxer -requests -``` +1. CLI flags (`--env`, `--region`, `--debug`) +2. Environment variables (`DREADGOAD_ENV`, `DREADGOAD_REGION`, etc.) +3. Config file (YAML) +4. Built-in defaults -- You can launch goad without installing all the pip package but for that you will have to disable some dependencies with the `-d` arguments: - -```bash --d vmware : disable vmware provider --d virtualbox : disable virtualbox provider --d azure : disable azure provider --d aws : disable azure provider --d proxmox : disable proxmox provider --d ludus : disable ludus provider --d local : disable local provisioning method (if you use docker only) --d runner : disable ansible runner provisioning method (if you use docker only) --d remote : disable remote provisioning method --d docker : disable docker provisioning method -``` +```yaml +# Active environment (selects into the environments map below) +env: staging -## Configuration files +# AWS region override (default: resolved from inventory) +# region: us-west-2 -### $HOME/.goad/goad.ini - -- On the first launch goad create a global configuration file at : `$HOME/.goad/goad.ini` this file contains some default configuration and some parameters needed by some providers. - -- If you change the `[default]` config it will change the default selection when goad start -- Others configurations are related to specific providers - -```ini -[default] -; lab: goad / goad-light / minilab / nha / sccm -lab = GOAD -; provider : virtualbox / vmware / aws / azure / proxmox -provider = vmware -; provisioner method : local / remote -provisioner = local -; ip_range (3 first ip digits) -ip_range = 192.168.56 - -[aws] -aws_region = eu-west-3 -aws_zone = eu-west-3c - -[azure] -az_location = westeurope - -[proxmox] -pm_api_url = https://192.168.1.1:8006/api2/json -pm_user = infra_as_code@pve -pm_node = GOAD -pm_pool = GOAD -pm_full_clone = false -pm_storage = local -pm_vlan = 10 -pm_network_bridge = vmbr3 -pm_network_model = e1000 - -[proxmox_templates_id] -winserver2019_x64 = 102 -winserver2016_x64 = 103 -winserver2019_x64_utd = 104 -windows10_22h2_x64 = 105 - -[ludus] -; api key must not have % if you have a % in it, change it by a %% -ludus_api_key = change_me -use_impersonation = yes +debug: false +max_retries: 3 # Ansible playbook retry attempts ``` ### Global configuration : globalsettings.ini -- Goad got a global configuration file : `globalsettings.ini` used by the ansible provisioning +- DreadGOAD has a global configuration file: `globalsettings.ini` used by the ansible provisioning - This file is an ansible inventory file. - This file is always added at the end of the ansible inventory file list so you can override values here -- You can change it before running the installation to modify : +- You can change it before running the installation to modify: - keyboard_layouts - proxy configuration - add a route to the vm diff --git a/docs/mkdocs/docs/installation/linux.md b/docs/mkdocs/docs/installation/linux.md index 8978d808..dc67e880 100644 --- a/docs/mkdocs/docs/installation/linux.md +++ b/docs/mkdocs/docs/installation/linux.md @@ -1,7 +1,8 @@ + # :material-linux: Linux -- First you will prepare your host for an hypervisor -- Second you will prepare your python environment +- First you will prepare your host for a hypervisor +- Second you will install the `dreadgoad` CLI ## Prepare your Provider @@ -96,8 +97,8 @@ === "🏟️ Ludus" - - To add GOAD on Ludus please use goad directly on the server. - - By now goad can work only directly on the server and not from a workstation client. + - To add GOAD on Ludus please use dreadgoad directly on the server. + - By now dreadgoad can work only directly on the server and not from a workstation client. - Install Ludus : [https://docs.ludus.cloud/docs/quick-start/install-ludus/](https://docs.ludus.cloud/docs/quick-start/install-ludus/) @@ -106,96 +107,73 @@ - Once your installation is complete on ludus server (debian 12) and your user is created do : ```bash - git clone https://github.com/Orange-Cyberdefense/GOAD.git - cd GOAD - sudo apt install python3.11-venv - ./goad.sh - ...>exit - vim ~/.goad/goad.ini # add the api_key in the config file (keep impersonate to yes and use an admin user) - ./goad.sh -p ludus - ...>set_lab XXX # GOAD/GOAD-Light/NHA/SCCM - ...>install + git clone https://github.com/dreadnode/DreadGOAD.git + cd DreadGOAD + + # Install the CLI (see "Install the CLI" section below) + # Then: + dreadgoad config init + dreadgoad config set ludus.api_key + dreadgoad provision -p ludus ``` -## Prepare your python environment for goad.py +## Install the CLI -=== "Classic" +=== "Download binary" - - To run the Goad installation/management script you will need : **Python version >=3.8** with venv module installed - - - Install the python3-venv corresponding to your python version + Download the latest release for your platform from the [DreadGOAD releases page](https://github.com/dreadnode/DreadGOAD/releases): ```bash - sudo apt install python-venv + # Example for Linux amd64 - adjust version and platform as needed + curl -LO https://github.com/dreadnode/DreadGOAD/releases/latest/download/dreadgoad-linux-amd64 + chmod +x dreadgoad-linux-amd64 + sudo mv dreadgoad-linux-amd64 /usr/local/bin/dreadgoad ``` - - Example: + Verify the installation: ```bash - sudo apt install python3.10-venv - ``` - - - Then you are ready to launch - - ``` - ./goad.sh + dreadgoad --version ``` - - The script will : - - verify python version >=3.8 - - create a venv in `~/.goad/.venv` - - launch python requirements installation - - launch ansible-galaxy collections requirements installation - - start goad.py with the venv created +=== "Build from source" - !!! tip - if you got an error during requirements installation, look at the error and delete `~/.goad/.venv` before try again + Requires Go 1.21+ installed on your system. - !!! tip - if you need to force a python version change the variable `py=python3` to `py=python3.10` for example in the `goad.sh` script + ```bash + git clone https://github.com/dreadnode/DreadGOAD.git + cd DreadGOAD/cli + go build -o dreadgoad . + sudo mv dreadgoad /usr/local/bin/ + ``` -=== "With poetry" + Verify the installation: - - Install python dependencies: - ``` - poetry install + ```bash + dreadgoad --version ``` - - Install ansible-galaxy requirements: - - If python < 3.11 - ``` - poetry run ansible-galaxy collection install -r ansible/requirements.yml - ``` +=== "Go install" - - If python >= 3.11 - ``` - poetry run ansible-galaxy ansible/requirements_311.yml - ``` + If you have Go installed: - - Run goad: - ``` - poetry run python3 goad.py + ```bash + go install github.com/dreadnode/DreadGOAD/cli@latest ``` -=== "Provisioning with docker" + Make sure `$GOPATH/bin` is in your `PATH`. - !!! info - With this method ansible-core will not be installed locally on your venv +## Initial setup - - [x] be sure you have docker installed on your os for the provisioning part (ansible will be run from the container) - - [x] To run the Goad installation/management script you will need : - - Python (version >= 3.8) with venv module installed +Once the CLI is installed, initialize your configuration and verify dependencies: - - Install the python3-venv corresponding to your python version +```bash +# Create default configuration +dreadgoad config init - ```bash - sudo apt install python-venv - ``` - - - Example: - - ```bash - sudo apt install python3.10-venv - ``` +# Check all required dependencies are installed +dreadgoad doctor +``` - - Run goad with `./goad_docker.sh` instead of `./goad.sh` to install the dependencies without the ansible part (local and runner provisioning method will not be available) +!!! tip + `dreadgoad doctor` checks for ansible-core, AWS CLI, Python (for Ansible), and required Ansible collections. Fix any issues it reports before proceeding with installation. diff --git a/docs/mkdocs/docs/installation/windows.md b/docs/mkdocs/docs/installation/windows.md index 5a0cf7b5..78b1eb76 100644 --- a/docs/mkdocs/docs/installation/windows.md +++ b/docs/mkdocs/docs/installation/windows.md @@ -1,9 +1,7 @@ # :material-microsoft-windows: Windows -- First you will prepare your windows host for an hypervisor -- Second you will choose between - - install debian 12 with WSL to run goad install script - - Or prepare your windows host (install with a provisioning machine) +- First you will prepare your windows host for a hypervisor +- Second you will install the `dreadgoad` CLI ## Prepare Windows Host @@ -75,21 +73,21 @@ === ":simple-amazon: Aws" - Nothing to prepare on windows host, install and prepare wsl and next follow linux install from your wsl console : [see aws linux install](linux.md/#__tabbed_1_4) + Nothing to prepare on windows host, install and prepare WSL and next follow linux install from your WSL console : [see aws linux install](linux.md/#__tabbed_1_4) === ":material-microsoft-azure: Azure" - Nothing to prepare on windows host, install and prepare wsl and next linux install from your wsl console [see azure linux install](linux.md/#__tabbed_1_3) + Nothing to prepare on windows host, install and prepare WSL and next follow linux install from your WSL console [see azure linux install](linux.md/#__tabbed_1_3) === ":simple-proxmox: Promox" - Not supported, you will have to create a provisioning machine on your proxmox and run goad from then ([see proxmox linux install](linux.md/#__tabbed_1_5)) + Not supported, you will have to create a provisioning machine on your proxmox and run dreadgoad from there ([see proxmox linux install](linux.md/#__tabbed_1_5)) === "🏟️ Ludus" Not supported, you will have to act from your ludus server ([see ludus linux install](linux.md/#__tabbed_1_6)) -## :simple-python: Prepare python environment +## Install the CLI === "With WSL" - Now your host environment is ready for virtual machine creation. Now we will install WSL to run the goad installation script. + Now your host environment is ready for virtual machine creation. Install WSL to run the `dreadgoad` CLI and Ansible. !!! info "wsl version" New Linux installations, installed using the wsl --install command, will be set to WSL 2 by default. @@ -106,48 +104,57 @@ - First install wsl on your environment [https://learn.microsoft.com/en-us/windows/wsl/install](https://learn.microsoft.com/en-us/windows/wsl/install) - Next go to the microsoft store and install debian (debian12) - ### Prepare WSL distribution + ### Install dreadgoad in WSL + - Open debian console then : - - Verify you are using python version >= 3.8 + - Install Ansible dependencies ```bash - python3 --version + sudo apt update + sudo apt install python3 python3-pip ansible-core sshpass ``` - - Install python packages + - Install the `dreadgoad` CLI ```bash - sudo apt update - sudo apt install python3 python3-pip python3-venv libpython3-dev + # Download the latest Linux binary + curl -LO https://github.com/dreadnode/DreadGOAD/releases/latest/download/dreadgoad-linux-amd64 + chmod +x dreadgoad-linux-amd64 + sudo mv dreadgoad-linux-amd64 /usr/local/bin/dreadgoad ``` - - Next you can clone and run goad + - Or build from source + ```bash + cd /mnt/c/whatever_folder_you_want + git clone https://github.com/dreadnode/DreadGOAD.git + cd DreadGOAD/cli + go build -o dreadgoad . + sudo mv dreadgoad /usr/local/bin/ + ``` + - Initialize and verify ```bash - cd /mnt/c/whatever_folder_you_want - git clone https://github.com/Orange-Cyberdefense/GOAD.git - cd GOAD - ./goad.sh + dreadgoad config init + dreadgoad doctor ``` -=== "With Python on windows host" +=== "Direct Windows Install" !!! info "For vmware or virtualbox only" - This mode doesn't need WSL but it is only if you plan to install goad locally on vmware or virtualbox + This mode runs `dreadgoad` natively on Windows. Ansible provisioning still requires WSL or a remote provisioning machine. - Prerequisites: - - :simple-python: [python](https://www.python.org/downloads/windows/) on your windows (tested ok with python 3.10) - :simple-git: [git](https://git-scm.com/downloads/win) - - Checkout GOAD : + - Download the Windows binary from the [DreadGOAD releases page](https://github.com/dreadnode/DreadGOAD/releases): ``` - git clone https://github.com/Orange-Cyberdefense/GOAD - cd GOAD/ + curl -LO https://github.com/dreadnode/DreadGOAD/releases/latest/download/dreadgoad-windows-amd64.exe + move dreadgoad-windows-amd64.exe C:\Users\%USERNAME%\bin\dreadgoad.exe ``` - - Install python dependencies (choose the noansible file) : - ``` - pip install -r noansible_requirements.yml - ``` - - Launch goad with vm provisioning method : + + - Add `C:\Users\%USERNAME%\bin` to your PATH if not already present. + + - Initialize and verify: ``` - py goad.py -m vm + dreadgoad config init + dreadgoad doctor ``` diff --git a/docs/mkdocs/docs/labs/NHA.md b/docs/mkdocs/docs/labs/NHA.md index 69ac73b1..84019656 100644 --- a/docs/mkdocs/docs/labs/NHA.md +++ b/docs/mkdocs/docs/labs/NHA.md @@ -13,32 +13,20 @@ - Install : ```bash -./goad.sh -t install -l NHA -p virtualbox -``` - -or - -```bash -./goad.sh -> set_lab NHA -> set_provider -> set_iprange 192.168.56 # select the one you want and you can skip this with ludus -> install +dreadgoad provision ``` - Once install finish disable vagrant user to avoid using it : ```bash -./goad.sh -> load -> disable_vagrant +dreadgoad provision --plays disable_vagrant.yml ``` - Now do a reboot of all the machine to avoid unintended secrets stored : ```bash -> stop -> start +dreadgoad lab stop +dreadgoad lab start ``` And you are ready to play ! :) @@ -46,8 +34,7 @@ And you are ready to play ! :) - If you need to re-enable vagrant ```bash -> load -> enable_vagrant +dreadgoad provision --plays enable_vagrant.yml ``` - If you want to create a write up of the chall, no problem, have fun. Please ping me on X (@M4yFly) or Discord, i will be happy to read it :) diff --git a/docs/mkdocs/docs/labs/SCCM.md b/docs/mkdocs/docs/labs/SCCM.md index 6984b1cd..cd2f9036 100644 --- a/docs/mkdocs/docs/labs/SCCM.md +++ b/docs/mkdocs/docs/labs/SCCM.md @@ -38,12 +38,12 @@ All vms got defender activated 3) on the provisioning computer : ```bash -./goad.sh -t check -l SCCM -p proxmox -m local -./goad.sh -t install -l SCCM -p proxmox -m local +dreadgoad doctor +dreadgoad provision ``` 4) if something goes wrong (restart of the vms during install, etc...), you can rerun only ansible with -a ```bash -./goad.sh -t install -l SCCM -p proxmox -m local -a +dreadgoad provision --from ``` diff --git a/docs/mkdocs/docs/providers/aws-ami-workflow.md b/docs/mkdocs/docs/providers/aws-ami-workflow.md index 3ff0c1bd..29444af0 100644 --- a/docs/mkdocs/docs/providers/aws-ami-workflow.md +++ b/docs/mkdocs/docs/providers/aws-ami-workflow.md @@ -68,13 +68,16 @@ For full details on all config options, see [CLI configuration](../../cli.md). ### Choosing an environment -The repo ships with a `staging` directory tree. To use a different environment (e.g., `dev`), duplicate the directory structure: +The repo ships with a `staging` directory tree. To create a new environment (e.g., `dev`), use the CLI: ```bash -cp -r infra/goad-deployment/staging infra/goad-deployment/dev +dreadgoad env create dev ``` -Then edit `dev/env.hcl` to set `env = "dev"` and adjust the account ID, VPC CIDR, or other settings as needed. Each environment gets its own Terraform state, so you can run multiple labs in parallel. +This scaffolds the full Terragrunt tree, pulling the VPC CIDR from your +`dreadgoad.yaml` config (`environments.dev.vpc_cidr`). You can also pass +`--vpc-cidr` explicitly. Each environment gets its own Terraform state, +so you can run multiple labs in parallel. Throughout this guide, examples use `staging` and `us-west-1` to match the defaults. Replace with your chosen env and region as needed. @@ -87,43 +90,39 @@ DreadGOAD provides four warpgate templates under `warpgate-templates/`: | `goad-dc-base` | DC01, DC02 | Windows Server 2019 | ~25 min/host | | `goad-dc-base-2016` | DC03 | Windows Server 2016 | ~25 min/host | | `goad-mssql-base` | SRV02 | Windows Server 2019 | ~48 min/host | -| `goad-member-base-2016` | SRV03 (optional) | Windows Server 2016 | ~20 min/host | +| `goad-mssql-base-2016` | SRV03 (optional) | Windows Server 2016 | ~20 min/host | ### Build all AMIs ```bash -# Domain Controllers (Windows 2019) -warpgate build goad-dc-base --target ami - -# Domain Controller (Windows 2016, for DC03/meereen) -warpgate build goad-dc-base-2016 --target ami +# Build all templates in parallel +dreadgoad ami build --all + +# Or build individually +dreadgoad ami build goad-dc-base +dreadgoad ami build goad-dc-base-2016 +dreadgoad ami build goad-mssql-base +dreadgoad ami build goad-mssql-base-2016 +``` -# Member Server with MSSQL (Windows 2019, for SRV02/castelblack) -warpgate build goad-mssql-base --target ami +To build for a specific region: -# Member Server (Windows 2016, for SRV03/braavos -- optional) -warpgate build goad-member-base-2016 --target ami +```bash +dreadgoad ami build goad-dc-base --region us-west-1 ``` -To build for a specific region: +You can also call `warpgate` directly for more control: ```bash warpgate build goad-dc-base --target ami --region us-west-1 ``` -### Record the AMI IDs - -Each build outputs an AMI ID (e.g., `ami-0abc1234def56789`). Record these -- you'll need them in the next step: +### AMI resolution is automatic -| Template | AMI ID | Used By | -|----------|--------|---------| -| `goad-dc-base` | `ami-xxxxxxxxx` | DC01 (kingslanding), DC02 (winterfell) | -| `goad-dc-base-2016` | `ami-xxxxxxxxx` | DC03 (meereen) | -| `goad-mssql-base` | `ami-xxxxxxxxx` | SRV02 (castelblack) | -| `goad-member-base-2016` | `ami-xxxxxxxxx` | SRV03 (braavos) -- optional | +Each warpgate template tags its output AMI with `Name: ` (e.g., `Name: goad-dc-base`). The terragrunt host configurations filter by this tag with `most_recent = true`, so they always pick up the latest build automatically. No manual AMI ID tracking is needed. !!! note "SRV03 (braavos)" - SRV03 runs Windows Server 2016 as a member server. You can use the dedicated `goad-member-base-2016` AMI, or alternatively `goad-dc-base-2016` (the extra AD DS role won't interfere) or a vanilla Windows Server 2016 AMI. + SRV03 runs Windows Server 2016 as a member server. You can use the dedicated `goad-mssql-base-2016` AMI, or alternatively `goad-dc-base-2016` (the extra AD DS role won't interfere) or a vanilla Windows Server 2016 AMI. ### What's pre-baked vs. runtime @@ -164,69 +163,29 @@ locals { } ``` -### Insert AMI IDs into host configurations - -Each host has a `terragrunt.hcl` under `infra/goad-deployment/staging/us-west-1/goad/`. Update the `additional_windows_ami_filters` block in each: +### How AMI selection works -**DC01 and DC02** (`dc01/terragrunt.hcl`, `dc02/terragrunt.hcl`) -- use `goad-dc-base` AMI: +Each host's `terragrunt.hcl` already contains the correct AMI filter. For example, `dc01/terragrunt.hcl`: ```hcl -additional_windows_ami_filters = [ - { - name = "image-id" - values = ["ami-xxxxxxxxx"] # goad-dc-base AMI ID - } -] - -windows_os = "Windows_Server" -windows_os_version = "2019-English-Full-Base" windows_ami_owners = ["self"] -``` -**DC03** (`dc03/terragrunt.hcl`) -- use `goad-dc-base-2016` AMI: - -```hcl additional_windows_ami_filters = [ { - name = "image-id" - values = ["ami-xxxxxxxxx"] # goad-dc-base-2016 AMI ID + name = "tag:Name" + values = ["goad-dc-base"] } ] - -windows_os = "Windows_Server" -windows_os_version = "2016-English-Full-Base" -windows_ami_owners = ["self"] ``` -**SRV02** (`srv02/terragrunt.hcl`) -- use `goad-mssql-base` AMI: - -```hcl -additional_windows_ami_filters = [ - { - name = "image-id" - values = ["ami-xxxxxxxxx"] # goad-mssql-base AMI ID - } -] +The instance-factory module uses `most_recent = true`, so after building a new AMI with `dreadgoad ami build`, the next `dreadgoad infra apply` automatically picks it up. -windows_os = "Windows_Server" -windows_os_version = "2019-English-Full-Base" -windows_ami_owners = ["self"] -``` - -**SRV03** (`srv03/terragrunt.hcl`) -- use `goad-member-base-2016` AMI (optional): - -```hcl -additional_windows_ami_filters = [ - { - name = "image-id" - values = ["ami-xxxxxxxxx"] # goad-member-base-2016 AMI ID - } -] - -windows_os = "Windows_Server" -windows_os_version = "2016-English-Full-Base" -windows_ami_owners = ["self"] -``` +| Template | Hosts | Filter Tag | +|----------|-------|------------| +| `goad-dc-base` | DC01, DC02 | `tag:Name = goad-dc-base` | +| `goad-dc-base-2016` | DC03 | `tag:Name = goad-dc-base-2016` | +| `goad-mssql-base` | SRV02 | `tag:Name = goad-mssql-base` | +| `goad-mssql-base-2016` | SRV03 | `tag:Name = goad-mssql-base-2016` | ### Set admin passwords @@ -244,6 +203,13 @@ export TF_VAR_goad_srv03_password="YourSecurePassword5" ### Initialize and apply +```bash +dreadgoad infra init --env staging --region us-west-1 +dreadgoad infra apply --env staging --region us-west-1 +``` + +Or with raw Terragrunt for more control: + ```bash cd infra/goad-deployment/staging/us-west-1 @@ -260,14 +226,20 @@ terragrunt run-all apply ``` !!! tip - `terragrunt run-all` deploys DC01-DC03, SRV02, and SRV03 in parallel. The dependency on the network module is resolved automatically. + `terragrunt run-all` (and `dreadgoad infra apply`) deploys DC01-DC03, SRV02, and SRV03 in parallel. The dependency on the network module is resolved automatically. ### Verify instances All instances use SSM for management -- no SSH keys or open ports required: ```bash -# Check instance status via AWS CLI +dreadgoad health-check --env staging --region us-west-1 +``` + +Or with the AWS CLI directly: + +```bash +# Check instance status aws ec2 describe-instances \ --filters "Name=tag:Project,Values=DreadGOAD" \ --query "Reservations[].Instances[].[Tags[?Key=='Name'].Value|[0],State.Name,InstanceId]" \ @@ -277,12 +249,6 @@ aws ec2 describe-instances \ aws ssm start-session --target ``` -Or use the DreadGOAD CLI: - -```bash -dreadgoad health-check --env staging --region us-west-1 -``` - ## Step 4: Provision with Ansible Once all instances are running, provision the Active Directory environment: @@ -352,7 +318,7 @@ dreadgoad validate --env staging --region us-west-1 | winterfell | DC02 | dc02 | north.sevenkingdoms.local | 2019 | goad-dc-base | | meereen | DC03 | dc03 | essos.local | 2016 | goad-dc-base-2016 | | castelblack | SRV02 | srv02 | north.sevenkingdoms.local | 2019 | goad-mssql-base | -| braavos | SRV03 | srv03 | essos.local | 2016 | goad-member-base-2016 (optional) | +| braavos | SRV03 | srv03 | essos.local | 2016 | goad-mssql-base-2016 (optional) | ## Rebuilding AMIs diff --git a/docs/mkdocs/docs/providers/aws.md b/docs/mkdocs/docs/providers/aws.md index fbba0918..bfbeadc3 100644 --- a/docs/mkdocs/docs/providers/aws.md +++ b/docs/mkdocs/docs/providers/aws.md @@ -30,7 +30,7 @@ You need to configure AWS cli. Use a key with enough privileges on the tenant. aws configure ``` -- Create an aws access key and secret for goad usage +- Create an AWS access key and secret for DreadGOAD usage - Go to IAM > User > your user > Security credentials - Click the Create access key button - Create a group "[goad]" in credentials file ~/.aws/credentials @@ -46,16 +46,16 @@ aws configure !!! warning "credentials in plain text" Storing credentials in plain text is always a bad idea, but aws cli work like that be sure to restrain the right access to this file -## Goad configuration +## DreadGOAD configuration -- The goad configuration file as some options for aws: +- Initialize the configuration file with `dreadgoad config init` +- AWS-specific settings are configured in `dreadgoad.yaml`: -```ini -# ~/.goad/goad.ini -... -[aws] -aws_region = eu-west-3 -aws_zone = eu-west-3c +```yaml +# dreadgoad.yaml +aws: + region: eu-west-3 + zone: eu-west-3c ``` - If you want to use a different region and zone you can modify it. @@ -65,26 +65,24 @@ aws_zone = eu-west-3c ```bash # check prerequisites -./goad.sh -t check -l GOAD -p aws -# Install -./goad.sh -t install -l GOAD -p aws -``` - -or from the interactive console : - -```bash -GOAD/aws/remote/192.168.56.X > install +dreadgoad doctor +# Create cloud infrastructure +dreadgoad infra apply +# Sync inventory +dreadgoad inventory sync +# Provision the lab +dreadgoad provision ``` ## start/stop/status -- You can see the status of the lab with the command `status` -- You can also start and stop the lab with the command `start` and `stop` +- You can see the status of the lab with `dreadgoad lab status` +- You can also start and stop the lab with `dreadgoad lab start` and `dreadgoad lab stop` ## VMs ami -- The vm used for goad are defined in the lab terraform file : `ad//providers/aws/windows.tf` +- The VMs used for DreadGOAD are defined in the lab terraform file: `ad//providers/aws/windows.tf` - This file is containing information about each vm in use ```hcl @@ -101,28 +99,22 @@ GOAD/aws/remote/192.168.56.X > install ## How it works ? -- On the installation goad script will create a folder into `goad/workspaces/` -- This folder will contain the terraform scripts and some of the ansible inventories -- Goad will create the cloud infrastructure with terraform. -- The lab is created (not provisioned yet) and a "jumpbox" vm is also created +- The DreadGOAD CLI uses Terragrunt/Terraform to create the cloud infrastructure (`dreadgoad infra apply`) +- The lab is created (not provisioned yet) and a "jumpbox" VM is also created - Next the needed sources will be pushed to the jumpbox using `ssh` and `rsync` -- The jumpbox ssh_key is stored on `goad/workspaces//ssh_keys` -- The jumpbox is prepared to run ansible -- The provisioning is launch with ssh remotely on the jumpbox +- The jumpbox is prepared to run Ansible +- The provisioning is launched with SSH remotely on the jumpbox ## Install step by step ```bash -GOAD/aws/remote/192.168.56.X > create_empty # create empty instance -GOAD/aws/remote/192.168.56.X > load -GOAD/aws/remote/192.168.56.X () > provide # play terraform -GOAD/aws/remote/192.168.56.X () > sync_source_jumpbox # sync jumpbox source -GOAD/aws/remote/192.168.56.X () > prepare_jumpbox # install dependencies on jumpbox -GOAD/aws/remote/192.168.56.X () > provision_lab # run ansible +dreadgoad doctor # check prerequisites +dreadgoad infra apply # create cloud infrastructure with Terragrunt/Terraform +dreadgoad inventory sync # sync inventory and sources to jumpbox +dreadgoad provision # run Ansible provisioning via jumpbox ``` ## Tips -- To connect to the jumpbox VM you can use `ssh_jumpbox` in the goad interactive console -- To setup a socks proxy you can use `ssh_jumpbox_proxy ` in the goad interactive console -- All aws elements are tagged with `-` +- To connect to a host via SSM you can use `dreadgoad ssm connect ` +- All AWS elements are tagged with `-` diff --git a/docs/mkdocs/docs/providers/azure.md b/docs/mkdocs/docs/providers/azure.md index d057673c..ad71de1f 100644 --- a/docs/mkdocs/docs/providers/azure.md +++ b/docs/mkdocs/docs/providers/azure.md @@ -29,15 +29,15 @@ You need to login to Azure with the CLI. az login ``` -## Goad configuration +## DreadGOAD configuration -- The goad configuration file as some options for azure: +- Initialize the configuration file with `dreadgoad config init` +- Azure-specific settings are configured in `dreadgoad.yaml`: -```ini -# ~/.goad/goad.ini -... -[azure] -az_location = westeurope +```yaml +# dreadgoad.yaml +azure: + location: westeurope ``` - If you want to use a different location you can modify it. @@ -47,28 +47,26 @@ az_location = westeurope ```bash # check prerequisites -./goad.sh -t check -l GOAD -p azure -# Install -./goad.sh -t install -l GOAD -p azure -``` - -or from the interactive console : - -```bash -GOAD/azure/remote/192.168.56.X > install +dreadgoad doctor +# Create cloud infrastructure +dreadgoad infra apply +# Sync inventory +dreadgoad inventory sync +# Provision the lab +dreadgoad provision ``` ## start/stop/status -- You can see the status of the lab with the command `status` -- You can also start and stop the lab with the command `start` and `stop` +- You can see the status of the lab with `dreadgoad lab status` +- You can also start and stop the lab with `dreadgoad lab start` and `dreadgoad lab stop` !!! info The command `stop` use deallocate, it take a long time to run but it is not only stopping the vms, it will deallocate them. By doing that, you will stop paying from them (but you still paying storage) and can save some money. ## VMs sku -- The vm used for goad are defined in the lab terraform file : `ad//providers/azure/windows.tf` +- The VMs used for DreadGOAD are defined in the lab terraform file: `ad//providers/azure/windows.tf` - This file is containing information about each vm in use ```hcl @@ -86,30 +84,24 @@ GOAD/azure/remote/192.168.56.X > install ## How it works ? -- On the installation goad script will create a folder into `goad/workspaces/` -- This folder will contain the terraform scripts and some of the ansible inventories -- Goad will create the cloud infrastructure with terraform. -- The lab is created (not provisioned yet) and a "jumpbox" vm is also created +- The DreadGOAD CLI uses Terragrunt/Terraform to create the cloud infrastructure (`dreadgoad infra apply`) +- The lab is created (not provisioned yet) and a "jumpbox" VM is also created - Next the needed sources will be pushed to the jumpbox using `ssh` and `rsync` -- The jumpbox ssh_key is stored on `goad/workspaces//ssh_keys` -- The jumpbox is prepared to run ansible -- The provisioning is launch with ssh remotely on the jumpbox +- The jumpbox is prepared to run Ansible +- The provisioning is launched with SSH remotely on the jumpbox ## Install step by step ```bash -GOAD/azure/remote/192.168.56.X > create_empty # create empty instance -GOAD/azure/remote/192.168.56.X > load -GOAD/azure/remote/192.168.56.X () > provide # play terraform -GOAD/azure/remote/192.168.56.X () > sync_source_jumpbox # sync jumpbox source -GOAD/azure/remote/192.168.56.X () > prepare_jumpbox # install dependencies on jumpbox -GOAD/azure/remote/192.168.56.X () > provision_lab # run ansible +dreadgoad doctor # check prerequisites +dreadgoad infra apply # create cloud infrastructure with Terragrunt/Terraform +dreadgoad inventory sync # sync inventory and sources to jumpbox +dreadgoad provision # run Ansible provisioning via jumpbox ``` ## Tips -- To connect to the jumpbox VM you can use `ssh_jumpbox` in the goad interactive console -- To setup a socks proxy you can use `ssh_jumpbox_proxy ` in the goad interactive console +- To connect to a host you can use `dreadgoad ssm connect ` - If the command `destroy` or `delete` fails, you can delete the resource group using the CLI diff --git a/docs/mkdocs/docs/providers/ludus.md b/docs/mkdocs/docs/providers/ludus.md index 4cb8c1c0..68587836 100644 --- a/docs/mkdocs/docs/providers/ludus.md +++ b/docs/mkdocs/docs/providers/ludus.md @@ -9,8 +9,8 @@ !!! warning "Install on ludus server only" - To add GOAD on Ludus please use goad directly on the server. - By now goad can work only directly on the server and not from a workstation client. + To add DreadGOAD on Ludus please use the CLI directly on the server. + By now DreadGOAD can work only directly on the server and not from a workstation client. - Install Ludus : [https://docs.ludus.cloud/docs/quick-start/install-ludus/](https://docs.ludus.cloud/docs/quick-start/install-ludus/) @@ -18,45 +18,39 @@ - Once your installation is complete on ludus server (debian 12) and your user is created do : ```bash -git clone https://github.com/Orange-Cyberdefense/GOAD.git -cd GOAD -sudo apt install python3.11-venv # because by default ludus use debian 12 with python3.11 +git clone https://github.com/dreadnode/DreadGOAD.git +cd DreadGOAD export LUDUS_API_KEY='myapikey' # put your api key here -./goad.sh -p ludus -GOAD/ludus/local > check -GOAD/ludus/local > set_lab XXX # GOAD/GOAD-Light/NHA/SCCM -GOAD/ludus/local > install +dreadgoad doctor # check prerequisites +dreadgoad provision # provision the lab ``` -And goad launch the installation ;) +## DreadGOAD configuration -## Goad configuration +- If you don't want to do the export LUDUS_API_KEY before running the CLI you can also add the API key in the configuration file +- Initialize the configuration file with `dreadgoad config init` +- Ludus-specific settings are configured in `dreadgoad.yaml`: -- If you don't want to do the export LUDUS_API_KEY before using goad you can also add the api_key in the goad.ini configuration file -- The goad configuration file as some options for ludus: - -```ini -# ~/.goad/goad.ini -... -[ludus] -ludus_api_key = changeme -use_impersonation = yes +```yaml +# dreadgoad.yaml +ludus: + api_key: changeme + use_impersonation: true ``` -- change the api_key with the one of your admin user +- Change the api_key with the one of your admin user ## Install ```bash -./goad.sh -p ludus -GOAD/ludus/local > set_lab XXX # GOAD/GOAD-Light/NHA/SCCM -GOAD/ludus/local > install +dreadgoad doctor # check prerequisites +dreadgoad provision # provision the lab (GOAD/GOAD-Light/NHA/SCCM) ``` - The installation will create a new simple_user to generate the pool we will call him "lab_user" the id of this user will be `lab_name<6alphanumeric_digit>` - Next this "lab_user" will be impersonate to launch all the ludus deployment command - At the end the "lab_user" will share access to our user -- This way we can manage multiple lab instance with goad on the same ludus server. +- This way we can manage multiple lab instances with DreadGOAD on the same ludus server. !!! info On ludus the config ip_range is not used and is ignored. The ips will be setup automatically during the lab installation diff --git a/docs/mkdocs/docs/providers/proxmox.md b/docs/mkdocs/docs/providers/proxmox.md index b87c1afa..ca95f8ef 100644 --- a/docs/mkdocs/docs/providers/proxmox.md +++ b/docs/mkdocs/docs/providers/proxmox.md @@ -23,7 +23,11 @@ ```bash # check prerequisites -./goad.sh -t check -l GOAD -p proxmox -# Install -./goad.sh -t install -l GOAD -p proxmox +dreadgoad doctor +# Create infrastructure +dreadgoad infra apply +# Sync inventory +dreadgoad inventory sync +# Provision the lab +dreadgoad provision ``` diff --git a/docs/mkdocs/docs/providers/virtualbox.md b/docs/mkdocs/docs/providers/virtualbox.md index 928dcaa5..c806ce5d 100644 --- a/docs/mkdocs/docs/providers/virtualbox.md +++ b/docs/mkdocs/docs/providers/virtualbox.md @@ -19,16 +19,14 @@ - winrm-elevated - Provisioning - - Python3 >=3.8 - - goad requirements - - ansible-galaxy goad requirements + - Ansible (installed via the DreadGOAD CLI prerequisites) + - ansible-galaxy requirements (`ansible-galaxy collection install -r ansible/requirements.yml`) ## Check dependencies ```bash -./goad.sh -p virtualbox -GOAD/virtualbox/local/192.168.56.X > check +dreadgoad doctor ``` ![vbox_check_example.png](./../img/vbox_check_example.png) @@ -41,19 +39,10 @@ GOAD/virtualbox/local/192.168.56.X > check ## Install -- To install run the goad script and launch install or use the goad script arguments +- Once Vagrant has created the VMs, provision the lab using the DreadGOAD CLI: ```bash -./goad.sh -p virtualbox -GOAD/virtualbox/local/192.168.56.X > set_lab # here choose the lab you want (GOAD/GOAD-Light/NHA/SCCM) -GOAD/virtualbox/local/192.168.56.X > set_ip_range # here choose the ip range you want to use ex: 192.168.56 -GOAD/virtualbox/local/192.168.56.X > install +dreadgoad provision ``` ![vbox_install](./../img/vbox_install.png) - -- or all in command line with arguments - -```bash -./goad.sh -t install -p virtualbox -l -ip -``` diff --git a/docs/mkdocs/docs/providers/vmware.md b/docs/mkdocs/docs/providers/vmware.md index b1f839ee..391885f3 100644 --- a/docs/mkdocs/docs/providers/vmware.md +++ b/docs/mkdocs/docs/providers/vmware.md @@ -23,16 +23,14 @@ - winrm-elevated - Provisioning - - Python3 >=3.8 - - goad requirements - - ansible-galaxy goad requirements + - Ansible (installed via the DreadGOAD CLI prerequisites) + - ansible-galaxy requirements (`ansible-galaxy collection install -r ansible/requirements.yml`) ## check dependencies ```bash -./goad.sh -p vmware -GOAD/vmware/local/192.168.56.X > check +dreadgoad doctor ``` ![vmware_check.png](./../img/vmware_check.png) @@ -45,19 +43,10 @@ GOAD/vmware/local/192.168.56.X > check ## Install -- To install run the goad script and launch install or use the goad script arguments +- Once Vagrant has created the VMs, provision the lab using the DreadGOAD CLI: ```bash -./goad.sh -p vmware -GOAD/vmware/local/192.168.56.X > set_lab # here choose the lab you want (GOAD/GOAD-Light/NHA/SCCM) -GOAD/vmware/local/192.168.56.X > set_ip_range # here choose the ip range you want to use ex: 192.168.56 (only the first three digits) -GOAD/vmware/local/192.168.56.X > install +dreadgoad provision ``` ![vmware_install](./../img/vmware_install.png) - -- or all in command line with arguments - -```bash -./goad.sh -t install -p vmware -l -ip -``` diff --git a/docs/mkdocs/docs/providers/vmware_esxi.md b/docs/mkdocs/docs/providers/vmware_esxi.md index ac5b5640..5be8810c 100644 --- a/docs/mkdocs/docs/providers/vmware_esxi.md +++ b/docs/mkdocs/docs/providers/vmware_esxi.md @@ -24,19 +24,14 @@ - winrm-elevated - ovftool (https://developer.broadcom.com/tools/open-virtualization-format-ovf-tool/latest) -- Provisioning with python - - Python3 (>=3.8) - - [ansible-core==2.12.6](https://docs.ansible.com/ansible/latest/index.html) - - pywinrm - -- Or provisioning With Docker - - [Docker](https://www.docker.com/) +- Provisioning + - Ansible (installed via the DreadGOAD CLI prerequisites) + - ansible-galaxy requirements (`ansible-galaxy collection install -r ansible/requirements.yml`) ## check dependencies ```bash -./goad.sh -p vmware_esxi -GOAD/vmware_esxi/local/192.168.56.X > check +dreadgoad doctor ``` ![esxi_check.png](./../img/esxi_check.png) @@ -49,19 +44,10 @@ GOAD/vmware_esxi/local/192.168.56.X > check ## Install -- To install run the goad script and launch install or use the goad script arguments +- Once Vagrant has created the VMs, provision the lab using the DreadGOAD CLI: ```bash -./goad.sh -p vmware_esxi -GOAD/vmware_esxi/local/192.168.56.X > set_lab # here choose the lab you want (GOAD/GOAD-Light/NHA/SCCM) -GOAD/vmware_esxi/local/192.168.56.X > set_ip_range # here choose the ip range you want to use ex: 192.168.56 (only the first three digits) -GOAD/vmware_esxi/local/192.168.56.X > install +dreadgoad provision ``` ![esxi_install](./../img/esxi_install.png) - -- or all in command line with arguments - -```bash -./goad.sh -t install -p vmware_esxi -l -ip -``` diff --git a/docs/mkdocs/docs/providers/vmware_windows.md b/docs/mkdocs/docs/providers/vmware_windows.md index e2ce0650..85465447 100644 --- a/docs/mkdocs/docs/providers/vmware_windows.md +++ b/docs/mkdocs/docs/providers/vmware_windows.md @@ -25,9 +25,7 @@ Use VMware Workstation's Virtual Network Editor to configure the host-only netwo Inside the controller VM, install dependencies: ```bash -pip install --upgrade pip -pip install ansible-core pywinrm -sudo apt install sshpass lftp rsync openssh-client +sudo apt install sshpass lftp rsync openssh-client ansible # Install Ansible requirements cd DreadGOAD @@ -52,11 +50,6 @@ Once the VMs are running, switch to your Kali/Ubuntu controller VM and run the p ```bash cd DreadGOAD -# Using the CLI -./cli/dreadgoad provision - -# Or using the legacy script -./goad.sh -t install -l GOAD -p vmware -m local -a +# Using the DreadGOAD CLI +dreadgoad provision ``` - -The `-a` flag skips interactive prompts and runs with defaults. diff --git a/docs/mkdocs/docs/provisioning.md b/docs/mkdocs/docs/provisioning.md index 9f6e5bc0..4f52be9c 100644 --- a/docs/mkdocs/docs/provisioning.md +++ b/docs/mkdocs/docs/provisioning.md @@ -34,7 +34,7 @@ Ansible work with inventories. Inventories files contains all the hosts declarat - The lab inventory file (`ad//data/inventory`) is not modified/moved and contain all the main variables and hosts association, this file stay as this and is not modified. It contains the lab building logic. -- The provider inventory file (`ad//provider//inventory`) is modified with the settings and copied into the workspace folder (`workspace//inventory`) , this file contains variable specific to the provider and the host ip declaration +- The provider inventory file (`ad//providers//inventory`) is modified with the settings and copied into the workspace folder (`workspace//inventory`) , this file contains variable specific to the provider and the host ip declaration - The extension(s) inventory file(s) (`extensions//inventory`) is modified with the settings and copied into the workspace folder (`workspace//inventory_`) , this file contains variable specific to the extension and the extension host ip declaration diff --git a/docs/mkdocs/docs/references.md b/docs/mkdocs/docs/references.md index ec36e840..c7628f70 100644 --- a/docs/mkdocs/docs/references.md +++ b/docs/mkdocs/docs/references.md @@ -1,6 +1,6 @@ # References -🚧 TODO TO BE COMPLETED +Blog posts, writeups, podcasts, and videos covering DreadGOAD (GOAD) labs and related topics. - Mayfly's blog : - [GOAD writeups](https://mayfly277.github.io/categories/goad/) diff --git a/docs/mkdocs/docs/troobleshoot.md b/docs/mkdocs/docs/troobleshoot.md index a1ef32f1..4cf91ee2 100644 --- a/docs/mkdocs/docs/troobleshoot.md +++ b/docs/mkdocs/docs/troobleshoot.md @@ -1,11 +1,9 @@ # troubleshoot !!! tip -In most case if you get errors during install, don't think. -Select the failed instance ̀`load ` and just replay the install with `provision_lab` to relaunch all or `provision_lab_from ` if you know the last failed playbook -(most of the errors which could came up are due to windows latency during installation, wait few minutes and replay the install) - -🚧 TODO refresh me with new goad version :) + In most cases if you get errors during install, don't panic. + Run `dreadgoad provision` to relaunch all playbooks, or `dreadgoad provision --from ` to resume from a specific playbook. + Most errors are due to Windows latency during installation -- wait a few minutes and replay the install. ## vagrant up - WinRM - digest initialization failed : Initialization Error diff --git a/docs/mkdocs/docs/usage/goad_args.md b/docs/mkdocs/docs/usage/goad_args.md index 9839e89e..abf6c86e 100644 --- a/docs/mkdocs/docs/usage/goad_args.md +++ b/docs/mkdocs/docs/usage/goad_args.md @@ -1,32 +1,118 @@ -# Argument mode +# CLI Commands -- Launch goad.py script (or goad.sh wrapper) with arguments +The `dreadgoad` CLI uses a structured command and subcommand pattern. Every operation is a top-level command or a subcommand grouped under a resource. + +## Command structure ```text -usage: goad.py [-h] [-t TASK] [-l LAB] [-p PROVIDER] [-ip IP_RANGE] [-m METHOD] [-i INSTANCE] [-e EXTENSIONS] [-a ANSIBLE_ONLY] [-r RUN_PLAYBOOK] - -Description : goad lab management console. - -optional arguments: - -h, --help show this help message and exit - -t TASK, --task TASK tasks available : (install/start/stop/restart/destroy/status/show) - -l LAB, --lab LAB lab to use (default: GOAD) - -p PROVIDER, --provider PROVIDER - provider to use (default: vmware) - -ip IP_RANGE, --ip_range IP_RANGE - ip range to use (default: 192.168.56) - -m METHOD, --method METHOD - deploy method to use (default: local) - -i INSTANCE, --instance INSTANCE - use a specific instance (use default if not selected) - -e EXTENSIONS, --extensions EXTENSIONS - extensions to use - -a ANSIBLE_ONLY, --ansible_only ANSIBLE_ONLY - run only provisioning (ansible) on instance (-i) (for task install only) - -r RUN_PLAYBOOK, --run_playbook RUN_PLAYBOOK - run only one ansible playbook on instance (-i) (for task install only) - -Example : - - Install GOAD on virtualbox : python3 goad.py -t install -l GOAD -p virtualbox - - Launch GOAD interactive console : python3 goad.py +dreadgoad [subcommand] [flags] +``` + +## Global flags + +These flags apply to any command: + +| Flag | Description | +|---|---| +| `--env ` | Select the environment to operate on | +| `--config ` | Path to a config file (default: `dreadgoad.yaml`) | +| `--debug` | Enable debug logging | +| `--region ` | AWS region override | + +## Usage examples + +### Provisioning + +Run the full provisioning playbook sequence: + +```bash +dreadgoad provision ``` + +Start provisioning from a specific playbook: + +```bash +dreadgoad provision --from ad-trusts.yml +``` + +Run specific playbooks with a host limit: + +```bash +dreadgoad provision --plays ad-data.yml --limit dc01 +``` + +### Lab lifecycle + +```bash +dreadgoad lab status +dreadgoad lab start +dreadgoad lab stop +dreadgoad lab start-vm dc01 +dreadgoad lab stop-vm dc01 +dreadgoad lab restart-vm dc01 +dreadgoad lab destroy-vm dc01 +dreadgoad lab list +``` + +### Infrastructure + +```bash +dreadgoad infra init +dreadgoad infra plan +dreadgoad infra plan --module network +dreadgoad infra apply +dreadgoad infra destroy +dreadgoad infra output +dreadgoad infra validate +``` + +### Validation and diagnostics + +```bash +dreadgoad validate +dreadgoad validate --format json --output results.json +dreadgoad health-check +dreadgoad diagnose +dreadgoad doctor +dreadgoad verify-trusts +``` + +### Environment and configuration + +```bash +dreadgoad env create dev +dreadgoad env list +dreadgoad config init +dreadgoad config show +dreadgoad config set key value +``` + +### Extensions + +```bash +dreadgoad extension list +dreadgoad extension provision +dreadgoad extension provision-all +``` + +### SSM sessions + +```bash +dreadgoad ssm status +dreadgoad ssm connect +dreadgoad ssm run +dreadgoad ssm cleanup +``` + +### Inventory and AMI + +```bash +dreadgoad inventory sync +dreadgoad inventory show +dreadgoad inventory mapping +dreadgoad ami build +dreadgoad ami list-resources +dreadgoad ami purge +``` + +See the [CLI Reference](../cli-reference.md) for the full command listing and detailed flag descriptions. diff --git a/docs/mkdocs/docs/usage/goad_console.md b/docs/mkdocs/docs/usage/goad_console.md index bb7ec6d7..18917349 100644 --- a/docs/mkdocs/docs/usage/goad_console.md +++ b/docs/mkdocs/docs/usage/goad_console.md @@ -1,441 +1,58 @@ -# :material-console-line: GOAD interactive mode +# Migration from GOAD Console -Launch goad interactive mode +DreadGOAD replaces the GOAD interactive console (`goad.sh` / `goad.py`) with a structured CLI. There is no interactive mode -- all operations are run as standalone commands. -![console](./../img/console.png) +## Command mapping -## Enter interactive mode +The table below maps every old console command to its DreadGOAD equivalent. -To enter interactive mode just launch goad without the `-t` parameter +| Old Console Command | New CLI Command | +|---|---| +| `check` | `dreadgoad doctor` | +| `install` | `dreadgoad infra apply && dreadgoad provision` | +| `status` | `dreadgoad lab status` | +| `start` | `dreadgoad lab start` | +| `stop` | `dreadgoad lab stop` | +| `destroy` | `dreadgoad infra destroy` | +| `start_vm ` | `dreadgoad lab start-vm ` | +| `stop_vm ` | `dreadgoad lab stop-vm ` | +| `restart_vm ` | `dreadgoad lab restart-vm ` | +| `destroy_vm ` | `dreadgoad lab destroy-vm ` | +| `list_extensions` | `dreadgoad extension list` | +| `install_extension ` | `dreadgoad extension provision ` | +| `provision_extension ` | `dreadgoad extension provision ` | +| `provision ` | `dreadgoad provision --plays ` | +| `provision_lab` | `dreadgoad provision` | +| `provision_lab_from ` | `dreadgoad provision --from ` | +| `config` | `dreadgoad config show` | +| `labs` | `dreadgoad lab list` | -``` bash -./goad.sh -``` - -## No lab instance selected - -```text -*** Lab Instances *** -check ................................... check dependencies before creation -install / create ........................ install the selected lab and create a lab instance -create_empty ............................ prepare a lab instance folder without providing and provisioning -list .................................... list lab instances -load ...................... load a lab instance - -*** Configuration *** -config .................................. show current configuration -labs .................................... show all labs and available providers -set_lab ........................... set the lab to use -set_provider ................. set the provider to use -set_provisioning_method ........ set the provisioning method -set_ip_range .................... set the 3 first digit of the ip to use (ex: 192.168.56) -``` - -### check - -Will check the lab dependencies - -```text -check -``` - -![cmd_check](../img/cmd_check.png) - -### install - -Install the lab with the current select `config` - -```text -install -``` - -- This will: - - create an instance folder into workspaces/ - - run vagrant/terraform/ludus depending on the provider to create the machines - - synchronize source to jumpbox if provider is aws or azure - - provision jumpbox if provider is aws or azure - - run the ansible provisioning - -![cmd_install](../img/cmd_install.png) - -### create_empty - -Create an empty instance folder (into the workspaces/ folder) - -```text -create_empty -``` - -![create_empty](../img/cmd_create_empty.png) - -### list - -List instances - -> alias : `ls` - -```text -list -``` - -![list](../img/cmd_list.png) - -### load - -Select an instance by his name - -> alias : `use`, `cd` - -```bash -load -``` - -![load](../img/cmd_load.png) - -### config - -show current configuration - -```text -config -``` - -![config](../img/cmd_config.png) - -### labs - -show available labs - -```text -labs -``` - -![cmd_labs.png](../img/cmd_labs.png) - - -### set_lab - -Choose the lab to use (GOAD/GOAD-Light/NHA/SCCM/MINILAB) - -```bash -set_lab -``` - -### set_provider - -Choose the provider to use (virtualbox/vmware/aws/azure/ludus/proxmox) - -```bash -set_provider -``` - - -### set_provisioning_method - -Choose the provisioning method (local/runner/docker/remote) (most of the time you don't have to change it) - -```bash -set_provisioning -``` - -- local : launch ansible with subprocess (default for vbox/vmware/proxmox/ludus) -- runner : launch ansible with ansible runner -- remote : launch ansible through ssh using jumpbox (default for azure/aws) -- docker : user the docker container to launch ansible (docker container must be built first `sudo docker build -t goadansible .`) - -### set_ip_range - -Set the ip range you want to use (Three first digit, example : 192.168.10) - -```bash -set_ip_range -``` - - -## Instance selected - -![console](../img/console2.png) - -```text -*** Manage Lab instance commands *** -status .................................. show current status -start ................................... start lab -stop .................................... stop lab -destroy ................................. destroy lab - -*** Manage one vm commands *** -start_vm ...................... start selected virtual machine -stop_vm ....................... stop selected virtual machine -restart_vm .................... restart selected virtual machine -destroy_vm .................... destroy selected virtual machine - -*** Extensions *** -list_extensions ......................... list extensions -install_extension ........... install extension (providing + provisioning) -provision_extension ......... provision extension (provisioning only) - -*** JumpBox *** -prepare_jumpbox ......................... install package on the jumpbox for provisioning -sync_source_jumpbox ..................... sync source of the jumpbox -ssh_jumpbox ............................. connect to jump box with ssh -ssh_jumpbox_proxy .......... connect to jump box with ssh and start a socks proxy - -*** Providing (Vagrant/Terrafom) *** -provide ................................. run only the providing (vagrant/terraform) - -*** Provisioning (Ansible) *** -provision .................... run specific ansible playbook -provision_lab ........................... run all the current lab ansible playbooks -provision_lab_from ........... run all the current lab ansible playbooks from specific playbook to the end - -*** Lab Instances *** -check ................................... check dependencies before creation -install ................................. install the current instance (provide + prepare_jumpbox + provision_lab -set_as_default .......................... set instance as default -update_instance_files ................... update lab instance files -list .................................... list lab instances -load ...................... load a lab instance - -*** Configuration *** -config .................................. show current configuration -unload .................................. unload current instance -delete .................................. delete the currently selected lab instance -``` - -### status - -Give the current lab status - -```text -status -``` - -### start - -Start the current lab instance - -```text -start -``` - -### stop - -Stop the current lab instance - -```text -stop -``` - -### destroy - -!!! danger - Destroy the current lab instance vms - -```text -destroy -``` - -### start_vm - -Start a vm - -```bash -start_vm -``` - -### stop_vm - -Stop a vm - -```bash -stop_vm -``` - -### restart_vm - -Restart a vm (start and stop) - -```bash -restart_vm -``` - -### destroy_vm - -!!! danger - Destroy a vm - -```bash -destroy_vm -``` - -### list_extensions - -List available extensions - -```text -list_extensions -``` - -### install_extension - -Add an extension to the lab (providing + provisioning) - -!!! warning - An installed extension can't be deleted - -```bash -install_extension -``` - -### provision_extension - -Launch provisioning (ansible) for the extension - -```bash -provision_extension -``` - -### prepare_jumpbox - -Prepare jumpbox : run the preparation script on the jumpbox (install dependencies) - -```text -prepare_jumpbox -``` - -### sync_source_jumpbox - -Rsync goad source with the jumpbox - -```text -sync_source_jumpbox -``` - -### ssh_jumpbox - - -SSH into the jumpbox - -```text -ssh_jumpbox -``` - -### ssh_jumpbox_proxy - -SSH into the jumpbox with a socks proxy option (-D) - -```bash -ssh_jumpbox_proxy -``` - -### provide - -Launch providing (machine creation) - -```text -provide -``` - -### provision - -Launch specific playbook (use playbook in ansible/ folder) - -```bash -provision -``` - -### provision_lab - -Launch all the lab provisioning (install labs on machines with ansible) - -```text -provision_lab -``` - -### provision_lab_from - -Launch the lab provisioning from a specific playbook (use playbook in ansible/ folder) - -!!! tip - useful if the install crash to not redo all the provisioning - -```bash -provision_lab_from -``` - -### check (instance) - -Launch the check (same as without instance) - -```text -check -``` - -### install (instance) - -Launch the install (useful if you created an empty instance) - -```text -install -``` +## Key differences -### set_as_defualt +- **No interactive session.** Every command runs to completion and exits. Chain commands with `&&` when you need multi-step workflows. +- **Environments replace instances.** Use `dreadgoad env create ` to create an environment and `--env ` to target it. +- **Configuration is file-based.** Run `dreadgoad config init` to generate a `dreadgoad.yaml` config, then `dreadgoad config set` or edit the file directly. +- **Provider is always AWS.** DreadGOAD targets AWS exclusively; there are no provider flags. +- **No jumpbox commands.** SSM replaces SSH-based jumpbox access. Use `dreadgoad ssm connect` and `dreadgoad ssm run` instead. -Set the current instance as default (automatically loaded on goad start) +## Example: migrating a typical workflow -```text -set_as_defualt -``` - -### update_instance_files - -Recreate the files inside the workspace folder - -```text -update_instance_files -``` - -### list (instance) - -List instances - -> alias : `ls` +Old GOAD console session: ```text -list +./goad.sh +> set_lab GOAD +> set_provider aws +> check +> install +> status ``` -### load (instance) - -Select an instance by his name (here change the current instance) - -> alias : `use`, `cd` +Equivalent DreadGOAD commands: ```bash -load -``` - -### config (instance) - -Show current configuration - -```text -config -``` - -### unload - -Unload the instance (alias `cd ..`) - -```text -unload -``` - -### delete - -!!! danger - delete the current instance lab and vms - -```text -delete +dreadgoad doctor +dreadgoad infra apply +dreadgoad provision +dreadgoad lab status ``` diff --git a/docs/mkdocs/docs/usage/index.md b/docs/mkdocs/docs/usage/index.md index 16dbabb5..e711ad96 100644 --- a/docs/mkdocs/docs/usage/index.md +++ b/docs/mkdocs/docs/usage/index.md @@ -1,7 +1,38 @@ # Usage -- Goad script can be run in two ways. - - [argument_mode](goad_args.md) : launch goad.sh with arguments to launch one task - - [interactive_mode](goad_console.md) : launch an interactive console to manage multiple labs and instances. +DreadGOAD is managed through the `dreadgoad` CLI. All operations are available as commands and subcommands. -- The easy way to use goad is just launch `./goad.sh` and use `?` in the interactive console to get some help. +```bash +dreadgoad --help +``` + +See the [CLI Reference](../cli-reference.md) for the full command listing. + +## Common workflows + +### First-time setup + +```bash +dreadgoad config init # Create default config +dreadgoad doctor # Verify dependencies +dreadgoad env create dev # Create an environment +``` + +### Deploy a lab + +```bash +dreadgoad infra init # Initialize Terragrunt +dreadgoad infra apply # Provision infrastructure +dreadgoad inventory sync # Sync inventory with AWS +dreadgoad provision # Run Ansible provisioning +dreadgoad validate # Validate vulnerabilities +``` + +### Day-to-day operations + +```bash +dreadgoad lab status # Check lab state +dreadgoad lab stop # Stop all instances +dreadgoad lab start # Start all instances +dreadgoad health-check # Verify lab health +``` diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index be5d8d96..d2686b44 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -35,8 +35,9 @@ nav: - wazuh: extensions/wazuh.md - 💻 Usage: - index: usage/index.md - - Arguments: usage/goad_args.md - - Interactive: usage/goad_console.md + - CLI Commands: usage/goad_args.md + - Migration Guide: usage/goad_console.md + - 🖥️ CLI Reference: cli-reference.md - 📝 Developers: - index: developers/index.md - Add Extension: developers/add_extension.md diff --git a/dreadgoad.yaml b/dreadgoad.yaml index 88547e4d..7a9f160b 100644 --- a/dreadgoad.yaml +++ b/dreadgoad.yaml @@ -15,5 +15,11 @@ environments: variant_source: ad/GOAD variant_target: ad/GOAD-variant-1 variant_name: variant-1 + vpc_cidr: "10.0.0.0/16" staging: variant: false + vpc_cidr: "10.1.0.0/16" + prod: + vpc_cidr: "10.2.0.0/16" + test: + vpc_cidr: "10.8.0.0/16" diff --git a/infra/README.md b/infra/README.md index 3c733d86..d0fbbda2 100644 --- a/infra/README.md +++ b/infra/README.md @@ -64,6 +64,13 @@ The Terragrunt configs reference modules from `modules/`: ## Deployment +```bash +dreadgoad infra init --env staging --region us-west-1 +dreadgoad infra apply --env staging --region us-west-1 +``` + +Or with raw Terragrunt for more control: + ```bash cd infra/goad-deployment/staging/us-west-1 @@ -78,11 +85,28 @@ All instances use SSM for management -- no SSH keys or open ports. VPC endpoints ## Adding a New Environment +Use the CLI to scaffold a new environment: + ```bash -# Duplicate the staging tree -cp -r goad-deployment/staging goad-deployment/dev +dreadgoad env create dev +``` + +This reads the VPC CIDR from `dreadgoad.yaml` (`environments.dev.vpc_cidr`), +generates `env.hcl`, `region.hcl`, copies infrastructure from the reference +environment, and creates an inventory file. See `dreadgoad env create --help`. -# Edit dev/env.hcl with new account ID, VPC CIDR, etc. +To set a custom CIDR, either configure it in `dreadgoad.yaml`: + +```yaml +environments: + dev: + vpc_cidr: "10.0.0.0/16" +``` + +Or pass it as a flag: + +```bash +dreadgoad env create dev --vpc-cidr 10.0.0.0/16 ``` Each environment gets its own Terraform state, so multiple labs can run in parallel. @@ -90,10 +114,10 @@ Each environment gets its own Terraform state, so multiple labs can run in paral ## Adding a New Region ```bash -# Create region directory under your environment -mkdir -p goad-deployment/staging/eu-west-1 -cp goad-deployment/staging/us-west-1/region.hcl goad-deployment/staging/eu-west-1/ -# Edit region.hcl, then copy network/ and goad/ directories +dreadgoad env create staging --region eu-west-1 --reference staging ``` +This scaffolds the region directory with `region.hcl`, network, and host +configurations copied from the reference environment. + For the full end-to-end workflow including warpgate AMI builds and Ansible provisioning, see the [AWS AMI build & deploy workflow](../docs/mkdocs/docs/providers/aws-ami-workflow.md). diff --git a/warpgate-templates/README.md b/warpgate-templates/README.md index 6f0bf286..2313813f 100644 --- a/warpgate-templates/README.md +++ b/warpgate-templates/README.md @@ -84,7 +84,7 @@ goad-{template-name}/ The `warpgate.yaml` files reference Ansible playbooks under `ansible/playbooks/base/`: - `dc_base.yml` -- used by `goad-dc-base`, `goad-dc-base-2016`, and `goad-dc-base-2025` -- `mssql_base.yml` -- used by `goad-mssql-base`, `goad-mssql-base-2016`, and `goad-mssql-base-2025` +- `mssql_base_setup.yml` + `mssql_base_sql.yml` -- used by `goad-mssql-base`, `goad-mssql-base-2016`, and `goad-mssql-base-2025` ## Using Built AMIs diff --git a/warpgate-templates/goad-dc-base-2016/scripts/01-install-modules.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/01-install-modules.ps1 deleted file mode 100644 index 85e7ad72..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/01-install-modules.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing NuGet provider..." -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Install-PackageProvider -Name NuGet -Force -Confirm:$false - -Write-Host "Installing PowerShellGet module..." -Install-Module PowerShellGet -Force -Confirm:$false - -Write-Host "Installing required DSC modules..." -$modules = @('ComputerManagementDsc', 'ActiveDirectoryDsc', 'xNetworking', 'NetworkingDsc') -foreach ($module in $modules) { - Write-Host "Installing module: $module" - Install-Module -Name $module -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -} - -Write-Host "PowerShell modules installed successfully" diff --git a/warpgate-templates/goad-dc-base-2016/scripts/02-install-adds-role.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/02-install-adds-role.ps1 deleted file mode 100644 index 4e620c38..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/02-install-adds-role.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing AD Domain Services role..." -Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools - -Write-Host "Installing DNS Server role..." -Install-WindowsFeature -Name DNS -IncludeManagementTools - -Write-Host "Installing RSAT tools..." -Install-WindowsFeature -Name RSAT-AD-Tools, RSAT-DNS-Server, RSAT-ADDS - -Write-Host "Installing Group Policy Management..." -Install-WindowsFeature -Name GPMC - -Write-Host "AD DS role installation complete" diff --git a/warpgate-templates/goad-dc-base-2016/scripts/03-enable-rdp.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/03-enable-rdp.ps1 deleted file mode 100644 index 35400465..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/03-enable-rdp.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Write-Host "Enabling Remote Desktop..." -Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 -Enable-NetFirewallRule -DisplayGroup "Remote Desktop" - -Write-Host "RDP enabled" diff --git a/warpgate-templates/goad-dc-base-2016/scripts/04-windows-updates.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/04-windows-updates.ps1 deleted file mode 100644 index 618a5771..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/04-windows-updates.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Starting Windows Update service..." -Set-Service -Name wuauserv -StartupType Automatic -Start-Service -Name wuauserv - -Write-Host "Installing PSWindowsUpdate module..." -Install-Module -Name PSWindowsUpdate -Force -Confirm:$false - -Write-Host "Checking for Windows Updates..." -Import-Module PSWindowsUpdate - -Write-Host "Installing Windows Updates (this may take 15-30 minutes)..." -$updates = Get-WindowsUpdate -AcceptAll -Install -AutoReboot:$false -IgnoreReboot - -if ($updates) { - Write-Host "Installed $($updates.Count) updates" -} else { - Write-Host "No updates available" -} - -Write-Host "Windows Updates complete" diff --git a/warpgate-templates/goad-dc-base-2016/scripts/05-configure-ssm.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/05-configure-ssm.ps1 deleted file mode 100644 index e1446905..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/05-configure-ssm.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' - -Write-Host "Configuring SSM Agent for post-DC-promotion operation..." - -# SSM agent needs special configuration to survive DC promotion -# Create a scheduled task to restart SSM agent after DC promotion -$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-Command "Start-Sleep -Seconds 60; Restart-Service AmazonSSMAgent"' -$trigger = New-ScheduledTaskTrigger -AtStartup -$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest -$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable - -Register-ScheduledTask -TaskName "RestartSSMAfterBoot" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force - -Write-Host "SSM Agent configuration complete" - -# Ensure SSM agent is running now -$ssmService = Get-Service -Name "AmazonSSMAgent" -ErrorAction SilentlyContinue -if ($ssmService) { - if ($ssmService.Status -ne 'Running') { - Start-Service -Name "AmazonSSMAgent" - Write-Host "SSM Agent started" - } else { - Write-Host "SSM Agent already running" - } -} else { - Write-Host "Warning: SSM Agent not found" -} diff --git a/warpgate-templates/goad-dc-base-2016/scripts/06-cleanup.ps1 b/warpgate-templates/goad-dc-base-2016/scripts/06-cleanup.ps1 deleted file mode 100644 index 9f7a8c1c..00000000 --- a/warpgate-templates/goad-dc-base-2016/scripts/06-cleanup.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -Write-Host "Cleaning up for AMI creation..." - -# Clear Windows Update download cache -Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue -Start-Service -Name wuauserv - -# Clear temp files -Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear event logs -wevtutil cl Application -wevtutil cl Security -wevtutil cl System - -Write-Host "Cleanup complete" diff --git a/warpgate-templates/goad-dc-base/scripts/01-install-modules.ps1 b/warpgate-templates/goad-dc-base/scripts/01-install-modules.ps1 deleted file mode 100644 index 85e7ad72..00000000 --- a/warpgate-templates/goad-dc-base/scripts/01-install-modules.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing NuGet provider..." -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Install-PackageProvider -Name NuGet -Force -Confirm:$false - -Write-Host "Installing PowerShellGet module..." -Install-Module PowerShellGet -Force -Confirm:$false - -Write-Host "Installing required DSC modules..." -$modules = @('ComputerManagementDsc', 'ActiveDirectoryDsc', 'xNetworking', 'NetworkingDsc') -foreach ($module in $modules) { - Write-Host "Installing module: $module" - Install-Module -Name $module -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -} - -Write-Host "PowerShell modules installed successfully" diff --git a/warpgate-templates/goad-dc-base/scripts/02-install-adds-role.ps1 b/warpgate-templates/goad-dc-base/scripts/02-install-adds-role.ps1 deleted file mode 100644 index 4e620c38..00000000 --- a/warpgate-templates/goad-dc-base/scripts/02-install-adds-role.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing AD Domain Services role..." -Install-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools - -Write-Host "Installing DNS Server role..." -Install-WindowsFeature -Name DNS -IncludeManagementTools - -Write-Host "Installing RSAT tools..." -Install-WindowsFeature -Name RSAT-AD-Tools, RSAT-DNS-Server, RSAT-ADDS - -Write-Host "Installing Group Policy Management..." -Install-WindowsFeature -Name GPMC - -Write-Host "AD DS role installation complete" diff --git a/warpgate-templates/goad-dc-base/scripts/03-enable-rdp.ps1 b/warpgate-templates/goad-dc-base/scripts/03-enable-rdp.ps1 deleted file mode 100644 index 35400465..00000000 --- a/warpgate-templates/goad-dc-base/scripts/03-enable-rdp.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Write-Host "Enabling Remote Desktop..." -Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 -Enable-NetFirewallRule -DisplayGroup "Remote Desktop" - -Write-Host "RDP enabled" diff --git a/warpgate-templates/goad-dc-base/scripts/04-windows-updates.ps1 b/warpgate-templates/goad-dc-base/scripts/04-windows-updates.ps1 deleted file mode 100644 index 618a5771..00000000 --- a/warpgate-templates/goad-dc-base/scripts/04-windows-updates.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Starting Windows Update service..." -Set-Service -Name wuauserv -StartupType Automatic -Start-Service -Name wuauserv - -Write-Host "Installing PSWindowsUpdate module..." -Install-Module -Name PSWindowsUpdate -Force -Confirm:$false - -Write-Host "Checking for Windows Updates..." -Import-Module PSWindowsUpdate - -Write-Host "Installing Windows Updates (this may take 15-30 minutes)..." -$updates = Get-WindowsUpdate -AcceptAll -Install -AutoReboot:$false -IgnoreReboot - -if ($updates) { - Write-Host "Installed $($updates.Count) updates" -} else { - Write-Host "No updates available" -} - -Write-Host "Windows Updates complete" diff --git a/warpgate-templates/goad-dc-base/scripts/05-configure-ssm.ps1 b/warpgate-templates/goad-dc-base/scripts/05-configure-ssm.ps1 deleted file mode 100644 index e1446905..00000000 --- a/warpgate-templates/goad-dc-base/scripts/05-configure-ssm.ps1 +++ /dev/null @@ -1,27 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' - -Write-Host "Configuring SSM Agent for post-DC-promotion operation..." - -# SSM agent needs special configuration to survive DC promotion -# Create a scheduled task to restart SSM agent after DC promotion -$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-Command "Start-Sleep -Seconds 60; Restart-Service AmazonSSMAgent"' -$trigger = New-ScheduledTaskTrigger -AtStartup -$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest -$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable - -Register-ScheduledTask -TaskName "RestartSSMAfterBoot" -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force - -Write-Host "SSM Agent configuration complete" - -# Ensure SSM agent is running now -$ssmService = Get-Service -Name "AmazonSSMAgent" -ErrorAction SilentlyContinue -if ($ssmService) { - if ($ssmService.Status -ne 'Running') { - Start-Service -Name "AmazonSSMAgent" - Write-Host "SSM Agent started" - } else { - Write-Host "SSM Agent already running" - } -} else { - Write-Host "Warning: SSM Agent not found" -} diff --git a/warpgate-templates/goad-dc-base/scripts/06-cleanup.ps1 b/warpgate-templates/goad-dc-base/scripts/06-cleanup.ps1 deleted file mode 100644 index 9f7a8c1c..00000000 --- a/warpgate-templates/goad-dc-base/scripts/06-cleanup.ps1 +++ /dev/null @@ -1,17 +0,0 @@ -Write-Host "Cleaning up for AMI creation..." - -# Clear Windows Update download cache -Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue -Start-Service -Name wuauserv - -# Clear temp files -Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear event logs -wevtutil cl Application -wevtutil cl Security -wevtutil cl System - -Write-Host "Cleanup complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 deleted file mode 100644 index 85e7ad72..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/01-install-modules.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing NuGet provider..." -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Install-PackageProvider -Name NuGet -Force -Confirm:$false - -Write-Host "Installing PowerShellGet module..." -Install-Module PowerShellGet -Force -Confirm:$false - -Write-Host "Installing required DSC modules..." -$modules = @('ComputerManagementDsc', 'ActiveDirectoryDsc', 'xNetworking', 'NetworkingDsc') -foreach ($module in $modules) { - Write-Host "Installing module: $module" - Install-Module -Name $module -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -} - -Write-Host "PowerShell modules installed successfully" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 deleted file mode 100644 index 2c73305a..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/02-install-iis.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing IIS..." -Install-WindowsFeature -Name Web-Server -IncludeManagementTools -IncludeAllSubFeature - -Write-Host "Installing WebDAV..." -Install-WindowsFeature -Name Web-DAV-Publishing - -Write-Host "IIS installation complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 deleted file mode 100644 index 97c3402e..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/03-download-mssql.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -$downloadUrl = "https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe" - -Write-Host "Creating installation directories..." -New-Item -Path "C:\setup\mssql\media" -ItemType Directory -Force | Out-Null -New-Item -Path "C:\setup\mssql\extraction" -ItemType Directory -Force | Out-Null - -Write-Host "Downloading SQL Server Express 2019 installer..." -Invoke-WebRequest -Uri $downloadUrl -OutFile "C:\setup\mssql\sql_installer.exe" -UseBasicParsing - -Write-Host "Downloading SQL Server installation media (this may take 5-10 minutes)..." -Start-Process -FilePath "C:\setup\mssql\sql_installer.exe" -ArgumentList "/ACTION=Download", "/MEDIAPATH=C:\setup\mssql\media", "/Q" -Wait -NoNewWindow - -Write-Host "Extracting SQL Server installation files..." -Start-Process -FilePath "C:\setup\mssql\media\SQLEXPR_x64_ENU.exe" -ArgumentList "/x:C:\setup\mssql\extraction", "/q" -Wait -NoNewWindow - -Write-Host "SQL Server media download complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 deleted file mode 100644 index 8fd8a4e2..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/04-install-mssql.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -$sqlInstanceName = "SQLEXPRESS" - -# Create configuration file -$configContent = @" -[OPTIONS] -ACTION="Install" -FEATURES=SQLENGINE -INSTANCENAME="$sqlInstanceName" -INSTANCEID="$sqlInstanceName" -SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" -SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" -AGTSVCSTARTUPTYPE="Automatic" -SQLSVCSTARTUPTYPE="Automatic" -BROWSERSVCSTARTUPTYPE="Automatic" -SECURITYMODE="SQL" -SAPWD="TempSaPassword123!" -TCPENABLED="1" -NPENABLED="1" -IACCEPTSQLSERVERLICENSETERMS="True" -QUIET="True" -QUIETSIMPLE="False" -UpdateEnabled="False" -ERRORREPORTING="False" -SQMREPORTING="False" -"@ - -Write-Host "Creating SQL Server configuration file..." -$configContent | Out-File -FilePath "C:\setup\mssql\sql_conf.ini" -Encoding ASCII - -Write-Host "Installing SQL Server Express 2019 (this may take 15-25 minutes)..." - -$process = Start-Process -FilePath "C:\setup\mssql\extraction\SETUP.EXE" ` - -ArgumentList "/ConfigurationFile=C:\setup\mssql\sql_conf.ini" ` - -Wait -NoNewWindow -PassThru - -if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { - Write-Host "SQL Server Express installation completed successfully" -} else { - # Check if SQL is actually installed despite exit code - $sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue - if ($sqlService) { - Write-Host "SQL Server Express installation completed (service exists)" - } else { - Write-Error "SQL Server installation failed with exit code: $($process.ExitCode)" - } -} diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 deleted file mode 100644 index b58e3fd9..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/05-configure-mssql.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' - -Write-Host "Configuring SQL Server TCP port..." - -# Set TCP port to 1433 -$regPath = "HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll" -if (Test-Path $regPath) { - Set-ItemProperty -Path $regPath -Name "TcpPort" -Value "1433" - Set-ItemProperty -Path $regPath -Name "TcpDynamicPorts" -Value "" - Write-Host "TCP port configured to 1433" -} else { - Write-Host "Registry path not found - SQL Server may need a restart" -} - -Write-Host "Configuring firewall rules for SQL Server..." - -# Allow SQL Server through firewall -New-NetFirewallRule -DisplayName "MSSQL TCP 1433" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow -Profile Domain -ErrorAction SilentlyContinue -New-NetFirewallRule -DisplayName "MSSQL UDP 1434" -Direction Inbound -Protocol UDP -LocalPort 1434 -Action Allow -Profile Domain -ErrorAction SilentlyContinue - -Write-Host "Firewall rules configured" - -Write-Host "Verifying SQL Server installation..." - -$sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue -if ($sqlService) { - Write-Host "SQL Server service found: $($sqlService.Status)" - - # Ensure service is running - if ($sqlService.Status -ne 'Running') { - Start-Service -Name "MSSQL`$SQLEXPRESS" - Write-Host "SQL Server service started" - } -} else { - Write-Error "SQL Server service not found!" -} - -# Configure SQL Browser service -$browserService = Get-Service -Name "SQLBrowser" -ErrorAction SilentlyContinue -if ($browserService) { - Set-Service -Name "SQLBrowser" -StartupType Automatic - if ($browserService.Status -ne 'Running') { - Start-Service -Name "SQLBrowser" - } - Write-Host "SQL Browser service running" -} - -Write-Host "SQL Server configuration complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 deleted file mode 100644 index 35400465..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/06-enable-rdp.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Write-Host "Enabling Remote Desktop..." -Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 -Enable-NetFirewallRule -DisplayGroup "Remote Desktop" - -Write-Host "RDP enabled" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 deleted file mode 100644 index 618a5771..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/07-windows-updates.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Starting Windows Update service..." -Set-Service -Name wuauserv -StartupType Automatic -Start-Service -Name wuauserv - -Write-Host "Installing PSWindowsUpdate module..." -Install-Module -Name PSWindowsUpdate -Force -Confirm:$false - -Write-Host "Checking for Windows Updates..." -Import-Module PSWindowsUpdate - -Write-Host "Installing Windows Updates (this may take 15-30 minutes)..." -$updates = Get-WindowsUpdate -AcceptAll -Install -AutoReboot:$false -IgnoreReboot - -if ($updates) { - Write-Host "Installed $($updates.Count) updates" -} else { - Write-Host "No updates available" -} - -Write-Host "Windows Updates complete" diff --git a/warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 b/warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 deleted file mode 100644 index 00c98077..00000000 --- a/warpgate-templates/goad-mssql-base-2016/scripts/08-cleanup.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -Write-Host "Cleaning up for AMI creation..." - -# Clear Windows Update download cache -Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue -Start-Service -Name wuauserv - -# Keep SQL installer files (in case reinstall needed) -# Remove-Item -Path "C:\setup" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear temp files -Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear event logs -wevtutil cl Application -wevtutil cl Security -wevtutil cl System - -Write-Host "Cleanup complete" diff --git a/warpgate-templates/goad-mssql-base-2016/warpgate.yaml b/warpgate-templates/goad-mssql-base-2016/warpgate.yaml index ccafe611..9af0b2b2 100644 --- a/warpgate-templates/goad-mssql-base-2016/warpgate.yaml +++ b/warpgate-templates/goad-mssql-base-2016/warpgate.yaml @@ -27,9 +27,16 @@ variables: ami_name_filter: "Windows_Server-2016-English-Full-Base-*" provisioners: - # Provision with Ansible via AWS SSM - type: ansible - playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base.yml + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_setup.yml + galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml + extra_vars: + ansible_connection: aws_ssm + ansible_shell_type: powershell + ansible_aws_ssm_bucket_name: "" + ansible_aws_ssm_region: "${aws_region}" + - type: ansible + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_sql.yml galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml extra_vars: ansible_connection: aws_ssm diff --git a/warpgate-templates/goad-mssql-base-2025/warpgate.yaml b/warpgate-templates/goad-mssql-base-2025/warpgate.yaml index 3ab6492e..eba97130 100644 --- a/warpgate-templates/goad-mssql-base-2025/warpgate.yaml +++ b/warpgate-templates/goad-mssql-base-2025/warpgate.yaml @@ -28,9 +28,16 @@ variables: ami_name_filter: "Windows_Server-2025-English-Full-Base-*" provisioners: - # Provision with Ansible via AWS SSM - type: ansible - playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base.yml + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_setup.yml + galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml + extra_vars: + ansible_connection: aws_ssm + ansible_shell_type: powershell + ansible_aws_ssm_bucket_name: "" + ansible_aws_ssm_region: "${aws_region}" + - type: ansible + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_sql.yml galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml extra_vars: ansible_connection: aws_ssm diff --git a/warpgate-templates/goad-mssql-base/scripts/01-install-modules.ps1 b/warpgate-templates/goad-mssql-base/scripts/01-install-modules.ps1 deleted file mode 100644 index 85e7ad72..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/01-install-modules.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing NuGet provider..." -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -Install-PackageProvider -Name NuGet -Force -Confirm:$false - -Write-Host "Installing PowerShellGet module..." -Install-Module PowerShellGet -Force -Confirm:$false - -Write-Host "Installing required DSC modules..." -$modules = @('ComputerManagementDsc', 'ActiveDirectoryDsc', 'xNetworking', 'NetworkingDsc') -foreach ($module in $modules) { - Write-Host "Installing module: $module" - Install-Module -Name $module -Force -Confirm:$false -SkipPublisherCheck -AcceptLicense -} - -Write-Host "PowerShell modules installed successfully" diff --git a/warpgate-templates/goad-mssql-base/scripts/02-install-iis.ps1 b/warpgate-templates/goad-mssql-base/scripts/02-install-iis.ps1 deleted file mode 100644 index 2c73305a..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/02-install-iis.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Installing IIS..." -Install-WindowsFeature -Name Web-Server -IncludeManagementTools -IncludeAllSubFeature - -Write-Host "Installing WebDAV..." -Install-WindowsFeature -Name Web-DAV-Publishing - -Write-Host "IIS installation complete" diff --git a/warpgate-templates/goad-mssql-base/scripts/03-download-mssql.ps1 b/warpgate-templates/goad-mssql-base/scripts/03-download-mssql.ps1 deleted file mode 100644 index 97c3402e..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/03-download-mssql.ps1 +++ /dev/null @@ -1,19 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -$downloadUrl = "https://download.microsoft.com/download/7/f/8/7f8a9c43-8c8a-4f7c-9f92-83c18d96b681/SQL2019-SSEI-Expr.exe" - -Write-Host "Creating installation directories..." -New-Item -Path "C:\setup\mssql\media" -ItemType Directory -Force | Out-Null -New-Item -Path "C:\setup\mssql\extraction" -ItemType Directory -Force | Out-Null - -Write-Host "Downloading SQL Server Express 2019 installer..." -Invoke-WebRequest -Uri $downloadUrl -OutFile "C:\setup\mssql\sql_installer.exe" -UseBasicParsing - -Write-Host "Downloading SQL Server installation media (this may take 5-10 minutes)..." -Start-Process -FilePath "C:\setup\mssql\sql_installer.exe" -ArgumentList "/ACTION=Download", "/MEDIAPATH=C:\setup\mssql\media", "/Q" -Wait -NoNewWindow - -Write-Host "Extracting SQL Server installation files..." -Start-Process -FilePath "C:\setup\mssql\media\SQLEXPR_x64_ENU.exe" -ArgumentList "/x:C:\setup\mssql\extraction", "/q" -Wait -NoNewWindow - -Write-Host "SQL Server media download complete" diff --git a/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 b/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 deleted file mode 100644 index 8fd8a4e2..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/04-install-mssql.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -$sqlInstanceName = "SQLEXPRESS" - -# Create configuration file -$configContent = @" -[OPTIONS] -ACTION="Install" -FEATURES=SQLENGINE -INSTANCENAME="$sqlInstanceName" -INSTANCEID="$sqlInstanceName" -SQLSVCACCOUNT="NT AUTHORITY\NETWORK SERVICE" -SQLSYSADMINACCOUNTS="BUILTIN\Administrators" "NT AUTHORITY\NETWORK SERVICE" -AGTSVCSTARTUPTYPE="Automatic" -SQLSVCSTARTUPTYPE="Automatic" -BROWSERSVCSTARTUPTYPE="Automatic" -SECURITYMODE="SQL" -SAPWD="TempSaPassword123!" -TCPENABLED="1" -NPENABLED="1" -IACCEPTSQLSERVERLICENSETERMS="True" -QUIET="True" -QUIETSIMPLE="False" -UpdateEnabled="False" -ERRORREPORTING="False" -SQMREPORTING="False" -"@ - -Write-Host "Creating SQL Server configuration file..." -$configContent | Out-File -FilePath "C:\setup\mssql\sql_conf.ini" -Encoding ASCII - -Write-Host "Installing SQL Server Express 2019 (this may take 15-25 minutes)..." - -$process = Start-Process -FilePath "C:\setup\mssql\extraction\SETUP.EXE" ` - -ArgumentList "/ConfigurationFile=C:\setup\mssql\sql_conf.ini" ` - -Wait -NoNewWindow -PassThru - -if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) { - Write-Host "SQL Server Express installation completed successfully" -} else { - # Check if SQL is actually installed despite exit code - $sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue - if ($sqlService) { - Write-Host "SQL Server Express installation completed (service exists)" - } else { - Write-Error "SQL Server installation failed with exit code: $($process.ExitCode)" - } -} diff --git a/warpgate-templates/goad-mssql-base/scripts/05-configure-mssql.ps1 b/warpgate-templates/goad-mssql-base/scripts/05-configure-mssql.ps1 deleted file mode 100644 index b58e3fd9..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/05-configure-mssql.ps1 +++ /dev/null @@ -1,48 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' - -Write-Host "Configuring SQL Server TCP port..." - -# Set TCP port to 1433 -$regPath = "HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL15.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll" -if (Test-Path $regPath) { - Set-ItemProperty -Path $regPath -Name "TcpPort" -Value "1433" - Set-ItemProperty -Path $regPath -Name "TcpDynamicPorts" -Value "" - Write-Host "TCP port configured to 1433" -} else { - Write-Host "Registry path not found - SQL Server may need a restart" -} - -Write-Host "Configuring firewall rules for SQL Server..." - -# Allow SQL Server through firewall -New-NetFirewallRule -DisplayName "MSSQL TCP 1433" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow -Profile Domain -ErrorAction SilentlyContinue -New-NetFirewallRule -DisplayName "MSSQL UDP 1434" -Direction Inbound -Protocol UDP -LocalPort 1434 -Action Allow -Profile Domain -ErrorAction SilentlyContinue - -Write-Host "Firewall rules configured" - -Write-Host "Verifying SQL Server installation..." - -$sqlService = Get-Service -Name "MSSQL`$SQLEXPRESS" -ErrorAction SilentlyContinue -if ($sqlService) { - Write-Host "SQL Server service found: $($sqlService.Status)" - - # Ensure service is running - if ($sqlService.Status -ne 'Running') { - Start-Service -Name "MSSQL`$SQLEXPRESS" - Write-Host "SQL Server service started" - } -} else { - Write-Error "SQL Server service not found!" -} - -# Configure SQL Browser service -$browserService = Get-Service -Name "SQLBrowser" -ErrorAction SilentlyContinue -if ($browserService) { - Set-Service -Name "SQLBrowser" -StartupType Automatic - if ($browserService.Status -ne 'Running') { - Start-Service -Name "SQLBrowser" - } - Write-Host "SQL Browser service running" -} - -Write-Host "SQL Server configuration complete" diff --git a/warpgate-templates/goad-mssql-base/scripts/06-enable-rdp.ps1 b/warpgate-templates/goad-mssql-base/scripts/06-enable-rdp.ps1 deleted file mode 100644 index 35400465..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/06-enable-rdp.ps1 +++ /dev/null @@ -1,5 +0,0 @@ -Write-Host "Enabling Remote Desktop..." -Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" -Value 0 -Enable-NetFirewallRule -DisplayGroup "Remote Desktop" - -Write-Host "RDP enabled" diff --git a/warpgate-templates/goad-mssql-base/scripts/07-windows-updates.ps1 b/warpgate-templates/goad-mssql-base/scripts/07-windows-updates.ps1 deleted file mode 100644 index 618a5771..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/07-windows-updates.ps1 +++ /dev/null @@ -1,23 +0,0 @@ -$ProgressPreference = 'SilentlyContinue' -$ErrorActionPreference = 'Stop' - -Write-Host "Starting Windows Update service..." -Set-Service -Name wuauserv -StartupType Automatic -Start-Service -Name wuauserv - -Write-Host "Installing PSWindowsUpdate module..." -Install-Module -Name PSWindowsUpdate -Force -Confirm:$false - -Write-Host "Checking for Windows Updates..." -Import-Module PSWindowsUpdate - -Write-Host "Installing Windows Updates (this may take 15-30 minutes)..." -$updates = Get-WindowsUpdate -AcceptAll -Install -AutoReboot:$false -IgnoreReboot - -if ($updates) { - Write-Host "Installed $($updates.Count) updates" -} else { - Write-Host "No updates available" -} - -Write-Host "Windows Updates complete" diff --git a/warpgate-templates/goad-mssql-base/scripts/08-cleanup.ps1 b/warpgate-templates/goad-mssql-base/scripts/08-cleanup.ps1 deleted file mode 100644 index 00c98077..00000000 --- a/warpgate-templates/goad-mssql-base/scripts/08-cleanup.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -Write-Host "Cleaning up for AMI creation..." - -# Clear Windows Update download cache -Stop-Service -Name wuauserv -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\SoftwareDistribution\Download\*" -Recurse -Force -ErrorAction SilentlyContinue -Start-Service -Name wuauserv - -# Keep SQL installer files (in case reinstall needed) -# Remove-Item -Path "C:\setup" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear temp files -Remove-Item -Path "$env:TEMP\*" -Recurse -Force -ErrorAction SilentlyContinue -Remove-Item -Path "C:\Windows\Temp\*" -Recurse -Force -ErrorAction SilentlyContinue - -# Clear event logs -wevtutil cl Application -wevtutil cl Security -wevtutil cl System - -Write-Host "Cleanup complete" diff --git a/warpgate-templates/goad-mssql-base/warpgate.yaml b/warpgate-templates/goad-mssql-base/warpgate.yaml index 29ee7824..8050124a 100644 --- a/warpgate-templates/goad-mssql-base/warpgate.yaml +++ b/warpgate-templates/goad-mssql-base/warpgate.yaml @@ -26,9 +26,16 @@ variables: ami_name_filter: "Windows_Server-2019-English-Full-Base-*" provisioners: - # Provision with Ansible via AWS SSM - type: ansible - playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base.yml + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_setup.yml + galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml + extra_vars: + ansible_connection: aws_ssm + ansible_shell_type: powershell + ansible_aws_ssm_bucket_name: "" + ansible_aws_ssm_region: "${aws_region}" + - type: ansible + playbook_path: ${PROVISION_REPO_PATH}/ansible/playbooks/base/mssql_base_sql.yml galaxy_file: ${PROVISION_REPO_PATH}/ansible/requirements.yml extra_vars: ansible_connection: aws_ssm