1717package config
1818
1919import (
20+ "context"
2021 "encoding/base64"
2122 "encoding/json"
2223 "errors"
2324 "fmt"
2425 "log/slog"
26+ "net"
27+ "net/url"
2528 "os"
2629 "path/filepath"
2730 "regexp"
2831 "strconv"
2932 "strings"
3033
34+ awsConfig "github.com/aws/aws-sdk-go-v2/config"
35+ "github.com/aws/aws-sdk-go-v2/feature/rds/auth"
36+
3137 "github.com/specterops/bloodhound/cmd/api/src/serde"
3238 "github.com/specterops/bloodhound/packages/go/bhlog/attr"
3339 "github.com/specterops/bloodhound/packages/go/crypto"
34- dawgs "github.com/specterops/dawgs/drivers"
3540)
3641
3742const (
@@ -51,6 +56,71 @@ func (s TLSConfiguration) Enabled() bool {
5156 return s .CertFile != "" && s .KeyFile != ""
5257}
5358
59+ type DatabaseConfiguration struct {
60+ Connection string `json:"connection"`
61+ Address string `json:"addr"`
62+ Database string `json:"database"`
63+ Username string `json:"username"`
64+ Secret string `json:"secret"`
65+ MaxConcurrentSessions int `json:"max_concurrent_sessions"`
66+ EnableRDSIAMAuth bool `json:"enable_rds_iam_auth"`
67+ }
68+
69+ func (s DatabaseConfiguration ) PostgreSQLConnectionString () string {
70+ if s .Connection != "" {
71+ return s .Connection
72+ }
73+
74+ return fmt .Sprintf ("postgresql://%s:%s@%s/%s" , s .Username , s .Secret , s .Address , s .Database )
75+ }
76+
77+ func (s DatabaseConfiguration ) Neo4JConnectionString () string {
78+ if s .Connection != "" {
79+ return s .Connection
80+ }
81+
82+ return fmt .Sprintf ("neo4j://%s:%s@%s/%s" , s .Username , s .Secret , s .Address , s .Database )
83+ }
84+
85+ func (s DatabaseConfiguration ) RDSIAMAuthConnectionString () string {
86+ if cfg , err := awsConfig .LoadDefaultConfig (context .TODO ()); err != nil {
87+ slog .Error ("AWS Config Loading Error" , slog .String ("err" , err .Error ()))
88+ } else {
89+ // Must use instance endpoint with IAM auth
90+ endpoint := s .LookupEndpoint ()
91+
92+ slog .Info ("Requesting RDS IAM Auth Token" )
93+
94+ if authenticationToken , err := auth .BuildAuthToken (context .TODO (), endpoint , cfg .Region , s .Username , cfg .Credentials ); err != nil {
95+ slog .Error ("RDS IAM Auth Token Request Error" , slog .String ("err" , err .Error ()))
96+ } else {
97+ slog .Info ("RDS IAM Auth Token Created" )
98+ return fmt .Sprintf ("postgresql://%s:%s@%s/%s" , s .Username , url .QueryEscape (authenticationToken ), endpoint , s .Database )
99+ }
100+ }
101+
102+ slog .Warn ("Failed to create IAM auth token. Falling back to default Postgres connection string" )
103+ return s .PostgreSQLConnectionString ()
104+ }
105+
106+ func (s DatabaseConfiguration ) LookupEndpoint () string {
107+ host , port , err := net .SplitHostPort (s .Address )
108+ if err != nil {
109+ slog .Warn ("Missing port in address. Using default port 5432." , slog .String ("err" , err .Error ()))
110+ host = s .Address
111+ port = "5432"
112+ }
113+
114+ if hostCName , err := net .DefaultResolver .LookupCNAME (context .TODO (), host ); err != nil {
115+ slog .Warn ("Error looking up CNAME for DB host. Using original address." , slog .String ("err" , err .Error ()))
116+ } else {
117+ host = hostCName
118+ }
119+
120+ // Instance endpoint always returns with a trailing '.'
121+ return net .JoinHostPort (strings .TrimSuffix (host , "." ), port )
122+ }
123+
54124type CollectorManifest struct {
55125 Latest string `json:"latest"`
56126 Versions []CollectorVersion `json:"versions"`
@@ -111,39 +181,39 @@ type DefaultAdminConfiguration struct {
111181}
112182
113183type Configuration struct {
114- Version int `json:"version"`
115- BindAddress string `json:"bind_addr"`
116- SlowQueryThreshold int64 `json:"slow_query_threshold"`
117- MaxGraphQueryCacheSize int `json:"max_graphdb_cache_size"`
118- MaxAPICacheSize int `json:"max_api_cache_size"`
119- MetricsPort string `json:"metrics_port"`
120- RootURL serde.URL `json:"root_url"`
121- WorkDir string `json:"work_dir"`
122- LogLevel string `json:"log_level"`
123- LogPath string `json:"log_path"`
124- TLS TLSConfiguration `json:"tls"`
125- GraphDriver string `json:"graph_driver"`
126- Database dawgs. DatabaseConfiguration `json:"database"`
127- Neo4J dawgs. DatabaseConfiguration `json:"neo4j"`
128- Crypto CryptoConfiguration `json:"crypto"`
129- SAML SAMLConfiguration `json:"saml"`
130- DefaultAdmin DefaultAdminConfiguration `json:"default_admin"`
131- CollectorsBucketURL serde.URL `json:"collectors_bucket_url"`
132- CollectorsBasePath string `json:"collectors_base_path"`
133- DatapipeInterval int `json:"datapipe_interval"`
134- EnableStartupWaitPeriod bool `json:"enable_startup_wait_period"`
135- EnableAPILogging bool `json:"enable_api_logging"`
136- EnableCypherMutations bool `json:"enable_cypher_mutations"`
137- DisableAnalysis bool `json:"disable_analysis"`
138- DisableCypherComplexityLimit bool `json:"disable_cypher_complexity_limit"`
139- DisableIngest bool `json:"disable_ingest"`
140- DisableMigrations bool `json:"disable_migrations"`
141- GraphQueryMemoryLimit uint16 `json:"graph_query_memory_limit"`
142- EnableTextLogger bool `json:"enable_text_logger"`
143- RecreateDefaultAdmin bool `json:"recreate_default_admin"`
144- EnableUserAnalytics bool `json:"enable_user_analytics"`
145- ForceDownloadEmbeddedCollectors bool `json:"force_download_embedded_collectors"`
146- EnableAuditLogStdout bool `json:"enable_audit_log_stdout"`
184+ Version int `json:"version"`
185+ BindAddress string `json:"bind_addr"`
186+ SlowQueryThreshold int64 `json:"slow_query_threshold"`
187+ MaxGraphQueryCacheSize int `json:"max_graphdb_cache_size"`
188+ MaxAPICacheSize int `json:"max_api_cache_size"`
189+ MetricsPort string `json:"metrics_port"`
190+ RootURL serde.URL `json:"root_url"`
191+ WorkDir string `json:"work_dir"`
192+ LogLevel string `json:"log_level"`
193+ LogPath string `json:"log_path"`
194+ TLS TLSConfiguration `json:"tls"`
195+ GraphDriver string `json:"graph_driver"`
196+ Database DatabaseConfiguration `json:"database"`
197+ Neo4J DatabaseConfiguration `json:"neo4j"`
198+ Crypto CryptoConfiguration `json:"crypto"`
199+ SAML SAMLConfiguration `json:"saml"`
200+ DefaultAdmin DefaultAdminConfiguration `json:"default_admin"`
201+ CollectorsBucketURL serde.URL `json:"collectors_bucket_url"`
202+ CollectorsBasePath string `json:"collectors_base_path"`
203+ DatapipeInterval int `json:"datapipe_interval"`
204+ EnableStartupWaitPeriod bool `json:"enable_startup_wait_period"`
205+ EnableAPILogging bool `json:"enable_api_logging"`
206+ EnableCypherMutations bool `json:"enable_cypher_mutations"`
207+ DisableAnalysis bool `json:"disable_analysis"`
208+ DisableCypherComplexityLimit bool `json:"disable_cypher_complexity_limit"`
209+ DisableIngest bool `json:"disable_ingest"`
210+ DisableMigrations bool `json:"disable_migrations"`
211+ GraphQueryMemoryLimit uint16 `json:"graph_query_memory_limit"`
212+ EnableTextLogger bool `json:"enable_text_logger"`
213+ RecreateDefaultAdmin bool `json:"recreate_default_admin"`
214+ EnableUserAnalytics bool `json:"enable_user_analytics"`
215+ ForceDownloadEmbeddedCollectors bool `json:"force_download_embedded_collectors"`
216+ EnableAuditLogStdout bool `json:"enable_audit_log_stdout"`
147217}
148218
149219func (s Configuration ) TempDirectory () string {
0 commit comments