Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .ansible-lint

This file was deleted.

1 change: 1 addition & 0 deletions .hooks/linters/ansible-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
exclude_paths:
- .claude/
- .github/
- infra/
# Files with unskippable load-failure (unicode/runtime) or syntax-check issues
- ad/GOAD-Light/files/dc01/templates/
- ad/GOAD-Mini/files/dc01/templates/
Expand Down
9 changes: 6 additions & 3 deletions cli/cmd/ami.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"gopkg.in/yaml.v3"
)

var envVarPattern = regexp.MustCompile(`\$\{([^}]+)\}`)

var amiCmd = &cobra.Command{
Use: "ami",
Short: "AMI image management",
Expand Down Expand Up @@ -463,11 +465,12 @@ func loadWarpgateTemplate(path, projectRoot string) (*builder.Config, error) {
}

if _, ok := os.LookupEnv("PROVISION_REPO_PATH"); !ok && projectRoot != "" {
_ = os.Setenv("PROVISION_REPO_PATH", projectRoot)
if err := os.Setenv("PROVISION_REPO_PATH", projectRoot); err != nil {
return nil, fmt.Errorf("set PROVISION_REPO_PATH: %w", err)
}
}

varPattern := regexp.MustCompile(`\$\{([^}]+)\}`)
content = varPattern.ReplaceAllStringFunc(content, func(match string) string {
content = envVarPattern.ReplaceAllStringFunc(content, func(match string) string {
varName := match[2 : len(match)-1]
if val, ok := os.LookupEnv(varName); ok {
return val
Expand Down
66 changes: 63 additions & 3 deletions cli/cmd/env_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"io/fs"
"os"
"path/filepath"
"regexp"
"strings"

"github.com/dreadnode/dreadgoad/internal/config"
Expand All @@ -31,6 +32,7 @@ Creates:
- infra/goad-deployment/{env}/{region}/network/terragrunt.hcl
- infra/goad-deployment/{env}/{region}/goad/{host}/terragrunt.hcl + templates
- ad/GOAD/data/{env}-config.json
- {env}-inventory (Ansible inventory with PENDING instance IDs)

Use --variant to generate randomized entity names for the environment config.
Without --variant, the base config (dev-config.json) is copied as-is.`,
Expand Down Expand Up @@ -145,15 +147,23 @@ func runEnvCreate(cmd *cobra.Command, args []string) error {
color.Green(" Created config: %s-config.json", envName)
}

invPath := filepath.Join(cfg.ProjectRoot, envName+"-inventory")
if err := generateInventory(cfg.ProjectRoot, envName, region, reference); err != nil {
return fmt.Errorf("generate inventory: %w", err)
}
color.Green(" Created inventory: %s", filepath.Base(invPath))

fmt.Println()
color.Green("Environment %q created successfully!", envName)
fmt.Println()
fmt.Println("Next steps:")
fmt.Printf(" 1. Review: %s\n", envDir)
fmt.Printf(" 2. Review: %s\n", configPath)
fmt.Printf(" 3. Initialize: dreadgoad --env %s --region %s infra init\n", envName, region)
fmt.Printf(" 4. Plan: dreadgoad --env %s --region %s infra plan\n", envName, region)
fmt.Printf(" 5. Apply: dreadgoad --env %s --region %s infra apply --auto-approve\n", envName, region)
fmt.Printf(" 3. Review: %s\n", invPath)
fmt.Printf(" 4. Initialize: dreadgoad --env %s --region %s infra init\n", envName, region)
fmt.Printf(" 5. Plan: dreadgoad --env %s --region %s infra plan\n", envName, region)
fmt.Printf(" 6. Apply: dreadgoad --env %s --region %s infra apply --auto-approve\n", envName, region)
fmt.Printf(" 7. Sync IDs: dreadgoad --env %s --region %s inventory sync\n", envName, region)

return nil
}
Expand Down Expand Up @@ -315,6 +325,56 @@ func copyBaseConfig(projectRoot, envName string) error {
return os.WriteFile(dstPath, data, 0o644)
}

func generateInventory(projectRoot, envName, region, reference string) error {
refInvPath := filepath.Join(projectRoot, reference+"-inventory")
dstInvPath := filepath.Join(projectRoot, envName+"-inventory")

data, err := os.ReadFile(refInvPath)
if err != nil {
return fmt.Errorf("read reference inventory %s: %w", filepath.Base(refInvPath), err)
}
content := string(data)

// Extract reference env and region from the inventory vars
envRe := regexp.MustCompile(`(?m)^(\s*env=)(.+)$`)
regionRe := regexp.MustCompile(`(?m)^(\s*ansible_aws_ssm_region=)(.+)$`)
bucketRe := regexp.MustCompile(`(?m)^(\s*ansible_aws_ssm_bucket_name=)(.+)$`)
instanceRe := regexp.MustCompile(`(ansible_host=)i-[0-9a-f]+`)
ipFieldRe := regexp.MustCompile(`\s+(?:dc_ipv4|host_ipv4)=\S+`)

refEnv := reference
if m := envRe.FindStringSubmatch(content); len(m) > 2 {
refEnv = strings.TrimSpace(m[2])
}
refRegion := ""
if m := regionRe.FindStringSubmatch(content); len(m) > 2 {
refRegion = strings.TrimSpace(m[2])
}

// Replace env
content = envRe.ReplaceAllString(content, "${1}"+envName)

// Replace region
content = regionRe.ReplaceAllString(content, "${1}"+region)

// Replace bucket name: swap ref env/region for new env/region
if refRegion != "" {
if m := bucketRe.FindStringSubmatch(content); len(m) > 2 {
oldBucket := strings.TrimSpace(m[2])
newBucket := strings.Replace(oldBucket, refEnv+"-"+refRegion, envName+"-"+region, 1)
content = bucketRe.ReplaceAllString(content, "${1}"+newBucket)
}
}

// Replace instance IDs with PENDING placeholder
content = instanceRe.ReplaceAllString(content, "${1}PENDING")

// Strip dc_ipv4/host_ipv4 fields (will be populated after infra apply)
content = ipFieldRe.ReplaceAllString(content, "")

return os.WriteFile(dstInvPath, []byte(content), 0o644)
}

func generateVariantConfig(projectRoot, envName string) error {
source := filepath.Join(projectRoot, "ad", "GOAD")
target := filepath.Join(projectRoot, "ad", "GOAD-"+envName)
Expand Down
6 changes: 3 additions & 3 deletions cli/cmd/infra_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import (

var infraCmd = &cobra.Command{
Use: "infra",
Short: "Manage GOAD infrastructure via Terragrunt",
Long: `Manage the GOAD lab infrastructure lifecycle using Terragrunt.
Short: "Manage DreadGOAD infrastructure via Terragrunt",
Long: `Manage the DreadGOAD lab infrastructure lifecycle using Terragrunt.

Operates on the infra/ directory which contains Terragrunt configurations
for deploying the GOAD lab (VPC, EC2 instances, security groups, etc.).
for deploying the DreadGOAD lab (VPC, EC2 instances, security groups, etc.).

By default, commands operate on all modules (run-all). Use --module to
target a specific module (e.g. network, goad/dc01).`,
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/lab.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

var labCmd = &cobra.Command{
Use: "lab",
Short: "Manage GOAD lab lifecycle",
Short: "Manage DreadGOAD lab lifecycle",
}

var labStatusCmd = &cobra.Command{
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/lab_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

var labListCmd = &cobra.Command{
Use: "list",
Short: "List available GOAD labs and their providers",
Short: "List available DreadGOAD labs and their providers",
RunE: runLabList,
}

Expand Down
4 changes: 2 additions & 2 deletions cli/internal/ansible/logparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ func CheckAnsibleSuccess(output string) bool {
lines := strings.Split(output, "\n")
for i, line := range lines {
if strings.HasPrefix(line, "fatal:") {
// Check next 10 lines for "...ignoring"
end := i + 11
// Check next 20 lines for "...ignoring" (multi-line YAML output can be long)
end := i + 21
if end > len(lines) {
end = len(lines)
}
Expand Down
2 changes: 1 addition & 1 deletion cli/internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func setDefaults() {
viper.SetDefault("extensions.elk.impact", "add a linux machine and add a logbeat agent on all windows machines")
viper.SetDefault("extensions.elk.playbook", "ext-elk.yml")

viper.SetDefault("extensions.exchange.description", "Add an Exchange server to the GOAD lab")
viper.SetDefault("extensions.exchange.description", "Add an Exchange server to the DreadGOAD lab")
viper.SetDefault("extensions.exchange.machines", []string{"srv01"})
viper.SetDefault("extensions.exchange.compatibility", []string{"GOAD", "GOAD-Light", "GOAD-Mini"})
viper.SetDefault("extensions.exchange.impact", "modifies AD schema and adds a server (heavy)")
Expand Down
2 changes: 1 addition & 1 deletion cli/internal/lab/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"go.yaml.in/yaml/v3"
)

// Lab represents a discovered GOAD lab definition.
// Lab represents a discovered DreadGOAD lab definition.
type Lab struct {
Name string `json:"name"`
Path string `json:"path"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-*"] # warpgate-templates/goad-dc-base
name = "tag:Name"
values = ["goad-dc-base"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-*"] # warpgate-templates/goad-dc-base
name = "tag:Name"
values = ["goad-dc-base"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-2016-*"] # warpgate-templates/goad-dc-base-2016
name = "tag:Name"
values = ["goad-dc-base-2016"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-mssql-base-*"] # warpgate-templates/goad-mssql-base
name = "tag:Name"
values = ["goad-mssql-base"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-member-base-2016-*"] # warpgate-templates/goad-member-base-2016
name = "tag:Name"
values = ["goad-member-base-2016"]
}
]

Expand Down
4 changes: 2 additions & 2 deletions infra/goad-deployment/test/us-east-2/goad/dc01/terragrunt.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-*"] # warpgate-templates/goad-dc-base
name = "tag:Name"
values = ["goad-dc-base"]
}
]

Expand Down
4 changes: 2 additions & 2 deletions infra/goad-deployment/test/us-east-2/goad/dc02/terragrunt.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-*"] # warpgate-templates/goad-dc-base
name = "tag:Name"
values = ["goad-dc-base"]
}
]

Expand Down
4 changes: 2 additions & 2 deletions infra/goad-deployment/test/us-east-2/goad/dc03/terragrunt.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-dc-base-2016-*"] # warpgate-templates/goad-dc-base-2016
name = "tag:Name"
values = ["goad-dc-base-2016"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-mssql-base-*"] # warpgate-templates/goad-mssql-base
name = "tag:Name"
values = ["goad-mssql-base"]
}
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ inputs = {

additional_windows_ami_filters = [
{
name = "name"
values = ["goad-member-base-2016-*"] # warpgate-templates/goad-member-base-2016
name = "tag:Name"
values = ["goad-member-base-2016"]
}
]

Expand Down
10 changes: 5 additions & 5 deletions modules/terraform-aws-instance-factory/data.tf
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ data "aws_ami" "linux" {
}
}

# Only apply default name filter if no name filter exists in additional_linux_ami_filters
# Only apply default name filter if no name/tag:Name/image-id filter exists in additional_linux_ami_filters
dynamic "filter" {
for_each = length([for f in var.additional_linux_ami_filters : f if f.name == "name" || f.name == "image-id"]) > 0 ? [] : [1]
for_each = length([for f in var.additional_linux_ami_filters : f if f.name == "name" || f.name == "image-id" || f.name == "tag:Name"]) > 0 ? [] : [1]
content {
name = "name"
values = ["${var.linux_os}*${var.linux_os_version}*"]
Expand Down Expand Up @@ -77,9 +77,9 @@ data "aws_ami" "windows" {
}
}

# Only apply default name filter if no name filter exists in additional_windows_ami_filters
# Only apply default name filter if no name/tag:Name/image-id filter exists in additional_windows_ami_filters
dynamic "filter" {
for_each = length([for f in var.additional_windows_ami_filters : f if f.name == "name" || f.name == "image-id"]) > 0 ? [] : [1]
for_each = length([for f in var.additional_windows_ami_filters : f if f.name == "name" || f.name == "image-id" || f.name == "tag:Name"]) > 0 ? [] : [1]
content {
name = "name"
values = ["${var.windows_os}-${var.windows_os_version}*"]
Expand Down Expand Up @@ -128,7 +128,7 @@ data "aws_ami" "macos" {

# Only apply default name filter if no name filter exists in additional_macos_ami_filters
dynamic "filter" {
for_each = length([for f in var.additional_macos_ami_filters : f if f.name == "name" || f.name == "image-id"]) > 0 ? [] : [1]
for_each = length([for f in var.additional_macos_ami_filters : f if f.name == "name" || f.name == "image-id" || f.name == "tag:Name"]) > 0 ? [] : [1]
content {
name = "name"
values = ["${var.macos_os}*${var.macos_os_version}*"]
Expand Down
Loading