Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
9527b8b
feat(score): add live verification and standalone score command
mkultraWasHere Jul 12, 2026
d728855
fix(score): harden live verification checks
mkultraWasHere Jul 13, 2026
0b73f1d
feat(score): add Azure Bastion SSH support for live verification
mkultraWasHere Jul 13, 2026
ecb5587
fix(score): harden Azure BastionShellRunner
mkultraWasHere Jul 13, 2026
e50c563
feat(score): auto-discover Azure Bastion, Kali VM, and SSH key
mkultraWasHere Jul 13, 2026
0709974
docs: add scoring.md and update scoreboard.md references
mkultraWasHere Jul 13, 2026
ff89605
fix(score): use NetBIOS names for DCSync, remove technique scoring, a…
mkultraWasHere Jul 13, 2026
b37b6eb
fix: address Copilot PR review feedback
mkultraWasHere Jul 13, 2026
3db602e
fix(score): stderr summary, failed check reporting, filter krbtgt can…
mkultraWasHere Jul 13, 2026
1bc5463
fix: align remaining /tmp/report.jsonl references to ./report.jsonl
mkultraWasHere Jul 13, 2026
8b631de
fix(score): emit failed_check for missing dc_ip on live_auth, validat…
mkultraWasHere Jul 13, 2026
73c27c2
fix(score): improve error when Azure resource ID used without -p azure
mkultraWasHere Jul 13, 2026
3b9d9bc
fix: guard parseNXCOutput against empty username and fix FailedCheck …
mkultraWasHere Jul 13, 2026
407d7f5
fix(score): guard empty username, deduplicate cred matching, align SS…
mkultraWasHere Jul 13, 2026
e0572e3
fix(variant): re-encrypt PowerShell SecureStrings with mapped passwords
mkultraWasHere Jul 13, 2026
705f4ff
fix(score): local auth fallback for host admin checks and nxc flag co…
mkultraWasHere Jul 13, 2026
fcd3a88
fix(variant): randomize share names to prevent GOAD fingerprinting
mkultraWasHere Jul 14, 2026
b38aed5
fix(score): fallback to admin_users list when findings lack hostname tag
mkultraWasHere Jul 14, 2026
e516088
feat(score): add reset subcommand for between-run range cleanup
mkultraWasHere Jul 15, 2026
48454a9
fix(score): add rogue account detection, group membership diff, and e…
mkultraWasHere Jul 15, 2026
8a1da73
fix(score): replace extension-based /tmp cleanup with catch-all approach
mkultraWasHere Jul 16, 2026
06bfd8c
chore(scoreboard): add second agent prompt variant
mkultraWasHere Jul 16, 2026
a2d1024
fix(validate): distinguish transport errors from empty results in Win…
mkultraWasHere Jul 16, 2026
e6e0fcd
fix: remove design doc, scrub personal paths from docs and reset command
mkultraWasHere Jul 24, 2026
a427d09
refactor: address PR code review findings across 11 Go files
mkultraWasHere Jul 25, 2026
0fd4463
test(scoreboard,variant): add 65 test cases for scoring, live verific…
mkultraWasHere Jul 25, 2026
015c191
fix: address PR feedback — add missing tpl, fix warning semantics, us…
mkultraWasHere Jul 26, 2026
bb08292
docs(variant): fix decryptSecureString format comment to include vers…
mkultraWasHere Jul 26, 2026
80178dd
style: fix pre-commit lint failures (gofmt, errcheck, trailing newlines)
mkultraWasHere Jul 26, 2026
81cde04
refactor: fix pre-commit failures (gocyclo, errcheck, staticcheck, go…
mkultraWasHere Jul 26, 2026
f9b9cbf
fix: resolve remaining pre-commit failures (staticcheck, gocyclo)
mkultraWasHere Jul 26, 2026
2b21456
style: apply De Morgan's law to satisfy staticcheck QF1001
mkultraWasHere Jul 26, 2026
e85f15b
style: extract isText variable to avoid staticcheck QF1001
mkultraWasHere Jul 26, 2026
60f0ef5
Merge branch 'main' into feat/live-verification
mkultraWasHere Jul 26, 2026
8c9c964
style: apply pre-commit auto-fixes (markdownlint, terraform lock hashes)
mkultraWasHere Jul 26, 2026
e27ab7a
fix: resolve ACL validation false warnings and Bastion SSH script fai…
mkultraWasHere Jul 28, 2026
c349167
feat(kali): install scoring deps and recon tools via cloud-init
mkultraWasHere Jul 28, 2026
32b5563
fix(validate): retry ACL checks on inconclusive WinRM output
mkultraWasHere Jul 29, 2026
6ff735b
Merge branch 'main' into feat/live-verification
mkultraWasHere Aug 2, 2026
c9fc726
Merge branch 'main' into feat/live-verification
mkultraWasHere Aug 2, 2026
bf61366
fix(validate): extract checkSingleACL to fix gocyclo threshold
mkultraWasHere Aug 2, 2026
a555ec6
fix(ci): skip terraform_validate in manual-stage pre-commit step
mkultraWasHere Aug 2, 2026
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
4 changes: 4 additions & 0 deletions .github/workflows/pre-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ jobs:
- name: Run check-only hooks
id: precommit-check
if: ${{ !cancelled() }}
env:
# terraform_validate has no stages restriction, so pre-commit
# runs it in the manual stage too. Keep it skipped here as well.
SKIP: terraform_validate
run: |
pre-commit run --show-diff-on-failure --color=always --all-files --hook-stage manual

Expand Down
4 changes: 2 additions & 2 deletions ad/GOAD-variant-1/files/dc02/sysvol_scripts/secret.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@

# secret stored :
$keyData = 177, 252, 228, 64, 28, 91, 12, 201, 20, 91, 21, 139, 255, 65, 9, 247, 41, 55, 164, 28, 75, 132, 143, 71, 62, 191, 211, 61, 154, 61, 216, 91
$secret="76492d1116743f0423413b16050a5345MgB8AGkAcwBDACsAUwArADIAcABRAEcARABnAGYAMwA3AEEAcgBFAEIAYQB2AEEAPQA9AHwAZQAwADgANAA2ADQAMABiADYANAAwADYANgA1ADcANgAxAGIAMQBhAGQANQBlAGYAYQBiADQAYQA2ADkAZgBlAGQAMQAzADAANQAyADUAMgAyADYANAA3ADAAZABiAGEAOAA0AGUAOQBkAGMAZABmAGEANAAyADkAZgAyADIAMwA="
$secret="76492d1116743f0423413b16050a5345MgB8ADIAWQBrAHkAUABYAHMAMABRAEgAWgA0AGMAZwB1ADgAVgBDAC8AMgBCAGcAPQA9AHwANgBjAGYAYgBlAGIANwBlADEAMQAyADUAMgBlADYAYQA1ADYAYwAyAGQAZgA4AGEAYgAwADgAMgAyAGEAYQAzAGMAZgA5ADQANwBjADIANgAzAGYAMQBkAGIAYQBiAGIAYQAyADgAZAA1AGEAOAAyADUAZAA4ADgAYgBhADAAYgA="

# T.L.
# B.J.
107 changes: 35 additions & 72 deletions cli/cmd/bastion.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,37 +108,54 @@ func azureClientFromProvider(prov provider.Provider) (*azure.Client, error) {
return ap.Client(), nil
}

func bastionContext(ctx context.Context) (*azure.Client, *azure.BastionHost, *config.Config, error) {
func bastionContext(ctx context.Context) (*azure.Client, *azure.BastionHost, *config.Config, provider.Provider, error) {
cfg, err := config.Get()
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
}
prov, err := cfg.NewProvider(ctx)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
}
client, err := azureClientFromProvider(prov)
if err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
}
if _, err := client.VerifyCredentials(ctx); err != nil {
return nil, nil, nil, err
return nil, nil, nil, nil, err
}
host, err := client.DiscoverBastion(ctx, cfg.Env)
if err != nil {
return nil, nil, nil, fmt.Errorf("discover bastion: %w", err)
return nil, nil, nil, nil, fmt.Errorf("discover bastion: %w", err)
}
if host == nil {
return nil, nil, nil, fmt.Errorf(
return nil, nil, nil, nil, fmt.Errorf(
"no Azure Bastion host found for env=%s. Deploy with: "+
"DREADGOAD_ENABLE_AZURE_BASTION=true dreadgoad infra apply --module bastion "+
"(or use --with-bastion on infra apply)", cfg.Env)
}
return client, host, cfg, nil
return client, host, cfg, prov, nil
}

