Skip to content

Commit 8dd58f1

Browse files
committed
first version.
1 parent cab9c6b commit 8dd58f1

31 files changed

+3423
-0
lines changed

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "gomod"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"

.github/workflows/release.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: goreleaser
2+
3+
on:
4+
push:
5+
tags:
6+
- '*'
7+
8+
permissions: { }
9+
10+
jobs:
11+
goreleaser:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: write
15+
steps:
16+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
17+
with:
18+
fetch-depth: 0
19+
persist-credentials: false
20+
- run: git fetch --force --tags
21+
- uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b
22+
with:
23+
go-version: '>=1.24.2'
24+
cache: false
25+
- uses: goreleaser/goreleaser-action@90a3faa9d0182683851fbfa97ca1a2cb983bfca3
26+
with:
27+
distribution: goreleaser
28+
version: latest
29+
args: release --clean
30+
env:
31+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.golangci.yaml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
version: "2"
2+
linters:
3+
default: all
4+
settings:
5+
cyclop:
6+
max-complexity: 11
7+
depguard:
8+
rules:
9+
main:
10+
allow:
11+
- $gostd
12+
- github.com/aws/aws-sdk-go-v2/aws
13+
- github.com/aws/aws-sdk-go-v2/config
14+
- github.com/aws/aws-sdk-go-v2/service/ec2
15+
- github.com/aws/aws-sdk-go-v2/service/ec2/types
16+
- github.com/aws/aws-sdk-go-v2/service/rds
17+
- github.com/aws/aws-sdk-go-v2/service/ssm
18+
- github.com/aws/aws-sdk-go-v2/service/sts
19+
- golang.org/x/sync/errgroup
20+
exclusions:
21+
generated: disable
22+
rules:
23+
- path: main.go
24+
linters:
25+
- funlen
26+
- lll
27+
- path: _string.go
28+
linters:
29+
- gochecknoglobals
30+
- path: _test.go
31+
linters:
32+
- dupl
33+
- err113
34+
- exhaustruct
35+
- funlen
36+
- varnamelen
37+
formatters:
38+
enable:
39+
- gci
40+
- gofmt
41+
- gofumpt
42+
- goimports
43+
- golines

.goreleaser.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
version: 2
2+
before:
3+
hooks:
4+
- go mod tidy
5+
builds:
6+
- env:
7+
- CGO_ENABLED=0
8+
goos:
9+
- linux
10+
- darwin
11+
- windows
12+
goarch:
13+
- amd64
14+
- arm64
15+
checksum:
16+
name_template: 'checksums.txt'
17+
snapshot:
18+
version_template: "{{ incpatch .Version }}-next"
19+
changelog:
20+
sort: asc
21+
filters:
22+
exclude:
23+
- '^docs:'
24+
- '^test:'
25+
archives:
26+
- formats: ['tar.gz']
27+
files:
28+
- LICENSE

.pre-commit-config.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v5.0.0
4+
hooks:
5+
- id: check-case-conflict
6+
- id: trailing-whitespace
7+
- id: end-of-file-fixer
8+
- id: check-yaml
9+
- id: check-added-large-files
10+
- repo: https://github.com/gruntwork-io/pre-commit
11+
rev: v0.1.29
12+
hooks:
13+
- id: shellcheck
14+
- id: gofmt
15+
- repo: local
16+
hooks:
17+
- id: addlicense
18+
name: Add License
19+
entry: go tool -modfile=tools/go.mod addlicense -c 'variHQ OÜ' -l 'BSD-3-Clause' -s=only
20+
language: system
21+
types: [ file ]
22+
exclude: '\.(hcl|yaml|yml)$'
23+
- id: golangci-lint
24+
name: golangci-lint
25+
entry: go tool -modfile=tools/go.mod golangci-lint run
26+
language: system
27+
types: [ go ]
28+
pass_filenames: false

