package server import ( "context" "flag" "fmt" "net/http" "os" "github.com/gin-contrib/requestid" "github.com/gin-gonic/gin" "github.com/go-logr/logr" "github.com/peterbourgon/ff/v3" "edge-infra.dev/pkg/edge/api/middleware" eamiddleware "edge-infra.dev/pkg/sds/emergencyaccess/middleware" rulesengine "edge-infra.dev/pkg/sds/emergencyaccess/rules" "edge-infra.dev/pkg/sds/emergencyaccess/rules/setup" ) type RulesEngine interface { GetEARolesForCommand(ctx context.Context, command rulesengine.Command, bannerID string) ([]string, error) UserHasRoles(_ string, eaRoles []string, userEARoles []string) bool AddCommands(ctx context.Context, names []rulesengine.PostCommandPayload) (rulesengine.AddNameResult, error) AddPrivileges(ctx context.Context, privs []rulesengine.PostPrivilegePayload) (rulesengine.AddNameResult, error) AddBannerRules(ctx context.Context, bannerName string, rules rulesengine.WriteRules) (rulesengine.AddRuleResult, error) AddDefaultRules(ctx context.Context, rules rulesengine.WriteRules) (rulesengine.AddRuleResult, error) DeleteCommand(ctx context.Context, name string) (rulesengine.DeleteResult, error) DeletePrivilege(ctx context.Context, name string) (rulesengine.DeleteResult, error) DeleteDefaultRule(ctx context.Context, commandName, privilegeName string) (rulesengine.DeleteResult, error) DeletePrivilegeFromBannerRule(ctx context.Context, bannerName, commandName, privilegeName string) (rulesengine.DeleteResult, error) ReadCommands(ctx context.Context) ([]rulesengine.Command, error) ReadCommand(ctx context.Context, name string) (rulesengine.Command, error) ReadPrivileges(ctx context.Context) ([]rulesengine.Privilege, error) ReadPrivilege(ctx context.Context, name string) (rulesengine.Privilege, error) ReadAllDefaultRules(ctx context.Context) ([]rulesengine.Rule, error) ReadDefaultRulesForCommand(ctx context.Context, commandName string) ([]rulesengine.Rule, error) ReadRulesForBanner(ctx context.Context, bannerName string) ([]rulesengine.Rule, error) ReadRulesForAllBanners(ctx context.Context) ([]rulesengine.ReadBannerRule, error) ReadBannerRulesForCommand(ctx context.Context, commandName string) (rulesengine.ReadBannerRule, error) ReadBannerRulesForCommandAndBanner(ctx context.Context, bannerName string, commandName string) (rulesengine.Rule, error) ReadAllRulesForCommand(ctx context.Context, commandName string) (rulesengine.RuleWithOverrides, error) } type RulesEngineService struct { GinEngine *gin.Engine RulesEngine RulesEngine logger logr.Logger } func (res RulesEngineService) newGinServer(checks ...func() error) { router := res.GinEngine router.ContextWithFallback = true // Http logging router.Use(middleware.SetRequestContext()) router.Use(gin.Recovery()) router.Any("/ready", func(c *gin.Context) { c.String(http.StatusOK, "ok") }) router.Any("/health", eamiddleware.HealthCheck(checks...)) public := router.Group("/") public.Use(requestid.New(requestid.WithCustomHeaderStrKey(eamiddleware.CorrelationIDKey))) public.Use(eamiddleware.SetLoggerInContext(res.logger)) public.Use(eamiddleware.RequestBookendLogs()) public.POST("/validatecommand", res.validateCommand) admin := public.Group("/admin") // commands admin.POST("/commands", res.postCommands) admin.DELETE("/commands/:name", res.deleteCommand) admin.GET("/commands", res.readCommands) admin.GET("commands/:name", res.readCommand) // privileges admin.POST("/privileges", res.postPrivileges) admin.DELETE("/privileges/:name", res.deletePrivilege) admin.GET("/privileges", res.readPrivileges) admin.GET("/privileges/:name", res.readPrivilege) // default rules admin.POST("/rules/default/commands", res.postDefaultRules) admin.DELETE("/rules/default/commands/:commandName/privileges/:privilegeName", res.deleteDefaultRule) admin.GET("/rules/default/commands", res.readAllDefaultRules) admin.GET("/rules/default/commands/:commandName", res.readDefaultRule) // Banner rules admin.POST("/rules/banner/commands", res.postRulesInBanner) admin.GET("/rules/banner/commands", res.readAllRulesForBanners) admin.GET("/rules/banner/commands/:commandName", res.readBannerRulesForCommand) admin.DELETE("/rules/banner/commands/:commandName/privileges/:privilegeName", res.deletePrivilegeFromBannerRule) // overview of rules admin.GET("/rules/commands/:commandName", res.readAllRulesForCommand) } func New(router *gin.Engine, rulesengine RulesEngine, logger logr.Logger, checks ...func() error) (RulesEngineService, error) { res := RulesEngineService{ RulesEngine: rulesengine, GinEngine: router, logger: logger, } res.newGinServer(checks...) return res, nil } // Run initialises the rules engine and calls GinEngine.Run. func Run() error { log := newLogger() flags := flag.NewFlagSet("ea-rules", flag.ExitOnError) conf := setup.RulesConfig{} conf.BindFlags(flags) if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil { return fmt.Errorf("failed to parse arguments: %w", err) } router := gin.New() ds, checks, err := setup.CreateDataset(log, conf) if err != nil { return err } rulesengine := rulesengine.New(ds) res, err := New(router, rulesengine, log, checks...) if err != nil { return err } return res.GinEngine.Run() }