Skip to content

Commit 9bb1000

Browse files
authored
Merge pull request #18 from theopenlane/feat-configandintel
fix up the config setup, add new pipeline
2 parents 945d7fb + 7180b71 commit 9bb1000

File tree

128 files changed

+372
-5953894
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

128 files changed

+372
-5953894
lines changed

.buildkite/pipeline.yaml

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,24 @@ steps:
7676
- "CGO_ENABLED=0"
7777
- "GOOS=linux"
7878
- "GOFLAGS=-buildvcs=false"
79+
- label: ":golang: build release"
80+
key: "gobuild-release"
81+
cancel_on_build_failing: true
82+
artifact_paths: "bin/${APP_NAME}"
83+
if: build.tag != null
84+
plugins:
85+
- docker#v5.13.0:
86+
image: "ghcr.io/theopenlane/build-image:latest"
87+
always-pull: true
88+
command: ["task", "go:build"]
89+
propagate-environment: true
90+
volumes:
91+
- "$GOCACHE:$GOCACHE"
92+
- "$GOMODCACHE:$GOMODCACHE"
93+
environment:
94+
- "CGO_ENABLED=0"
95+
- "GOOS=linux"
96+
- "GOFLAGS=-buildvcs=false"
7997
- group: ":docker: Docker Image"
8098
depends_on: "go-build"
8199
key: "docker-build"
@@ -117,7 +135,7 @@ steps:
117135
plugins:
118136
- artifacts#v1.9.4:
119137
download: "bin/${APP_NAME}"
120-
step: "gobuild"
138+
step: "gobuild-release"
121139
- docker-login#v3.0.0:
122140
username: openlane-bender
123141
password-env: SECRET_GHCR_PUBLISH_TOKEN
@@ -140,17 +158,87 @@ steps:
140158
depends_on: "docker-build-and-tag"
141159
if: build.tag != null
142160
plugins:
143-
- docker#v5.13.0:
144-
image: "ghcr.io/theopenlane/build-image:latest"
145-
always-pull: true
146-
command: ["sh", "-c", "apk add --no-cache bash >/dev/null 2>&1 && bash .buildkite/image-tag-automation.sh"]
147-
propagate-environment: true
148-
volumes:
149-
- "$GOCACHE:$GOCACHE"
150-
- "$GOMODCACHE:$GOMODCACHE"
151-
environment:
152-
- "GITHUB_TOKEN"
153-
- "GITHUB_SSH_PRIVATE_KEY"
154-
- "HELM_CHART_REPO"
155-
- "HELM_CHART_PATH"
156-
- "BUILDKITE_BUILD_CHECKOUT_PATH=/workdir"
161+
- theopenlane/git#v0.1.5:
162+
execute-phase: command
163+
repository: "${HELM_CHART_REPO}"
164+
branch: "sleuth-${BUILDKITE_TAG}"
165+
preset: helm-sync
166+
chart-name: sleuth
167+
chart-version-env: BUILDKITE_TAG
168+
auth:
169+
mode: https-token
170+
token-env: GITHUB_TOKEN
171+
user:
172+
name: theopenlane-bender
173+
email: bender@theopenlane.io
174+
sync:
175+
- to: values.yaml
176+
type: merge-yaml
177+
merge-target-path: .sleuth.image.tag
178+
merge-source-env: BUILDKITE_TAG
179+
commit-message: "chore: bump sleuth to ${BUILDKITE_TAG}"
180+
pr:
181+
enabled: true
182+
title: "chore: bump sleuth to ${BUILDKITE_TAG}"
183+
- group: ":helm: Helm Config"
184+
key: "helm-config"
185+
steps:
186+
- label: ":helm: draft helm config PR"
187+
key: "helm_config_draft_pr"
188+
if: build.pull_request.id != null
189+
if_changed:
190+
- config/helm-values.yaml
191+
plugins:
192+
- theopenlane/git#v0.1.5:
193+
execute-phase: command
194+
repository: "${HELM_CHART_REPO}"
195+
branch: "sleuth-config-${BUILDKITE_PULL_REQUEST}"
196+
preset: helm-sync
197+
chart-name: sleuth
198+
auth:
199+
mode: https-token
200+
token-env: GITHUB_TOKEN
201+
user:
202+
name: theopenlane-bender
203+
email: bender@theopenlane.io
204+
sync:
205+
- from: helm-values.yaml
206+
to: values.yaml
207+
type: merge-yaml
208+
merge-target-path: .sleuth.sleuthConfiguration
209+
merge-source-path: .sleuthConfiguration
210+
commit-message: "chore: update sleuth helm config from build #{{BUILD_NUMBER}}"
211+
pr:
212+
enabled: true
213+
draft: true
214+
title: "chore: update sleuth helm config (PR #{{SOURCE_PR_NUMBER}})"
215+
comment-on-source-pr: true
216+
source-comment: "Downstream helm chart draft PR: {{TARGET_PR_URL}}"
217+
- label: ":helm: update helm config"
218+
key: "helm_config_merge"
219+
if: build.branch == "main"
220+
if_changed:
221+
- config/helm-values.yaml
222+
plugins:
223+
- theopenlane/git#v0.1.5:
224+
execute-phase: command
225+
repository: "${HELM_CHART_REPO}"
226+
branch-prefix: "sleuth-config"
227+
preset: helm-sync
228+
chart-name: sleuth
229+
auth:
230+
mode: https-token
231+
token-env: GITHUB_TOKEN
232+
user:
233+
name: theopenlane-bender
234+
email: bender@theopenlane.io
235+
sync:
236+
- from: helm-values.yaml
237+
to: values.yaml
238+
type: merge-yaml
239+
merge-target-path: .sleuth.sleuthConfiguration
240+
merge-source-path: .sleuthConfiguration
241+
commit-message: "chore: update sleuth helm config from build #{{BUILD_NUMBER}}"
242+
pr:
243+
enabled: true
244+
title: "chore: update sleuth helm config (Build #{{BUILD_NUMBER}})"

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,5 @@ Thumbs.db
4141
*.log
4242
.tasktmp/
4343
.claude
44-
*.config*yaml
4544
config/.config.yaml
45+
data/

