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
25 changes: 14 additions & 11 deletions cmd/metal-api/internal/grpc/grpc-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type ServerConfig struct {
ResponseInterval time.Duration
CheckInterval time.Duration
BMCSuperUserPasswordFile string
Auditing auditing.Auditing
Auditing []auditing.Auditing
IPMISuperUser metal.MachineIPMISuperUser
}

Expand Down Expand Up @@ -121,7 +121,7 @@ func Run(cfg *ServerConfig) error {
logging.UnaryServerInterceptor(interceptorLogger(log)),
recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
}
if cfg.Auditing != nil {
if len(cfg.Auditing) > 0 {
shouldAudit := func(fullMethod string) bool {
switch fullMethod {
case "/api.v1.BootService/Register":
Expand All @@ -130,16 +130,19 @@ func Run(cfg *ServerConfig) error {
return false
}
}
auditStreamInterceptor, err := auditing.StreamServerInterceptor(cfg.Auditing, log.WithGroup("auditing-grpc"), shouldAudit)
if err != nil {
return err
}
auditUnaryInterceptor, err := auditing.UnaryServerInterceptor(cfg.Auditing, log.WithGroup("auditing-grpc"), shouldAudit)
if err != nil {
return err

for _, backend := range cfg.Auditing {
auditStreamInterceptor, err := auditing.StreamServerInterceptor(backend, log.WithGroup("auditing-grpc"), shouldAudit)
if err != nil {
return err
}
auditUnaryInterceptor, err := auditing.UnaryServerInterceptor(backend, log.WithGroup("auditing-grpc"), shouldAudit)
if err != nil {
return err
}
streamInterceptors = append(streamInterceptors, auditStreamInterceptor)
unaryInterceptors = append(unaryInterceptors, auditUnaryInterceptor)
}
streamInterceptors = append(streamInterceptors, auditStreamInterceptor)
unaryInterceptors = append(unaryInterceptors, auditUnaryInterceptor)
}

unaryInterceptors = append(unaryInterceptors, metrics.GrpcMetrics, recovery.UnaryServerInterceptor(recoveryOpt))
Expand Down
101 changes: 77 additions & 24 deletions cmd/metal-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ const (
DataStoreConnectTableInit dsConnectOpt = 0
// DataStoreConnectNoDemotion connects to the data store without demoting to runtime user in the end
DataStoreConnectNoDemotion dsConnectOpt = 1

auditingBackendTimescaleDB = "timescaledb"
auditingBackendMeilisearch = "meilisearch"
)

var (
Expand Down Expand Up @@ -287,11 +290,20 @@ func init() {
rootCmd.Flags().StringP("masterdata-certkeypath", "", "", "the tls certificate key to talk to the masterdata-api")

rootCmd.Flags().Bool("auditing-enabled", false, "enable auditing")
rootCmd.Flags().String("auditing-url", "http://localhost:7700", "url of the auditing service")
rootCmd.Flags().String("auditing-api-key", "secret", "api key for the auditing service")
rootCmd.Flags().String("auditing-index-prefix", "auditing", "auditing index prefix")
rootCmd.Flags().String("auditing-index-interval", "@daily", "auditing index creation interval, can be one of @hourly|@daily|@monthly")
rootCmd.Flags().Int64("auditing-keep", 14, "the amount of indexes to keep until cleanup")
rootCmd.Flags().String("auditing-search-backend", "", "the auditing backend used as a source for search in the audit service. if not specified the first one configured is picked given the following order of precedence: timescaledb,meilisearch")

rootCmd.Flags().String("auditing-meili-url", "http://localhost:7700", "url of the auditing service")
rootCmd.Flags().String("auditing-meili-api-key", "secret", "api key for the auditing service")
rootCmd.Flags().String("auditing-meili-index-prefix", "auditing", "auditing index prefix")
rootCmd.Flags().String("auditing-meili-index-interval", "@daily", "auditing index creation interval, can be one of @hourly|@daily|@monthly")
rootCmd.Flags().Int64("auditing-meili-keep", 14, "the amount of indexes to keep until cleanup")

rootCmd.Flags().String("auditing-timescaledb-host", "", "host of the auditing service")
rootCmd.Flags().String("auditing-timescaledb-port", "", "port of the auditing service")
rootCmd.Flags().String("auditing-timescaledb-db", "", "database name of the auditing service")
rootCmd.Flags().String("auditing-timescaledb-user", "", "user for the auditing service")
rootCmd.Flags().String("auditing-timescaledb-password", "", "password for the auditing service")
rootCmd.Flags().String("auditing-timescaledb-retention", "", "the time until audit traces are cleaned up")

rootCmd.Flags().String("headscale-addr", "", "address of headscale server")
rootCmd.Flags().String("headscale-cp-addr", "", "address of headscale control plane")
Expand Down Expand Up @@ -691,7 +703,7 @@ func initAuth(lg *slog.Logger) security.UserGetter {
return security.NewCreds(auths...)
}

func initRestServices(audit auditing.Auditing, withauth bool, ipmiSuperUser metal.MachineIPMISuperUser) *restfulspec.Config {
func initRestServices(searchAuditBackend auditing.Auditing, allAuditBackends []auditing.Auditing, withauth bool, ipmiSuperUser metal.MachineIPMISuperUser) *restfulspec.Config {
service.BasePath = viper.GetString("base-path")
if !strings.HasPrefix(service.BasePath, "/") || !strings.HasSuffix(service.BasePath, "/") {
log.Fatal("base path must start and end with a slash")
Expand Down Expand Up @@ -757,7 +769,7 @@ func initRestServices(audit auditing.Auditing, withauth bool, ipmiSuperUser meta
releaseVersion = pointer.Pointer(viper.GetString("release-version"))
}

restful.DefaultContainer.Add(service.NewAudit(logger.WithGroup("audit-service"), audit))
restful.DefaultContainer.Add(service.NewAudit(logger.WithGroup("audit-service"), searchAuditBackend))
restful.DefaultContainer.Add(service.NewPartition(logger.WithGroup("partition-service"), ds, nsqer))
restful.DefaultContainer.Add(service.NewImage(logger.WithGroup("image-service"), ds))
restful.DefaultContainer.Add(service.NewSize(logger.WithGroup("size-service"), ds, mdc))
Expand Down Expand Up @@ -790,18 +802,19 @@ func initRestServices(audit auditing.Auditing, withauth bool, ipmiSuperUser meta
restful.DefaultContainer.Filter(ensurer.EnsureAllowedTenantFilter)
}

if audit != nil {
for _, backend := range allAuditBackends {
filterOpt := auditing.NewHttpFilterErrorCallback(func(err error, response *restful.Response) {
httperr := httperrors.InternalServerError(err)
if err := response.WriteHeaderAndEntity(httperr.StatusCode, httperr); err != nil {
logger.Error("failed to send response", "error", err)
}
})

httpFilter, err := auditing.HttpFilter(audit, logger.WithGroup("audit-middleware"), filterOpt)
httpFilter, err := auditing.HttpFilter(backend, logger.WithGroup("audit-middleware"), filterOpt)
if err != nil {
log.Fatalf("unable to create http filter for auditing: %s", err)
}

restful.DefaultContainer.Filter(httpFilter) // FIXME
}

Expand Down Expand Up @@ -837,10 +850,9 @@ func initHeadscale() error {
}

func dumpSwaggerJSON() {

// This is required to make dump work
ipamer = ipam.New(nil)
cfg := initRestServices(nil, false, metal.DisabledIPMISuperUser())
cfg := initRestServices(nil, nil, false, metal.DisabledIPMISuperUser())
actual := restfulspec.BuildSwagger(*cfg)

// declare custom type for default errors, see:
Expand Down Expand Up @@ -915,33 +927,74 @@ func evaluateVPNConnected() error {
}

// might return (nil, nil) if auditing is disabled!
func createAuditingClient(log *slog.Logger) (auditing.Auditing, error) {
func createAuditingClient(log *slog.Logger) (searchBackend auditing.Auditing, backends []auditing.Auditing, err error) {
isEnabled := viper.GetBool("auditing-enabled")
if !isEnabled {
log.Warn("auditing is disabled, can be enabled by setting --auditing-enabled=true")
return nil, nil
return nil, nil, nil
}

return auditing.NewMeilisearch(auditing.Config{
c := auditing.Config{
Component: "metal-api",
Log: log,
}, auditing.MeilisearchConfig{
URL: viper.GetString("auditing-url"),
APIKey: viper.GetString("auditing-api-key"),
IndexPrefix: viper.GetString("auditing-index-prefix"),
RotationInterval: auditing.Interval(viper.GetString("auditing-index-interval")),
Keep: viper.GetInt64("auditing-keep"),
})
}

if viper.IsSet("auditing-timescaledb-host") {
backend, err := auditing.NewTimescaleDB(c, auditing.TimescaleDbConfig{
Host: viper.GetString("auditing-timescaledb-host"),
Port: viper.GetString("auditing-timescaledb-port"),
DB: viper.GetString("auditing-timescaledb-db"),
User: viper.GetString("auditing-timescaledb-user"),
Password: viper.GetString("auditing-timescaledb-password"),
Retention: viper.GetString("auditing-timescaledb-retention"),
})

if err != nil {
return nil, nil, err
}

backends = append(backends, backend)

if viper.GetString("auditing-search-backend") == auditingBackendTimescaleDB {
searchBackend = backend
}
}

if viper.IsSet("auditing-meili-api-key") {
backend, err := auditing.NewMeilisearch(c, auditing.MeilisearchConfig{
URL: viper.GetString("auditing-meili-url"),
APIKey: viper.GetString("auditing-meili-api-key"),
IndexPrefix: viper.GetString("auditing-meili-index-prefix"),
RotationInterval: auditing.Interval(viper.GetString("auditing-meili-index-interval")),
Keep: viper.GetInt64("auditing-meili-keep"),
})

if err != nil {
return nil, nil, err
}

backends = append(backends, backend)

if viper.GetString("auditing-search-backend") == auditingBackendMeilisearch {
searchBackend = backend
}
}

if searchBackend == nil {
searchBackend = pointer.FirstOrZero(backends)
}

return searchBackend, backends, nil
}

func run() error {
ipmiSuperUser := metal.NewIPMISuperUser(logger, viper.GetString("bmc-superuser-pwd-file"))

audit, err := createAuditingClient(logger)
auditSearchBackend, allAuditBackends, err := createAuditingClient(logger)
if err != nil {
log.Fatalf("cannot create auditing client:%s ", err)
}
initRestServices(audit, true, ipmiSuperUser)
initRestServices(auditSearchBackend, allAuditBackends, true, ipmiSuperUser)

// enable OPTIONS-request so clients can query CORS information
restful.DefaultContainer.Filter(restful.DefaultContainer.OPTIONSFilter)
Expand Down Expand Up @@ -1001,7 +1054,7 @@ func run() error {
ServerCertFile: viper.GetString("grpc-server-cert-file"),
ServerKeyFile: viper.GetString("grpc-server-key-file"),
BMCSuperUserPasswordFile: viper.GetString("bmc-superuser-pwd-file"),
Auditing: audit,
Auditing: allAuditBackends,
IPMISuperUser: ipmiSuperUser,
})
if err != nil {
Expand Down