LICENSE

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
BSD 3-Clause License
2+
3+
Copyright (c) 2025, variHQ OÜ
4+
5+
Redistribution and use in source and binary forms, with or without
6+
modification, are permitted provided that the following conditions are met:
7+
8+
1. Redistributions of source code must retain the above copyright notice, this
9+
list of conditions and the following disclaimer.
10+
11+
2. Redistributions in binary form must reproduce the above copyright notice,
12+
this list of conditions and the following disclaimer in the documentation
13+
and/or other materials provided with the distribution.
14+
15+
3. Neither the name of the copyright holder nor the names of its
16+
contributors may be used to endorse or promote products derived from
17+
this software without specific prior written permission.
18+
19+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Readme.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# spark
2+
3+
> **Seeking Public AWS Resources and Kernels**
4+
5+
> [!NOTE]
6+
> command-line tool designed to identify public AWS resources—such as backups, AMIs, snapshots, and more—associated with
7+
> specific AWS accounts.
8+
9+
```shell
10+
$ spark -h
11+
Usage spark:
12+
-list-scanners
13+
list available resource types
14+
-region value
15+
AWS region to scan (can be specified multiple times)
16+
-scan value
17+
AWS resource type to scan (can be specified multiple times)
18+
-target string
19+
target AWS account ID (default "self")
20+
-verbose
21+
verbose log output
22+
-version
23+
show version
24+
25+
26+
$ spark -list-scanners
27+
AMI
28+
snapshotsEBS
29+
snapshotsRDS
30+
ssmDocument
31+
```
32+
33+
### Installation
34+
35+
#### From source
36+
37+
```shell
38+
# via the Go toolchain
39+
go install github.com/wakeful/spark
40+
```
41+
42+
#### Using a binary release
43+
44+
You can download a pre-built binary from the [release page](https://github.com/wakeful/spark/releases/latest) and add it
45+
to your user PATH.

app.go

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2025 variHQ OÜ
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"log/slog"
10+
"strings"
11+
12+
"github.com/aws/aws-sdk-go-v2/config"
13+
"github.com/aws/aws-sdk-go-v2/service/sts"
14+
"golang.org/x/sync/errgroup"
15+
)
16+
17+
// App represents a struct that provides functionality for interacting with the AWS services.
18+
type App struct {
19+
accountID string
20+
runners []runner
21+
stsClient stsClient
22+
}
23+
24+
func newApp(ctx context.Context, check []runnerType, regions []string) (*App, error) {
25+
if len(regions) == 0 {
26+
return nil, errEmptyRegion
27+
}
28+
29+
if len(check) == 0 {
30+
return nil, errEmptyCheck
31+
}
32+
33+
regions = uniqRegions(regions)
34+
35+
baseCfg, err := config.LoadDefaultConfig(ctx)
36+
if err != nil {
37+
return nil, fmt.Errorf("failed to load aws config, %w", err)
38+
}
39+
40+
stsCfg := baseCfg.Copy()
41+
stsCfg.Region = regions[0]
42+
43+
runners := make([]runner, 0)
44+
45+
for _, region := range regions {
46+
cfg := baseCfg.Copy()
47+
cfg.Region = region
48+
49+
for _, scan := range check {
50+
switch scan {
51+
case amiImage:
52+
runners = append(runners, newAMIImageScan(cfg))
53+
case ebsSnapshot:
54+
runners = append(runners, newEBSSnapshotRunner(cfg))
55+
case rdsSnapshot:
56+
runners = append(runners, newRDSSnapshotRunner(cfg))
57+
case ssmDocument:
58+
runners = append(runners, newSSMDocumentScan(cfg))
59+
}
60+
}
61+
}
62+
63+
return &App{
64+
accountID: "",
65+
runners: runners,
66+
stsClient: sts.NewFromConfig(stsCfg),
67+
}, nil
68+
}
69+
70+
// Run executes the scan process on the target using all available runners,
71+
// and returns the collected results or an error.
72+
func (a *App) Run(ctx context.Context, target string) ([]Result, error) {
73+
group, gCtx := errgroup.WithContext(ctx)
74+
group.SetLimit(1)
75+
76+
if strings.EqualFold(target, "self") {
77+
slog.Debug("replacing self with account ID",
78+
slog.String("accountID", a.accountID),
79+
)
80+
81+
target = a.accountID
82+
}
83+
84+
buffer := make(chan []Result, len(a.runners))
85+
86+
for _, scanRunner := range a.runners {
87+
group.Go(func() error {
88+
select {
89+
case <-gCtx.Done():
90+
return gCtx.Err()
91+
default:
92+
scanResults, err := scanRunner.scan(ctx, target)
93+
if err != nil {
94+
return err
95+
}
96+
97+
buffer <- scanResults
98+
}
99+
100+
return nil
101+
})
102+
}
103+
104+
if err := group.Wait(); err != nil {
105+
return nil, fmt.Errorf("failed to run all checks, %w", err)
106+
}
107+
108+
close(buffer)
109+
110+
var output []Result
111+
for item := range buffer {
112+
output = append(output, item...)
113+
}
114+
115+
return output, nil
116+
}
117+
118+
func (a *App) getAccountID(ctx context.Context) error {
119+
output, err := a.stsClient.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
120+
if err != nil {
121+
return fmt.Errorf("failed to get caller identity, %w", err)
122+
}
123+
124+
if *output.Account == "" {
125+
return errEmptyAccountID
126+
}
127+
128+
a.accountID = *output.Account
129+
130+
return nil
131+
}

0 commit comments

Comments
 (0)