Taskfile.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ tasks:
9797
- task: specs:generate
9898
- task: client:generate
9999

100+
config:ci:
101+
desc: Regenerate config schema and example files for CI
102+
cmds:
103+
- task: config:schema
104+
100105
config:init:
101106
desc: Create a local .config.yaml from the example (run config:generate first)
102107
cmds:

cmd/serve.go

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -103,22 +103,11 @@ func serve(ctx context.Context) error {
103103

104104
// setupIntel initializes the threat intelligence manager from config
105105
func setupIntel(ctx context.Context, cfg *config.Config) (*intel.Manager, error) {
106-
feedCfg, err := intel.LoadFeedConfig(cfg.Intel.FeedConfig)
107-
if err != nil {
108-
return nil, fmt.Errorf("loading feed config from %s: %w", cfg.Intel.FeedConfig, err)
109-
}
110-
111-
intelClient := &http.Client{Timeout: cfg.Intel.RequestTimeout}
112-
113106
emailAuthAnalyzer := emailauth.NewAnalyzer()
114107
rdapClient := rdap.NewClient()
115108

116109
manager, err := intel.NewManager(
117-
feedCfg,
118-
intel.WithStorageDir(cfg.Intel.StorageDir),
119-
intel.WithHTTPClient(intelClient),
120-
intel.WithResolverTimeout(cfg.Intel.ResolverTimeout),
121-
intel.WithDNSCacheTTL(cfg.Intel.DNSCacheTTL),
110+
cfg.Intel,
122111
intel.WithEmailAuthAnalyzer(emailAuthAnalyzer),
123112
intel.WithRDAPAnalyzer(rdapClient),
124113
)
@@ -145,16 +134,7 @@ func setupIntel(ctx context.Context, cfg *config.Config) (*intel.Manager, error)
145134

146135
// setupScanner initializes the domain scanner from config
147136
func setupScanner(cfg *config.Config) (*scanner.Scanner, error) {
148-
opts := []scanner.ScanOption{
149-
scanner.WithVerbose(cfg.Scanner.Verbose),
150-
scanner.WithMaxSubdomains(cfg.Scanner.MaxSubdomains),
151-
}
152-
153-
if len(cfg.Scanner.NucleiSeverity) > 0 {
154-
opts = append(opts, scanner.WithNucleiSeverity(cfg.Scanner.NucleiSeverity))
155-
}
156-
157-
return scanner.New(opts...)
137+
return scanner.New(cfg.Scanner)
158138
}
159139

160140
// setupCloudflare initializes the Cloudflare client from config, returning nil when unconfigured
@@ -166,11 +146,7 @@ func setupCloudflare(cfg *config.Config) *cloudflare.Client {
166146
return nil
167147
}
168148

169-
client, err := cloudflare.New(
170-
cfg.Cloudflare.AccountID,
171-
cfg.Cloudflare.APIToken,
172-
cloudflare.WithHTTPClient(&http.Client{Timeout: cfg.Cloudflare.RequestTimeout}),
173-
)
149+
client, err := cloudflare.New(cfg.Cloudflare)
174150
if err != nil {
175151
log.Warn().Err(err).Msg("failed to initialize cloudflare client")
176152
return nil
@@ -195,10 +171,7 @@ func setupSlack(cfg *config.Config) *slack.Client {
195171
return nil
196172
}
197173

198-
client, err := slack.New(
199-
cfg.Slack.WebhookURL,
200-
slack.WithHTTPClient(&http.Client{Timeout: cfg.Slack.RequestTimeout}),
201-
)
174+
client, err := slack.New(cfg.Slack)
202175
if err != nil {
203176
log.Warn().Err(err).Msg("failed to initialize slack client")
204177
return nil

config/config.example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ scanner:
1515
- critical
1616
- high
1717
- medium
18+
- low
19+
- info
1820
timeout: 2m0s
1921
verbose: false
2022
server:

config/config.go

Lines changed: 8 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import (
1010
"github.com/knadh/koanf/v2"
1111
"github.com/mcuadros/go-defaults"
1212
"github.com/rs/zerolog/log"
13+
"github.com/theopenlane/sleuth/internal/cloudflare"
14+
"github.com/theopenlane/sleuth/internal/intel"
15+
"github.com/theopenlane/sleuth/internal/scanner"
16+
"github.com/theopenlane/sleuth/internal/slack"
1317
)
1418

1519
// envVarPrefix is the prefix for environment variables used to override config values
@@ -23,13 +27,13 @@ type Config struct {
2327
// Server contains HTTP server settings
2428
Server Server `json:"server" koanf:"server"`
2529
// Scanner contains domain scanner settings
26-
Scanner Scanner `json:"scanner" koanf:"scanner"`
30+
Scanner scanner.Config `json:"scanner" koanf:"scanner"`
2731
// Intel contains threat intelligence settings
28-
Intel Intel `json:"intel" koanf:"intel"`
32+
Intel intel.Config `json:"intel" koanf:"intel"`
2933
// Cloudflare contains Cloudflare API settings
30-
Cloudflare Cloudflare `json:"cloudflare" koanf:"cloudflare"`
34+
Cloudflare cloudflare.Config `json:"cloudflare" koanf:"cloudflare"`
3135
// Slack contains Slack webhook settings
32-
Slack Slack `json:"slack" koanf:"slack"`
36+
Slack slack.Config `json:"slack" koanf:"slack"`
3337
}
3438

3539
// Server contains HTTP server configuration
@@ -50,52 +54,6 @@ type Server struct {
5054
MaxBodySize int64 `json:"maxbodysize" koanf:"maxbodysize" default:"102400"`
5155
}
5256

53-
// Scanner contains domain scanner configuration
54-
type Scanner struct {
55-
// Timeout is the maximum duration for a scan operation
56-
Timeout time.Duration `json:"timeout" koanf:"timeout" default:"120s"`
57-
// MaxSubdomains is the maximum number of subdomains to enumerate
58-
MaxSubdomains int `json:"maxsubdomains" koanf:"maxsubdomains" default:"50"`
59-
// NucleiSeverity is the list of nuclei severity levels to scan for
60-
NucleiSeverity []string `json:"nucleiseverity" koanf:"nucleiseverity"`
61-
// Verbose enables verbose scanner output
62-
Verbose bool `json:"verbose" koanf:"verbose" default:"false"`
63-
}
64-
65-
// Intel contains threat intelligence configuration
66-
type Intel struct {
67-
// FeedConfig is the path to the feed configuration JSON file
68-
FeedConfig string `json:"feedconfig" koanf:"feedconfig" default:"config/feed_config.json"`
69-
// StorageDir is the directory for storing downloaded feed data
70-
StorageDir string `json:"storagedir" koanf:"storagedir" default:"data/intel"`
71-
// AutoHydrate enables automatic feed hydration on startup
72-
AutoHydrate bool `json:"autohydrate" koanf:"autohydrate" default:"false"`
73-
// RequestTimeout is the timeout for individual feed download requests
74-
RequestTimeout time.Duration `json:"requesttimeout" koanf:"requesttimeout" default:"90s"`
75-
// ResolverTimeout is the timeout for DNS lookups during scoring
76-
ResolverTimeout time.Duration `json:"resolvertimeout" koanf:"resolvertimeout" default:"10s"`
77-
// DNSCacheTTL is the TTL for cached DNS responses
78-
DNSCacheTTL time.Duration `json:"dnscachettl" koanf:"dnscachettl" default:"5m"`
79-
}
80-
81-
// Cloudflare contains Cloudflare API configuration
82-
type Cloudflare struct {
83-
// AccountID is the Cloudflare account identifier
84-
AccountID string `json:"accountid" koanf:"accountid"`
85-
// APIToken is the bearer token for Cloudflare API authentication
86-
APIToken string `json:"apitoken" koanf:"apitoken" sensitive:"true"`
87-
// RequestTimeout is the timeout for Cloudflare API requests
88-
RequestTimeout time.Duration `json:"requesttimeout" koanf:"requesttimeout" default:"30s"`
89-
}
90-
91-
// Slack contains Slack webhook configuration
92-
type Slack struct {
93-
// WebhookURL is the Slack incoming webhook URL
94-
WebhookURL string `json:"webhookurl" koanf:"webhookurl" sensitive:"true"`
95-
// RequestTimeout is the timeout for Slack webhook requests
96-
RequestTimeout time.Duration `json:"requesttimeout" koanf:"requesttimeout" default:"10s"`
97-
}
98-
9957
// Option configures the Config
10058
type Option func(*Config)
10159

config/config_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestNew(t *testing.T) {
4242
}
4343

4444
if cfg.Intel.StorageDir != "data/intel" {
45-
t.Errorf("expected default storage dir, got %s", cfg.Intel.StorageDir)
45+
t.Errorf("expected default storage dir data/intel, got %s", cfg.Intel.StorageDir)
4646
}
4747

4848
if cfg.Cloudflare.RequestTimeout != 30*time.Second {

config/helm-values.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
sleuthConfiguration:
2+
server:
3+
listen: ":17710"
4+
readtimeout: "30s"
5+
writetimeout: "3m0s"
6+
shutdowngraceperiod: "30s"
7+
maxbodysize: 102400
8+
scanner:
9+
timeout: "2m0s"
10+
maxsubdomains: 50
11+
nucleiseverity:
12+
- critical
13+
- high
14+
- medium
15+
verbose: false
16+
intel:
17+
feedconfig: "config/feed_config.json"
18+
storagedir: "data/intel"
19+
autohydrate: false
20+
requesttimeout: "1m30s"
21+
resolvertimeout: "10s"
22+
dnscachettl: "5m0s"
23+
cloudflare:
24+
requesttimeout: "30s"
25+
slack:
26+
requesttimeout: "10s"

0 commit comments

Comments
 (0)