// bastionWithVM resolves the bastion, verifies tunneling, and looks up the
// target VM. Common setup shared by ssh, rdp, and tunnel subcommands.
func bastionWithVM(ctx context.Context, host string) (*azure.Client, *azure.BastionHost, *config.Config, string, error) {
client, bh, cfg, prov, err := bastionContext(ctx)
if err != nil {
return nil, nil, nil, "", err
}
if !bh.TunnelingEnabled {
return nil, nil, nil, "", fmt.Errorf("bastion %s does not have tunneling enabled; requires bastion_tunneling_enabled=true on a Standard/Premium SKU", bh.Name)
}
vmID, err := resolveAzureHost(ctx, prov, cfg, host)
if err != nil {
return nil, nil, nil, "", err
}
return client, bh, cfg, vmID, nil
}

func runBastionStatus(cmd *cobra.Command, args []string) error {
ctx := context.Background()
ctx := cmd.Context()
cfg, err := config.Get()
if err != nil {
return err
Expand Down Expand Up @@ -179,20 +196,8 @@ func runBastionStatus(cmd *cobra.Command, args []string) error {
}

func runBastionSSH(cmd *cobra.Command, args []string) error {
ctx := context.Background()
client, host, cfg, err := bastionContext(ctx)
if err != nil {
return err
}
if !host.TunnelingEnabled {
return fmt.Errorf("bastion %s does not have tunneling enabled; ssh requires bastion_tunneling_enabled=true on a Standard/Premium SKU", host.Name)
}

prov, err := cfg.NewProvider(ctx)
if err != nil {
return err
}
vmID, err := resolveAzureHost(ctx, prov, cfg, args[0])
ctx := cmd.Context()
client, host, cfg, vmID, err := bastionWithVM(ctx, args[0])
if err != nil {
return err
}
Expand All @@ -203,7 +208,7 @@ func runBastionSSH(cmd *cobra.Command, args []string) error {

// Auto-pick the ephemeral key for known VM roles. A failed live lookup
// is non-fatal — we just fall back to the user-supplied flag values.
if defaults := resolveRoleDefaults(client, ctx, cfg.Env, args[0]); defaults != nil && defaults.sshKey != "" {
if defaults := resolveRoleDefaults(ctx, client, cfg.Env, args[0]); defaults != nil && defaults.sshKey != "" {
if !cmd.Flags().Changed("auth-type") {
authType = defaults.authType
}
Expand All @@ -228,7 +233,7 @@ type roleDefaults struct {

// resolveRoleDefaults looks up a VM by hostname and returns SSH defaults based
// on its Role tag. Returns nil if the VM is not found or has no known role.
func resolveRoleDefaults(client *azure.Client, ctx context.Context, env, hostname string) *roleDefaults {
func resolveRoleDefaults(ctx context.Context, client *azure.Client, env, hostname string) *roleDefaults {
inst, err := client.FindInstanceByHostname(ctx, env, hostname)
if err != nil {
return nil
Expand All @@ -244,7 +249,7 @@ func resolveRoleDefaults(client *azure.Client, ctx context.Context, env, hostnam
return &roleDefaults{
authType: "ssh-key",
user: "kali",
sshKey: kaliKeyPath(env, inst.Name),
sshKey: azure.KaliKeyPath(env, inst.Name),
}
default:
return nil
Expand Down Expand Up @@ -272,40 +277,9 @@ func controllerKeyPath(env, vmName string) string {
return path
}

// kaliKeyPath derives the conventional ephemeral private-key path the
// terraform-azure-kali module writes. VM names follow
// "{env}-{deployment}-kali-vm"; the module writes to
// "~/.dreadgoad/keys/azure-{env}-{deployment}-kali".
func kaliKeyPath(env, vmName string) string {
deployment := strings.TrimSuffix(strings.TrimPrefix(vmName, env+"-"), "-kali-vm")
if deployment == "" || deployment == vmName {
return ""
}
home, err := os.UserHomeDir()
if err != nil {
return ""
}
path := filepath.Join(home, ".dreadgoad", "keys", fmt.Sprintf("azure-%s-%s-kali", env, deployment))
if _, err := os.Stat(path); err != nil {
return ""
}
return path
}

func runBastionRDP(cmd *cobra.Command, args []string) error {
ctx := context.Background()
client, host, cfg, err := bastionContext(ctx)
if err != nil {
return err
}
if !host.TunnelingEnabled {
return fmt.Errorf("bastion %s does not have tunneling enabled; rdp requires bastion_tunneling_enabled=true on a Standard/Premium SKU", host.Name)
}
prov, err := cfg.NewProvider(ctx)
if err != nil {
return err
}
vmID, err := resolveAzureHost(ctx, prov, cfg, args[0])
ctx := cmd.Context()
client, host, _, vmID, err := bastionWithVM(ctx, args[0])
if err != nil {
return err
}
Expand All @@ -315,19 +289,8 @@ func runBastionRDP(cmd *cobra.Command, args []string) error {
}

func runBastionTunnel(cmd *cobra.Command, args []string) error {
ctx := context.Background()
client, host, cfg, err := bastionContext(ctx)
if err != nil {
return err
}
if !host.TunnelingEnabled {
return fmt.Errorf("bastion %s does not have tunneling enabled; tunnel requires bastion_tunneling_enabled=true on a Standard/Premium SKU", host.Name)
}
prov, err := cfg.NewProvider(ctx)
if err != nil {
return err
}
vmID, err := resolveAzureHost(ctx, prov, cfg, args[0])
ctx := cmd.Context()
client, host, _, vmID, err := bastionWithVM(ctx, args[0])
if err != nil {
return err
}
Expand Down
Loading
Loading