diff --git a/cmd/spire-server/cli/cli.go b/cmd/spire-server/cli/cli.go index 93b5447cda9..4fdb631cea7 100644 --- a/cmd/spire-server/cli/cli.go +++ b/cmd/spire-server/cli/cli.go @@ -11,6 +11,7 @@ import ( "github.com/spiffe/spire/cmd/spire-server/cli/federation" "github.com/spiffe/spire/cmd/spire-server/cli/healthcheck" "github.com/spiffe/spire/cmd/spire-server/cli/jwt" + "github.com/spiffe/spire/cmd/spire-server/cli/logger" "github.com/spiffe/spire/cmd/spire-server/cli/run" "github.com/spiffe/spire/cmd/spire-server/cli/token" "github.com/spiffe/spire/cmd/spire-server/cli/validate" @@ -96,6 +97,12 @@ func (cc *CLI) Run(ctx context.Context, args []string) int { "federation update": func() (cli.Command, error) { return federation.NewUpdateCommand(), nil }, + "logger get": func() (cli.Command, error) { + return logger.NewGetCommand(), nil + }, + "logger set": func() (cli.Command, error) { + return logger.NewSetCommand(), nil + }, "run": func() (cli.Command, error) { return run.NewRunCommand(ctx, cc.LogOptions, cc.AllowUnknownConfig), nil }, diff --git a/cmd/spire-server/cli/logger/get.go b/cmd/spire-server/cli/logger/get.go new file mode 100644 index 00000000000..b6753a3df6f --- /dev/null +++ b/cmd/spire-server/cli/logger/get.go @@ -0,0 +1,59 @@ +package logger + +import ( + "context" + "flag" + "fmt" + + "github.com/mitchellh/cli" + api "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" + "github.com/spiffe/spire/cmd/spire-server/util" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/spiffe/spire/pkg/common/cliprinter" +) + +type getCommand struct { + env *commoncli.Env + printer cliprinter.Printer +} + +// Returns a cli.command that gets the logger information using +// the default cli environment. +func NewGetCommand() cli.Command { + return NewGetCommandWithEnv(commoncli.DefaultEnv) +} + +// Returns a cli.command that gets the root logger information. +func NewGetCommandWithEnv(env *commoncli.Env) cli.Command { + return util.AdaptCommand(env, &getCommand{env: env}) +} + +// The name of the command. +func (*getCommand) Name() string { + return "logger get" +} + +// The help presented description of the command. +func (*getCommand) Synopsis() string { + return "Gets the logger details" +} + +// Adds additional flags specific to the command. +func (c *getCommand) AppendFlags(fs *flag.FlagSet) { + cliprinter.AppendFlagWithCustomPretty(&c.printer, fs, c.env, c.prettyPrintLogger) +} + +// The routine that executes the command +func (c *getCommand) Run(ctx context.Context, _ *commoncli.Env, serverClient util.ServerClient) error { + logger, err := serverClient.NewLoggerClient().GetLogger(ctx, &api.GetLoggerRequest{}) + if err != nil { + return fmt.Errorf("error fetching logger: %w", err) + } + + return c.printer.PrintProto(logger) +} + +// Formatting for the logger under pretty printing of output. +func (c *getCommand) prettyPrintLogger(env *commoncli.Env, results ...any) error { + return PrettyPrintLogger(env, results...) +} diff --git a/cmd/spire-server/cli/logger/get_posix_test.go b/cmd/spire-server/cli/logger/get_posix_test.go new file mode 100644 index 00000000000..9e5cf4b3dbf --- /dev/null +++ b/cmd/spire-server/cli/logger/get_posix_test.go @@ -0,0 +1,12 @@ +//go:build !windows + +package logger_test + +var ( + getUsage = `Usage of logger get: + -output value + Desired output format (pretty, json); default: pretty. + -socketPath string + Path to the SPIRE Server API socket (default "/tmp/spire-server/private/api.sock") +` +) diff --git a/cmd/spire-server/cli/logger/get_test.go b/cmd/spire-server/cli/logger/get_test.go new file mode 100644 index 00000000000..b571f785b7d --- /dev/null +++ b/cmd/spire-server/cli/logger/get_test.go @@ -0,0 +1,178 @@ +package logger_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/cli/logger" +) + +func TestGetHelp(t *testing.T) { + test := setupCliTest(t, nil, logger.NewGetCommandWithEnv) + test.client.Help() + require.Equal(t, "", test.stdout.String()) + require.Equal(t, getUsage, test.stderr.String()) +} + +func TestGetSynopsis(t *testing.T) { + cmd := logger.NewGetCommand() + require.Equal(t, "Gets the logger details", cmd.Synopsis()) +} + +func TestGet(t *testing.T) { + for _, tt := range []struct { + name string + // server state + server *mockLoggerServer + // input + args []string + // expected items + expectReturnCode int + expectStdout string + expectStderr string + }{ + { + name: "configured to info, set to info, using pretty output", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_INFO, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectReturnCode: 0, + expectStdout: `Logger Level : info +Launch Level : info + +`, + }, + { + name: "configured to debug, set to warn, using pretty output", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_WARN, + LaunchLevel: types.LogLevel_DEBUG, + }, + }, + expectReturnCode: 0, + expectStdout: `Logger Level : warning +Launch Level : debug + +`, + }, + { + name: "configured to error, set to trace, using pretty output", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_TRACE, + LaunchLevel: types.LogLevel_ERROR, + }, + }, + expectReturnCode: 0, + expectStdout: `Logger Level : trace +Launch Level : error + +`, + }, + { + name: "configured to panic, set to fatal, using pretty output", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_FATAL, + LaunchLevel: types.LogLevel_PANIC, + }, + }, + expectReturnCode: 0, + expectStdout: `Logger Level : fatal +Launch Level : panic + +`, + }, + { + name: "configured to info, set to info, using json output", + args: []string{"-output", "json"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_INFO, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectReturnCode: 0, + expectStdout: `{"current_level":"INFO","launch_level":"INFO"} +`, + }, + { + name: "configured to debug, set to warn, using json output", + args: []string{"-output", "json"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_WARN, + LaunchLevel: types.LogLevel_DEBUG, + }, + }, + expectReturnCode: 0, + expectStdout: `{"current_level":"WARN","launch_level":"DEBUG"} +`, + }, + { + name: "configured to error, set to trace, using json output", + args: []string{"-output", "json"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_TRACE, + LaunchLevel: types.LogLevel_ERROR, + }, + }, + expectReturnCode: 0, + expectStdout: `{"current_level":"TRACE","launch_level":"ERROR"} +`, + }, + { + name: "configured to panic, set to fatal, using json output", + args: []string{"-output", "json"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_FATAL, + LaunchLevel: types.LogLevel_PANIC, + }, + }, + expectReturnCode: 0, + expectStdout: `{"current_level":"FATAL","launch_level":"PANIC"} +`, + }, + { + name: "configured to info, set to info, server will error", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnErr: errors.New("server is unavailable"), + }, + expectReturnCode: 1, + expectStderr: `Error: error fetching logger: rpc error: code = Unknown desc = server is unavailable +`, + }, + { + name: "bizzarro world, returns neither logger nor error", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: nil, + }, + expectReturnCode: 1, + expectStderr: `Error: internal error: returned current log level is undefined; please report this as a bug +`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + test := setupCliTest(t, tt.server, logger.NewGetCommandWithEnv) + returnCode := test.client.Run(append(test.args, tt.args...)) + require.Equal(t, tt.expectStdout, test.stdout.String()) + require.Equal(t, tt.expectStderr, test.stderr.String()) + require.Equal(t, tt.expectReturnCode, returnCode) + }) + } +} diff --git a/cmd/spire-server/cli/logger/get_windows_test.go b/cmd/spire-server/cli/logger/get_windows_test.go new file mode 100644 index 00000000000..d7a1c535828 --- /dev/null +++ b/cmd/spire-server/cli/logger/get_windows_test.go @@ -0,0 +1,12 @@ +//go:build windows + +package logger_test + +var ( + getUsage = `Usage of logger get: + -namedPipeName string + Pipe name of the SPIRE Server API named pipe (default "\\spire-server\\private\\api") + -output value + Desired output format (pretty, json); default: pretty. +` +) diff --git a/cmd/spire-server/cli/logger/mocks_test.go b/cmd/spire-server/cli/logger/mocks_test.go new file mode 100644 index 00000000000..80eeba4a521 --- /dev/null +++ b/cmd/spire-server/cli/logger/mocks_test.go @@ -0,0 +1,110 @@ +package logger_test + +import ( + "io" + "testing" + + "github.com/spiffe/spire/test/spiretest" + + "bytes" + "context" + + "github.com/mitchellh/cli" + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/cli/common" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "google.golang.org/grpc" +) + +// an input/output capture struct +type loggerTest struct { + stdin *bytes.Buffer + stdout *bytes.Buffer + stderr *bytes.Buffer + args []string + server *mockLoggerServer + client cli.Command +} + +// serialization of capture +func (l *loggerTest) afterTest(t *testing.T) { + t.Logf("TEST:%s", t.Name()) + t.Logf("STDOUT:\n%s", l.stdout.String()) + t.Logf("STDIN:\n%s", l.stdin.String()) + t.Logf("STDERR:\n%s", l.stderr.String()) +} + +// setup of input/output capture +func setupCliTest(t *testing.T, server *mockLoggerServer, newClient func(*commoncli.Env) cli.Command) *loggerTest { + addr := spiretest.StartGRPCServer(t, func(s *grpc.Server) { + loggerv1.RegisterLoggerServer(s, server) + }) + + stdin := new(bytes.Buffer) + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + + client := newClient(&commoncli.Env{ + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + }) + + test := &loggerTest{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + args: []string{common.AddrArg, common.GetAddr(addr)}, + server: server, + client: client, + } + + t.Cleanup(func() { + test.afterTest(t) + }) + + return test +} + +// a mock grpc logger server +type mockLoggerServer struct { + loggerv1.UnimplementedLoggerServer + + receivedSetValue *types.LogLevel + returnLogger *types.Logger + returnErr error +} + +// mock implementation for GetLogger +func (s *mockLoggerServer) GetLogger(_ context.Context, _ *loggerv1.GetLoggerRequest) (*types.Logger, error) { + return s.returnLogger, s.returnErr +} + +func (s *mockLoggerServer) SetLogLevel(_ context.Context, req *loggerv1.SetLogLevelRequest) (*types.Logger, error) { + s.receivedSetValue = &req.NewLevel + return s.returnLogger, s.returnErr +} + +func (s *mockLoggerServer) ResetLogLevel(_ context.Context, _ *loggerv1.ResetLogLevelRequest) (*types.Logger, error) { + s.receivedSetValue = nil + return s.returnLogger, s.returnErr +} + +var _ io.Writer = &errorWriter{} + +type errorWriter struct { + ReturnError error + Buffer bytes.Buffer +} + +func (e *errorWriter) Write(p []byte) (n int, err error) { + if e.ReturnError != nil { + return 0, e.ReturnError + } + return e.Buffer.Write(p) +} + +func (e *errorWriter) String() string { + return e.Buffer.String() +} diff --git a/cmd/spire-server/cli/logger/printers.go b/cmd/spire-server/cli/logger/printers.go new file mode 100644 index 00000000000..8562dab9631 --- /dev/null +++ b/cmd/spire-server/cli/logger/printers.go @@ -0,0 +1,40 @@ +package logger + +import ( + "errors" + "fmt" + + apitype "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + commoncli "github.com/spiffe/spire/pkg/common/cli" + serverlogger "github.com/spiffe/spire/pkg/server/api/logger/v1" +) + +func PrettyPrintLogger(env *commoncli.Env, results ...any) error { + apiLogger, ok := results[0].(*apitype.Logger) + if !ok { + return errors.New("internal error: logger not found; please report this as a bug") + } + + logrusCurrent, found := serverlogger.LogrusLevel[apiLogger.CurrentLevel] + if !found { + return errors.New("internal error: returned current log level is undefined; please report this as a bug") + } + currentText, err := logrusCurrent.MarshalText() + if err != nil { + return fmt.Errorf("internal error: logrus log level %d has no name; please report this as a bug", logrusCurrent) + } + + logrusLaunch, found := serverlogger.LogrusLevel[apiLogger.LaunchLevel] + if !found { + return errors.New("internal error: returned launch log level is undefined; please report this as a bug") + } + launchText, err := logrusLaunch.MarshalText() + if err != nil { + return fmt.Errorf("internal error: logrus log level %d has no name; please report this as a bug", logrusLaunch) + } + + if err := env.Printf("Logger Level : %s\nLaunch Level : %s\n\n", currentText, launchText); err != nil { + return err + } + return nil +} diff --git a/cmd/spire-server/cli/logger/printers_test.go b/cmd/spire-server/cli/logger/printers_test.go new file mode 100644 index 00000000000..cd5df7d3c92 --- /dev/null +++ b/cmd/spire-server/cli/logger/printers_test.go @@ -0,0 +1,67 @@ +package logger_test + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/cli/logger" + commoncli "github.com/spiffe/spire/pkg/common/cli" +) + +func TestPrettyPrintLogger(t *testing.T) { + for _, tt := range []struct { + name string + logger interface{} + outWriter errorWriter + errWriter errorWriter + env *commoncli.Env + expectedStdout string + expectedStderr string + expectedError error + }{ + { + name: "test", + logger: &types.Logger{ + CurrentLevel: types.LogLevel_DEBUG, + LaunchLevel: types.LogLevel_INFO, + }, + expectedStdout: `Logger Level : debug +Launch Level : info + +`, + }, + { + name: "test env returning an error", + outWriter: errorWriter{ + ReturnError: errors.New("cannot write"), + }, + logger: &types.Logger{ + CurrentLevel: types.LogLevel_DEBUG, + LaunchLevel: types.LogLevel_INFO, + }, + expectedError: errors.New("cannot write"), + }, + { + name: "test nil logger", + outWriter: errorWriter{ + ReturnError: errors.New("cannot write"), + }, + logger: &types.Entry{}, + expectedError: errors.New("internal error: logger not found; please report this as a bug"), + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + tt.env = &commoncli.Env{ + Stdout: &tt.outWriter, + Stderr: &tt.errWriter, + } + require.Equal(t, logger.PrettyPrintLogger(tt.env, tt.logger), tt.expectedError) + require.Equal(t, tt.outWriter.String(), tt.expectedStdout) + require.Equal(t, tt.errWriter.String(), tt.expectedStderr) + }) + } +} diff --git a/cmd/spire-server/cli/logger/set.go b/cmd/spire-server/cli/logger/set.go new file mode 100644 index 00000000000..d43220380e3 --- /dev/null +++ b/cmd/spire-server/cli/logger/set.go @@ -0,0 +1,84 @@ +package logger + +import ( + "context" + "flag" + "fmt" + "strings" + + "github.com/mitchellh/cli" + "github.com/sirupsen/logrus" + api "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" + apitype "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/util" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/spiffe/spire/pkg/common/cliprinter" + serverlogger "github.com/spiffe/spire/pkg/server/api/logger/v1" +) + +type setCommand struct { + env *commoncli.Env + newLevel string + printer cliprinter.Printer +} + +// Returns a cli.command that sets the log level using the default +// cli environment. +func NewSetCommand() cli.Command { + return NewSetCommandWithEnv(commoncli.DefaultEnv) +} + +// Returns a cli.command that sets the log level. +func NewSetCommandWithEnv(env *commoncli.Env) cli.Command { + return util.AdaptCommand(env, &setCommand{env: env}) +} + +// The name of the command. +func (*setCommand) Name() string { + return "logger set" +} + +// The help presented description of the command. +func (*setCommand) Synopsis() string { + return "Sets the logger details" +} + +// Adds additional flags specific to the command. +func (c *setCommand) AppendFlags(fs *flag.FlagSet) { + fs.StringVar(&c.newLevel, "level", "", "The new log level, one of (panic, fatal, error, warn, info, debug, trace, launch)") + cliprinter.AppendFlagWithCustomPretty(&c.printer, fs, c.env, c.prettyPrintLogger) +} + +// The routine that executes the command +func (c *setCommand) Run(ctx context.Context, _ *commoncli.Env, serverClient util.ServerClient) error { + if c.newLevel == "" { + return fmt.Errorf("a value (-level) must be set") + } + level := strings.ToLower(c.newLevel) + var logger *apitype.Logger + var err error + if level == "launch" { + logger, err = serverClient.NewLoggerClient().ResetLogLevel(ctx, &api.ResetLogLevelRequest{}) + } else { + var logrusLevel logrus.Level + logrusLevel, err = logrus.ParseLevel(level) + if err != nil { + return fmt.Errorf("the value %s is not a valid setting", c.newLevel) + } + apiLevel, found := serverlogger.APILevel[logrusLevel] + if !found { + return fmt.Errorf("the logrus level %d could not be transformed into an api log level", logrusLevel) + } + logger, err = serverClient.NewLoggerClient().SetLogLevel(ctx, &api.SetLogLevelRequest{ + NewLevel: apiLevel, + }) + } + if err != nil { + return fmt.Errorf("error fetching logger: %w", err) + } + return c.printer.PrintProto(logger) +} + +func (c *setCommand) prettyPrintLogger(env *commoncli.Env, results ...any) error { + return PrettyPrintLogger(env, results...) +} diff --git a/cmd/spire-server/cli/logger/set_posix_test.go b/cmd/spire-server/cli/logger/set_posix_test.go new file mode 100644 index 00000000000..1b84c77345f --- /dev/null +++ b/cmd/spire-server/cli/logger/set_posix_test.go @@ -0,0 +1,14 @@ +//go:build !windows + +package logger_test + +var ( + setUsage = `Usage of logger set: + -level string + The new log level, one of (panic, fatal, error, warn, info, debug, trace, launch) + -output value + Desired output format (pretty, json); default: pretty. + -socketPath string + Path to the SPIRE Server API socket (default "/tmp/spire-server/private/api.sock") +` +) diff --git a/cmd/spire-server/cli/logger/set_test.go b/cmd/spire-server/cli/logger/set_test.go new file mode 100644 index 00000000000..3b18d11c3b7 --- /dev/null +++ b/cmd/spire-server/cli/logger/set_test.go @@ -0,0 +1,151 @@ +package logger_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/cli/logger" +) + +func TestSetHelp(t *testing.T) { + test := setupCliTest(t, nil, logger.NewSetCommandWithEnv) + test.client.Help() + require.Equal(t, "", test.stdout.String()) + require.Equal(t, setUsage, test.stderr.String()) +} + +func TestSetSynopsis(t *testing.T) { + cmd := logger.NewSetCommand() + require.Equal(t, "Sets the logger details", cmd.Synopsis()) +} + +func TestSet(t *testing.T) { + for _, tt := range []struct { + name string + // server state + server *mockLoggerServer + // input + args []string + // expected items + expectedSetValue types.LogLevel + expectReturnCode int + expectStdout string + expectStderr string + }{ + { + name: "set to debug, configured to info, using pretty output", + args: []string{"-level", "debug", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_DEBUG, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectedSetValue: types.LogLevel_DEBUG, + expectReturnCode: 0, + expectStdout: `Logger Level : debug +Launch Level : info + +`, + }, + { + name: "set to warn, configured to debug, using pretty output", + args: []string{"-level", "warn", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_WARN, + LaunchLevel: types.LogLevel_DEBUG, + }, + }, + expectedSetValue: types.LogLevel_WARN, + expectReturnCode: 0, + expectStdout: `Logger Level : warning +Launch Level : debug + +`, + }, + { + name: "set to launch, configured to error, using pretty output", + args: []string{"-level", "launch", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_ERROR, + LaunchLevel: types.LogLevel_ERROR, + }, + }, + expectReturnCode: 0, + expectStdout: `Logger Level : error +Launch Level : error + +`, + }, + { + name: "set to panic, configured to fatal, using pretty output", + args: []string{"-level", "panic", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_PANIC, + LaunchLevel: types.LogLevel_FATAL, + }, + }, + expectedSetValue: types.LogLevel_PANIC, + expectReturnCode: 0, + expectStdout: `Logger Level : panic +Launch Level : fatal + +`, + }, + { + name: "set with invalid setting of never, logger unadjusted from (info,info)", + args: []string{"-level", "never", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_INFO, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectReturnCode: 1, + expectStderr: `Error: the value never is not a valid setting +`, + }, + { + name: "No attribute set, cli returns error", + args: []string{"-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_INFO, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectReturnCode: 1, + expectStderr: `Error: a value (-level) must be set +`, + }, + { + name: "bizzarro world, set to trace, logger unadjusted from (info,info)", + args: []string{"-level", "trace", "-output", "pretty"}, + server: &mockLoggerServer{ + returnLogger: &types.Logger{ + CurrentLevel: types.LogLevel_INFO, + LaunchLevel: types.LogLevel_INFO, + }, + }, + expectedSetValue: types.LogLevel_TRACE, + expectReturnCode: 0, + expectStdout: `Logger Level : info +Launch Level : info + +`, + }, + } { + t.Run(tt.name, func(t *testing.T) { + test := setupCliTest(t, tt.server, logger.NewSetCommandWithEnv) + returnCode := test.client.Run(append(test.args, tt.args...)) + require.Equal(t, tt.expectReturnCode, returnCode) + require.Equal(t, tt.expectStderr, test.stderr.String()) + require.Equal(t, tt.expectStdout, test.stdout.String()) + }) + } +} diff --git a/cmd/spire-server/cli/logger/set_windows_test.go b/cmd/spire-server/cli/logger/set_windows_test.go new file mode 100644 index 00000000000..7a561d39861 --- /dev/null +++ b/cmd/spire-server/cli/logger/set_windows_test.go @@ -0,0 +1,14 @@ +//go:build windows + +package logger_test + +var ( + setUsage = `Usage of logger set: + -level string + The new log level, one of (panic, fatal, error, warn, info, debug, trace, launch) + -namedPipeName string + Pipe name of the SPIRE Server API named pipe (default "\\spire-server\\private\\api") + -output value + Desired output format (pretty, json); default: pretty. +` +) diff --git a/cmd/spire-server/cli/run/run.go b/cmd/spire-server/cli/run/run.go index 2168f270dba..0dbab003276 100644 --- a/cmd/spire-server/cli/run/run.go +++ b/cmd/spire-server/cli/run/run.go @@ -385,6 +385,7 @@ func NewServerConfig(c *Config, logOptions []log.Option, allowUnknownConfig bool } logger, err := log.NewLogger(logOptions...) + sc.LaunchLogLevel, _ = logrus.ParseLevel(c.Server.LogLevel) if err != nil { return nil, fmt.Errorf("could not start logger: %w", err) } diff --git a/cmd/spire-server/util/util.go b/cmd/spire-server/util/util.go index 791359f49dc..258dfbc1c3f 100644 --- a/cmd/spire-server/util/util.go +++ b/cmd/spire-server/util/util.go @@ -14,6 +14,7 @@ import ( agentv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/agent/v1" bundlev1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1" + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" trustdomainv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" api_types "github.com/spiffe/spire-api-sdk/proto/spire/api/types" @@ -45,6 +46,7 @@ type ServerClient interface { NewAgentClient() agentv1.AgentClient NewBundleClient() bundlev1.BundleClient NewEntryClient() entryv1.EntryClient + NewLoggerClient() loggerv1.LoggerClient NewSVIDClient() svidv1.SVIDClient NewTrustDomainClient() trustdomainv1.TrustDomainClient NewHealthClient() grpc_health_v1.HealthClient @@ -78,6 +80,10 @@ func (c *serverClient) NewEntryClient() entryv1.EntryClient { return entryv1.NewEntryClient(c.conn) } +func (c *serverClient) NewLoggerClient() loggerv1.LoggerClient { + return loggerv1.NewLoggerClient(c.conn) +} + func (c *serverClient) NewSVIDClient() svidv1.SVIDClient { return svidv1.NewSVIDClient(c.conn) } diff --git a/go.mod b/go.mod index 5fd56c5f0d0..fbe34eb62cd 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( github.com/sigstore/sigstore v1.8.2 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.1.7 - github.com/spiffe/spire-api-sdk v1.2.5-0.20231107161112-ba57e0e943a2 + github.com/spiffe/spire-api-sdk v1.2.5-0.20240222231036-08f5a1ab98c6 github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d github.com/stretchr/testify v1.9.0 github.com/uber-go/tally/v4 v4.1.12 diff --git a/go.sum b/go.sum index ae299834c8e..31ab9cdab52 100644 --- a/go.sum +++ b/go.sum @@ -1393,8 +1393,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV github.com/spiffe/go-spiffe/v2 v2.1.6/go.mod h1:eVDqm9xFvyqao6C+eQensb9ZPkyNEeaUbqbBpOhBnNk= github.com/spiffe/go-spiffe/v2 v2.1.7 h1:VUkM1yIyg/x8X7u1uXqSRVRCdMdfRIEdFBzpqoeASGk= github.com/spiffe/go-spiffe/v2 v2.1.7/go.mod h1:QJDGdhXllxjxvd5B+2XnhhXB/+rC8gr+lNrtOryiWeE= -github.com/spiffe/spire-api-sdk v1.2.5-0.20231107161112-ba57e0e943a2 h1:EKSBig+9oEvyLUi80aE/88UHjoNCqlNGTFTjm02F+fk= -github.com/spiffe/spire-api-sdk v1.2.5-0.20231107161112-ba57e0e943a2/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= +github.com/spiffe/spire-api-sdk v1.2.5-0.20240222231036-08f5a1ab98c6 h1:gCctMhffEF4KcrLP85qQwOeQoHCMMYlDL1HR0fEZ+sE= +github.com/spiffe/spire-api-sdk v1.2.5-0.20240222231036-08f5a1ab98c6/go.mod h1:4uuhFlN6KBWjACRP3xXwrOTNnvaLp1zJs8Lribtr4fI= github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d h1:LCRQGU6vOqKLfRrG+GJQrwMwDILcAddAEIf4/1PaSVc= github.com/spiffe/spire-plugin-sdk v1.4.4-0.20230721151831-bf67dde4721d/go.mod h1:GA6o2PVLwyJdevT6KKt5ZXCY/ziAPna13y/seGk49Ik= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/pkg/common/api/middleware/names.go b/pkg/common/api/middleware/names.go index d09ee0829f2..acacbbfa37b 100644 --- a/pkg/common/api/middleware/names.go +++ b/pkg/common/api/middleware/names.go @@ -19,6 +19,8 @@ const ( EnvoySDSv3ServiceShortName = "SDS.v3" HealthServiceName = "grpc.health.v1.Health" HealthServiceShortName = "Health" + LoggerServiceName = "logger.v1.Logger" + LoggerServiceShortName = "Logger" DelegatedIdentityServiceName = "spire.api.agent.delegatedidentity.v1.DelegatedIdentity" DelegatedIdentityServiceShortName = "DelegatedIdentity" ServerReflectionServiceName = "grpc.reflection.v1.ServerReflection" @@ -33,6 +35,7 @@ var ( WorkloadAPIServiceName, WorkloadAPIServiceShortName, EnvoySDSv3ServiceName, EnvoySDSv3ServiceShortName, HealthServiceName, HealthServiceShortName, + LoggerServiceName, LoggerServiceShortName, DelegatedIdentityServiceName, DelegatedIdentityServiceShortName, ) diff --git a/pkg/server/api/logger/v1/levels.go b/pkg/server/api/logger/v1/levels.go new file mode 100644 index 00000000000..c8f0c06dcc0 --- /dev/null +++ b/pkg/server/api/logger/v1/levels.go @@ -0,0 +1,26 @@ +package logger + +import ( + logrus "github.com/sirupsen/logrus" + apitype "github.com/spiffe/spire-api-sdk/proto/spire/api/types" +) + +var APILevel = map[logrus.Level]apitype.LogLevel{ + logrus.PanicLevel: apitype.LogLevel_PANIC, + logrus.FatalLevel: apitype.LogLevel_FATAL, + logrus.ErrorLevel: apitype.LogLevel_ERROR, + logrus.WarnLevel: apitype.LogLevel_WARN, + logrus.InfoLevel: apitype.LogLevel_INFO, + logrus.DebugLevel: apitype.LogLevel_DEBUG, + logrus.TraceLevel: apitype.LogLevel_TRACE, +} + +var LogrusLevel = map[apitype.LogLevel]logrus.Level{ + apitype.LogLevel_PANIC: logrus.PanicLevel, + apitype.LogLevel_FATAL: logrus.FatalLevel, + apitype.LogLevel_ERROR: logrus.ErrorLevel, + apitype.LogLevel_WARN: logrus.WarnLevel, + apitype.LogLevel_INFO: logrus.InfoLevel, + apitype.LogLevel_DEBUG: logrus.DebugLevel, + apitype.LogLevel_TRACE: logrus.TraceLevel, +} diff --git a/pkg/server/api/logger/v1/levels_test.go b/pkg/server/api/logger/v1/levels_test.go new file mode 100644 index 00000000000..9b40ec879a0 --- /dev/null +++ b/pkg/server/api/logger/v1/levels_test.go @@ -0,0 +1,107 @@ +package logger_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/sirupsen/logrus" + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/pkg/server/api/logger/v1" +) + +func TestAPILevelValues(t *testing.T) { + for _, tt := range []struct { + name string + logrusLevel logrus.Level + expectedLevel types.LogLevel + }{ + { + name: "test logrus.PanicLevel fetches types.LogLevel_PANIC", + logrusLevel: logrus.PanicLevel, + expectedLevel: types.LogLevel_PANIC, + }, + { + name: "test logrus.FatalLevel fetches types.LogLevel_FATAL", + logrusLevel: logrus.FatalLevel, + expectedLevel: types.LogLevel_FATAL, + }, + { + name: "test logrus.ErrorLevel fetches types.LogLevel_ERROR", + logrusLevel: logrus.ErrorLevel, + expectedLevel: types.LogLevel_ERROR, + }, + { + name: "test logrus.WarnLevel fetches types.LogLevel_WARN", + logrusLevel: logrus.WarnLevel, + expectedLevel: types.LogLevel_WARN, + }, + { + name: "test logrus.InfoLevel fetches types.LogLevel_INFO", + logrusLevel: logrus.InfoLevel, + expectedLevel: types.LogLevel_INFO, + }, + { + name: "test logrus.DebugLevel fetches types.LogLevel_DEBUG", + logrusLevel: logrus.DebugLevel, + expectedLevel: types.LogLevel_DEBUG, + }, + { + name: "test logrus.TraceLevel fetches types.LogLevel_TRACE", + logrusLevel: logrus.TraceLevel, + expectedLevel: types.LogLevel_TRACE, + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, logger.APILevel[tt.logrusLevel], tt.expectedLevel) + }) + } +} + +func TestLogrusLevelValues(t *testing.T) { + for _, tt := range []struct { + name string + apiLevel types.LogLevel + expectedLevel logrus.Level + }{ + { + name: "test types.LogLevel_PANIC fetches logrus.PanicLevel", + apiLevel: types.LogLevel_PANIC, + expectedLevel: logrus.PanicLevel, + }, + { + name: "test types.LogLevel_FATAL fetches logrus.FatalLevel", + apiLevel: types.LogLevel_FATAL, + expectedLevel: logrus.FatalLevel, + }, + { + name: "test types.LogLevel_ERROR fetches logrus.ErrorLevel", + apiLevel: types.LogLevel_ERROR, + expectedLevel: logrus.ErrorLevel, + }, + { + name: "test types.LogLevel_WARN fetches logrus.WarnLevel", + apiLevel: types.LogLevel_WARN, + expectedLevel: logrus.WarnLevel, + }, + { + name: "test types.LogLevel_INFO fetches logrus.InfoLevel", + apiLevel: types.LogLevel_INFO, + expectedLevel: logrus.InfoLevel, + }, + { + name: "test types.LogLevel_DEBUG fetches logrus.DebugLevel", + apiLevel: types.LogLevel_DEBUG, + expectedLevel: logrus.DebugLevel, + }, + { + name: "test types.LogLevel_TRACE fetches logrus.TraceLevel", + apiLevel: types.LogLevel_TRACE, + expectedLevel: logrus.TraceLevel, + }, + } { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, logger.LogrusLevel[tt.apiLevel], tt.expectedLevel) + }) + } +} diff --git a/pkg/server/api/logger/v1/service.go b/pkg/server/api/logger/v1/service.go new file mode 100644 index 00000000000..c18bdd7e61d --- /dev/null +++ b/pkg/server/api/logger/v1/service.go @@ -0,0 +1,78 @@ +package logger + +import ( + "context" + "fmt" + + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" + apitype "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + + "github.com/sirupsen/logrus" + "google.golang.org/grpc" +) + +type Logger interface { + logrus.FieldLogger + GetLevel() logrus.Level + SetLevel(level logrus.Level) +} + +type Config struct { + Log Logger + LaunchLevel logrus.Level +} + +type Service struct { + loggerv1.UnsafeLoggerServer + + log Logger + launchLevel logrus.Level +} + +func New(config Config) *Service { + config.Log.WithFields(logrus.Fields{ + "LaunchLevel": config.LaunchLevel, + }).Info("Logger service configured") + return &Service{ + log: config.Log, + launchLevel: config.LaunchLevel, + } +} + +func RegisterService(s grpc.ServiceRegistrar, service *Service) { + loggerv1.RegisterLoggerServer(s, service) +} + +func (service *Service) GetLogger(_ context.Context, _ *loggerv1.GetLoggerRequest) (*apitype.Logger, error) { + service.log.Info("GetLogger Called") + logger := &apitype.Logger{ + CurrentLevel: APILevel[service.log.GetLevel()], + LaunchLevel: APILevel[service.launchLevel], + } + return logger, nil +} + +func (service *Service) SetLogLevel(_ context.Context, req *loggerv1.SetLogLevelRequest) (*apitype.Logger, error) { + if req.NewLevel == apitype.LogLevel_UNSPECIFIED { + return nil, fmt.Errorf("Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED") + } + service.log.WithFields(logrus.Fields{ + "NewLevel": LogrusLevel[req.NewLevel].String(), + }).Info("SetLogLevel Called") + service.log.SetLevel(LogrusLevel[req.NewLevel]) + logger := &apitype.Logger{ + CurrentLevel: APILevel[service.log.GetLevel()], + LaunchLevel: APILevel[service.launchLevel], + } + return logger, nil +} + +func (service *Service) ResetLogLevel(_ context.Context, _ *loggerv1.ResetLogLevelRequest) (*apitype.Logger, error) { + service.log.Info("ResetLogLevel Called") + service.log.SetLevel(service.launchLevel) + logger := &apitype.Logger{ + CurrentLevel: APILevel[service.log.GetLevel()], + LaunchLevel: APILevel[service.launchLevel], + } + return logger, nil +} diff --git a/pkg/server/api/logger/v1/service_test.go b/pkg/server/api/logger/v1/service_test.go new file mode 100644 index 00000000000..35e75d29fc3 --- /dev/null +++ b/pkg/server/api/logger/v1/service_test.go @@ -0,0 +1,900 @@ +package logger_test + +import ( + "context" + "testing" + + "github.com/sirupsen/logrus/hooks/test" + "github.com/spiffe/spire/test/grpctest" + "github.com/spiffe/spire/test/spiretest" + "github.com/stretchr/testify/require" + + "github.com/sirupsen/logrus" + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" + apitype "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/pkg/server/api/logger/v1" + "github.com/spiffe/spire/pkg/server/api/rpccontext" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +func TestGetLogger(t *testing.T) { + for _, tt := range []struct { + name string + launchLevel logrus.Level + + expectedErr error + expectedResponse *apitype.Logger + expectedLogs []spiretest.LogEntry + }{ + { + name: "test GetLogger on initialized to PANIC", + launchLevel: logrus.PanicLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_PANIC, + }, + // no outputted log messages, as the are at INFO level + expectedLogs: nil, + }, + { + name: "test GetLogger on initialized to FATAL", + launchLevel: logrus.FatalLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_FATAL, + LaunchLevel: apitype.LogLevel_FATAL, + }, + // no outputted log messages, as the are at INFO level + expectedLogs: nil, + }, + { + name: "test GetLogger on initialized to ERROR", + launchLevel: logrus.ErrorLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_ERROR, + LaunchLevel: apitype.LogLevel_ERROR, + }, + // no outputted log messages, as the are at INFO level + expectedLogs: nil, + }, + { + name: "test GetLogger on initialized to WARN", + launchLevel: logrus.WarnLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_WARN, + LaunchLevel: apitype.LogLevel_WARN, + }, + // no outputted log messages, as the are at INFO level + expectedLogs: nil, + }, + { + name: "test GetLogger on initialized to INFO", + launchLevel: logrus.InfoLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_INFO, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test GetLogger on initialized to DEBUG", + launchLevel: logrus.DebugLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_DEBUG, + LaunchLevel: apitype.LogLevel_DEBUG, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "debug", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test GetLogger on initialized to TRACE", + launchLevel: logrus.TraceLevel, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_TRACE, + LaunchLevel: apitype.LogLevel_TRACE, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t, tt.launchLevel) + defer test.Cleanup() + + resp, err := test.client.GetLogger(context.Background(), &loggerv1.GetLoggerRequest{}) + require.Equal(t, err, tt.expectedErr) + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectedLogs) + spiretest.RequireProtoEqual(t, resp, tt.expectedResponse) + }) + } +} + +// After changing the log level, gets the logger to check the log impact +func TestSetLoggerThenGetLogger(t *testing.T) { + for _, tt := range []struct { + name string + launchLevel logrus.Level + setLogLevelRequest *loggerv1.SetLogLevelRequest + + expectedErr error + expectedResponse *apitype.Logger + expectedLogs []spiretest.LogEntry + }{ + { + name: "test SetLogger to FATAL on initialized to PANIC", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_FATAL, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_FATAL, + LaunchLevel: apitype.LogLevel_PANIC, + }, + expectedLogs: nil, + }, + { + name: "test SetLogger to INFO on initialized to PANIC", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_PANIC, + }, + // only the ending get logger will log + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test SetLogger to DEBUG on initialized to PANIC", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_DEBUG, + LaunchLevel: apitype.LogLevel_PANIC, + }, + // only the ending get logger will log + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test SetLogger to PANIC on initialized to INFO", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_PANIC, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_INFO, + }, + // the ending getlogger will be suppressed + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "panic", + }, + }, + }, + }, + { + name: "test SetLogger to INFO on initialized to INFO", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_INFO, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test SetLogger to DEBUG on initialized to INFO", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_DEBUG, + LaunchLevel: apitype.LogLevel_INFO, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "debug", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test SetLogger to PANIC on initialized to TRACE", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_PANIC, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_TRACE, + }, + // the ending getlogger will be suppressed + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "panic", + }, + }, + }, + }, + { + name: "test SetLogger to INFO on initialized to TRACE", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_TRACE, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test SetLogger to DEBUG on initialized to TRACE", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_DEBUG, + LaunchLevel: apitype.LogLevel_TRACE, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "debug", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t, tt.launchLevel) + defer test.Cleanup() + + resp, _ := test.client.SetLogLevel(context.Background(), tt.setLogLevelRequest) + spiretest.RequireProtoEqual(t, resp, tt.expectedResponse) + resp, err := test.client.GetLogger(context.Background(), &loggerv1.GetLoggerRequest{}) + require.Equal(t, err, tt.expectedErr) + spiretest.RequireProtoEqual(t, resp, tt.expectedResponse) + + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectedLogs) + }) + } +} + +// After changing the log level, gets the logger to check the log impact +// After resetting the log level, gets the logger to check the log impact +func TestResetLogger(t *testing.T) { + for _, tt := range []struct { + name string + launchLevel logrus.Level + setLogLevelRequest *loggerv1.SetLogLevelRequest + + expectedErr error + expectedResponse *apitype.Logger + expectedLogs []spiretest.LogEntry + }{ + { + name: "test PANIC Logger set to FATAL then RESET", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_FATAL, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_PANIC, + }, + expectedLogs: nil, + }, + { + name: "test PANIC Logger set to INFO then RESET", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_PANIC, + }, + // only the ending get logger will log + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + }, + }, + { + name: "test PANIC Logger set to DEBUG then RESET", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_PANIC, + LaunchLevel: apitype.LogLevel_PANIC, + }, + // only the ending get logger will log + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + }, + }, + { + name: "test INFO Logger set to PANIC and then RESET", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_PANIC, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_INFO, + }, + // the ending getlogger will be suppressed + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "panic", + }, + }, + // the second get, after the reset + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test INFO Logger set to INFO and then RESET", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_INFO, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test INFO Logger set to DEBUG and then RESET", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_INFO, + LaunchLevel: apitype.LogLevel_INFO, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "debug", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test TRACE Logger set to PANIC and then RESET", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_PANIC, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_TRACE, + LaunchLevel: apitype.LogLevel_TRACE, + }, + // the ending getlogger will be suppressed + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "panic", + }, + }, + // the second get logger, after the reset + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test TRACE Logger set to INFO and then RESET", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_INFO, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_TRACE, + LaunchLevel: apitype.LogLevel_TRACE, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "info", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + { + name: "test TRACE Logger set to DEBUG and then RESET", + launchLevel: logrus.TraceLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_DEBUG, + }, + + expectedResponse: &apitype.Logger{ + CurrentLevel: apitype.LogLevel_TRACE, + LaunchLevel: apitype.LogLevel_TRACE, + }, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "trace", + }, + }, + { + Level: logrus.InfoLevel, + Message: "SetLogLevel Called", + Data: logrus.Fields{ + "NewLevel": "debug", + }, + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + { + Level: logrus.InfoLevel, + Message: "ResetLogLevel Called", + }, + { + Level: logrus.InfoLevel, + Message: "GetLogger Called", + }, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t, tt.launchLevel) + defer test.Cleanup() + + _, _ = test.client.SetLogLevel(context.Background(), tt.setLogLevelRequest) + _, _ = test.client.GetLogger(context.Background(), &loggerv1.GetLoggerRequest{}) + resp, err := test.client.ResetLogLevel(context.Background(), &loggerv1.ResetLogLevelRequest{}) + + require.Equal(t, err, tt.expectedErr) + spiretest.RequireProtoEqual(t, resp, tt.expectedResponse) + _, _ = test.client.GetLogger(context.Background(), &loggerv1.GetLoggerRequest{}) + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectedLogs) + }) + } +} + +func TestUnsetSetLogLevelRequest(t *testing.T) { + for _, tt := range []struct { + name string + launchLevel logrus.Level + setLogLevelRequest *loggerv1.SetLogLevelRequest + + code codes.Code + expectedErr string + expectedResponse *apitype.Logger + expectedLogs []spiretest.LogEntry + }{ + { + name: "test PANIC Logger set without a log level", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{}, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + // the error seems to clear the log capture + expectedLogs: nil, + }, + { + name: "test PANIC Logger set to UNSPECIFIED", + launchLevel: logrus.PanicLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_UNSPECIFIED, + }, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + // the error seems to clear the log capture + expectedLogs: nil, + }, + { + name: "test INFO Logger set without a log level", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{}, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + }, + }, + { + name: "test INFO Logger set to UNSPECIFIED", + launchLevel: logrus.InfoLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_UNSPECIFIED, + }, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "info", + }, + }, + }, + }, + { + name: "test DEBUG Logger set without a log level", + launchLevel: logrus.DebugLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{}, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "debug", + }, + }, + }, + }, + { + name: "test DEBUG Logger set to UNSPECIFIED", + launchLevel: logrus.DebugLevel, + setLogLevelRequest: &loggerv1.SetLogLevelRequest{ + NewLevel: apitype.LogLevel_UNSPECIFIED, + }, + + code: codes.Unknown, + expectedErr: "Invalid request, NewLevel value cannot be LogLevel_UNSPECIFIED", + expectedResponse: nil, + expectedLogs: []spiretest.LogEntry{ + { + Level: logrus.InfoLevel, + Message: "Logger service configured", + Data: logrus.Fields{ + "LaunchLevel": "debug", + }, + }, + }, + }, + } { + tt := tt + t.Run(tt.name, func(t *testing.T) { + test := setupServiceTest(t, tt.launchLevel) + defer test.Cleanup() + + resp, err := test.client.SetLogLevel(context.Background(), tt.setLogLevelRequest) + spiretest.RequireGRPCStatusContains(t, err, tt.code, tt.expectedErr) + require.Nil(t, resp) + + spiretest.RequireProtoEqual(t, resp, tt.expectedResponse) + spiretest.AssertLogs(t, test.logHook.AllEntries(), tt.expectedLogs) + }) + } +} + +type serviceTest struct { + client loggerv1.LoggerClient + done func() + + logHook *test.Hook +} + +func (s *serviceTest) Cleanup() { + s.done() +} + +func setupServiceTest(t *testing.T, launchLevel logrus.Level) *serviceTest { + log, logHook := test.NewNullLogger() + // logger level should initially match the launch level + log.SetLevel(launchLevel) + service := logger.New(logger.Config{ + Log: log, + LaunchLevel: launchLevel, + }) + + registerFn := func(s grpc.ServiceRegistrar) { + logger.RegisterService(s, service) + } + overrideContext := func(ctx context.Context) context.Context { + ctx = rpccontext.WithLogger(ctx, log) + return ctx + } + server := grpctest.StartServer(t, registerFn, grpctest.OverrideContext(overrideContext)) + conn := server.Dial(t) + + test := &serviceTest{ + done: server.Stop, + logHook: logHook, + client: loggerv1.NewLoggerClient(conn), + } + + return test +} diff --git a/pkg/server/authpolicy/policy_data.json b/pkg/server/authpolicy/policy_data.json index 9b3556cf4f4..d2363a1653d 100644 --- a/pkg/server/authpolicy/policy_data.json +++ b/pkg/server/authpolicy/policy_data.json @@ -113,6 +113,18 @@ "full_method": "/spire.api.server.entry.v1.Entry/SyncAuthorizedEntries", "allow_agent": true }, + { + "full_method": "/spire.api.server.logger.v1.Logger/GetLogger", + "allow_local": true + }, + { + "full_method": "/spire.api.server.logger.v1.Logger/SetLogLevel", + "allow_local": true + }, + { + "full_method": "/spire.api.server.logger.v1.Logger/ResetLogLevel", + "allow_local": true + }, { "full_method": "/spire.api.server.agent.v1.Agent/CountAgents", "allow_admin": true, diff --git a/pkg/server/config.go b/pkg/server/config.go index ae2dffc231d..c973151b56d 100644 --- a/pkg/server/config.go +++ b/pkg/server/config.go @@ -11,6 +11,7 @@ import ( common "github.com/spiffe/spire/pkg/common/catalog" "github.com/spiffe/spire/pkg/common/health" "github.com/spiffe/spire/pkg/common/telemetry" + loggerv1 "github.com/spiffe/spire/pkg/server/api/logger/v1" "github.com/spiffe/spire/pkg/server/authpolicy" bundle_client "github.com/spiffe/spire/pkg/server/bundle/client" "github.com/spiffe/spire/pkg/server/endpoints" @@ -22,7 +23,9 @@ type Config struct { // Configurations for server plugins PluginConfigs common.PluginConfigs - Log logrus.FieldLogger + Log loggerv1.Logger + + LaunchLogLevel logrus.Level // LogReopener facilitates handling a signal to rotate log file. LogReopener func(context.Context) error diff --git a/pkg/server/endpoints/config.go b/pkg/server/endpoints/config.go index 4ae690d8561..20d205a3ffe 100644 --- a/pkg/server/endpoints/config.go +++ b/pkg/server/endpoints/config.go @@ -20,6 +20,7 @@ import ( debugv1 "github.com/spiffe/spire/pkg/server/api/debug/v1" entryv1 "github.com/spiffe/spire/pkg/server/api/entry/v1" healthv1 "github.com/spiffe/spire/pkg/server/api/health/v1" + loggerv1 "github.com/spiffe/spire/pkg/server/api/logger/v1" svidv1 "github.com/spiffe/spire/pkg/server/api/svid/v1" trustdomainv1 "github.com/spiffe/spire/pkg/server/api/trustdomain/v1" "github.com/spiffe/spire/pkg/server/authpolicy" @@ -61,7 +62,15 @@ type Config struct { // Makes policy decisions AuthPolicyEngine *authpolicy.Engine - Log logrus.FieldLogger + // The logger for the endpoints subsystem + Log logrus.FieldLogger + + // The root logger for the entire process + RootLog loggerv1.Logger + + // The default (original config) log level + LaunchLogLevel logrus.Level + Metrics telemetry.Metrics // RateLimit holds rate limiting configurations. @@ -157,6 +166,10 @@ func (c *Config) makeAPIServers(entryFetcher api.AuthorizedEntryFetcher) APIServ TrustDomain: c.TrustDomain, DataStore: ds, }), + LoggerServer: loggerv1.New(loggerv1.Config{ + Log: c.RootLog, + LaunchLevel: c.LaunchLogLevel, + }), SVIDServer: svidv1.New(svidv1.Config{ TrustDomain: c.TrustDomain, EntryFetcher: entryFetcher, diff --git a/pkg/server/endpoints/endpoints.go b/pkg/server/endpoints/endpoints.go index a6c5429758d..1d767999e5b 100644 --- a/pkg/server/endpoints/endpoints.go +++ b/pkg/server/endpoints/endpoints.go @@ -25,6 +25,7 @@ import ( bundlev1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" debugv1_pb "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1" + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" trustdomainv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" "github.com/spiffe/spire/pkg/common/auth" @@ -86,6 +87,7 @@ type APIServers struct { DebugServer debugv1_pb.DebugServer EntryServer entryv1.EntryServer HealthServer grpc_health_v1.HealthServer + LoggerServer loggerv1.LoggerServer SVIDServer svidv1.SVIDServer TrustDomainServer trustdomainv1.TrustDomainServer } @@ -175,7 +177,7 @@ func (e *Endpoints) ListenAndServe(ctx context.Context) error { tcpServer := e.createTCPServer(ctx, unaryInterceptor, streamInterceptor) udsServer := e.createUDSServer(unaryInterceptor, streamInterceptor) - // New APIs + // TCP and UDS agentv1.RegisterAgentServer(tcpServer, e.APIServers.AgentServer) agentv1.RegisterAgentServer(udsServer, e.APIServers.AgentServer) bundlev1.RegisterBundleServer(tcpServer, e.APIServers.BundleServer) @@ -187,7 +189,8 @@ func (e *Endpoints) ListenAndServe(ctx context.Context) error { trustdomainv1.RegisterTrustDomainServer(tcpServer, e.APIServers.TrustDomainServer) trustdomainv1.RegisterTrustDomainServer(udsServer, e.APIServers.TrustDomainServer) - // Register Health and Debug only on UDS server + // UDS only + loggerv1.RegisterLoggerServer(udsServer, e.APIServers.LoggerServer) grpc_health_v1.RegisterHealthServer(udsServer, e.APIServers.HealthServer) debugv1_pb.RegisterDebugServer(udsServer, e.APIServers.DebugServer) diff --git a/pkg/server/endpoints/endpoints_test.go b/pkg/server/endpoints/endpoints_test.go index 812ff6ee5d7..542aafba4a8 100644 --- a/pkg/server/endpoints/endpoints_test.go +++ b/pkg/server/endpoints/endpoints_test.go @@ -19,6 +19,7 @@ import ( bundlev1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/bundle/v1" debugv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" entryv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/entry/v1" + loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" svidv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/svid/v1" trustdomainv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/trustdomain/v1" "github.com/spiffe/spire-api-sdk/proto/spire/api/types" @@ -95,6 +96,7 @@ func TestNew(t *testing.T) { BundleEndpoint: bundle.EndpointConfig{Address: tcpAddr}, JWTKeyPublisher: &fakeJWTKeyPublisher{}, Log: log, + RootLog: log, Metrics: metrics, RateLimit: rateLimit, Clock: clk, @@ -110,6 +112,7 @@ func TestNew(t *testing.T) { assert.NotNil(t, endpoints.APIServers.DebugServer) assert.NotNil(t, endpoints.APIServers.EntryServer) assert.NotNil(t, endpoints.APIServers.HealthServer) + assert.NotNil(t, endpoints.APIServers.LoggerServer) assert.NotNil(t, endpoints.APIServers.SVIDServer) assert.NotNil(t, endpoints.BundleEndpointServer) assert.NotNil(t, endpoints.EntryFetcherPruneEventsTask) @@ -208,6 +211,7 @@ func TestListenAndServe(t *testing.T) { DebugServer: debugServer{}, EntryServer: entryServer{}, HealthServer: healthServer{}, + LoggerServer: loggerServer{}, SVIDServer: svidServer{}, TrustDomainServer: trustDomainServer{}, }, @@ -301,6 +305,9 @@ func TestListenAndServe(t *testing.T) { t.Run("Health", func(t *testing.T) { testHealthAPI(ctx, t, conns) }) + t.Run("Logger", func(t *testing.T) { + testLoggerAPI(ctx, t, conns) + }) t.Run("Bundle", func(t *testing.T) { testBundleAPI(ctx, t, conns) }) @@ -518,6 +525,36 @@ func testHealthAPI(ctx context.Context, t *testing.T, conns testConns) { }) } +func testLoggerAPI(ctx context.Context, t *testing.T, conns testConns) { + t.Run("Local", func(t *testing.T) { + testAuthorization(ctx, t, loggerv1.NewLoggerClient(conns.local), map[string]bool{ + "GetLogger": true, + "SetLogLevel": true, + "ResetLogLevel": true, + }) + }) + + t.Run("NoAuth", func(t *testing.T) { + assertServiceUnavailable(ctx, t, loggerv1.NewLoggerClient(conns.noAuth)) + }) + + t.Run("Agent", func(t *testing.T) { + assertServiceUnavailable(ctx, t, loggerv1.NewLoggerClient(conns.agent)) + }) + + t.Run("Admin", func(t *testing.T) { + assertServiceUnavailable(ctx, t, loggerv1.NewLoggerClient(conns.admin)) + }) + + t.Run("Federated Admin", func(t *testing.T) { + assertServiceUnavailable(ctx, t, loggerv1.NewLoggerClient(conns.federatedAdmin)) + }) + + t.Run("Downstream", func(t *testing.T) { + assertServiceUnavailable(ctx, t, loggerv1.NewLoggerClient(conns.downstream)) + }) +} + func testDebugAPI(ctx context.Context, t *testing.T, conns testConns) { t.Run("Local", func(t *testing.T) { testAuthorization(ctx, t, debugv1.NewDebugClient(conns.local), map[string]bool{ @@ -1148,6 +1185,22 @@ func (healthServer) Watch(_ *grpc_health_v1.HealthCheckRequest, stream grpc_heal return stream.Send(&grpc_health_v1.HealthCheckResponse{}) } +type loggerServer struct { + loggerv1.UnsafeLoggerServer +} + +func (loggerServer) GetLogger(context.Context, *loggerv1.GetLoggerRequest) (*types.Logger, error) { + return &types.Logger{}, nil +} + +func (loggerServer) SetLogLevel(context.Context, *loggerv1.SetLogLevelRequest) (*types.Logger, error) { + return &types.Logger{}, nil +} + +func (loggerServer) ResetLogLevel(context.Context, *loggerv1.ResetLogLevelRequest) (*types.Logger, error) { + return &types.Logger{}, nil +} + type svidServer struct { svidv1.UnsafeSVIDServer } diff --git a/pkg/server/endpoints/middleware.go b/pkg/server/endpoints/middleware.go index 38d36ad2c4b..c8df56389d5 100644 --- a/pkg/server/endpoints/middleware.go +++ b/pkg/server/endpoints/middleware.go @@ -153,6 +153,9 @@ func RateLimits(config RateLimitConfig) map[string]api.RateLimiter { "/spire.api.server.entry.v1.Entry/BatchDeleteEntry": noLimit, "/spire.api.server.entry.v1.Entry/GetAuthorizedEntries": noLimit, "/spire.api.server.entry.v1.Entry/SyncAuthorizedEntries": noLimit, + "/spire.api.server.logger.v1.Logger/GetLogger": noLimit, + "/spire.api.server.logger.v1.Logger/SetLogLevel": noLimit, + "/spire.api.server.logger.v1.Logger/ResetLogLevel": noLimit, "/spire.api.server.agent.v1.Agent/CountAgents": noLimit, "/spire.api.server.agent.v1.Agent/ListAgents": noLimit, "/spire.api.server.agent.v1.Agent/GetAgent": noLimit, diff --git a/pkg/server/server.go b/pkg/server/server.go index 0c0c7a39597..304736e2555 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -75,6 +75,10 @@ func (s *Server) run(ctx context.Context) (err error) { telemetry.DataDir: s.config.DataDir, }).Info("Configured") + s.config.Log.WithFields(logrus.Fields{ + "LaunchLogLevel": s.config.LaunchLogLevel, + }).Info("Log Level") + // create the data directory if needed if err := diskutil.CreateDataDirectory(s.config.DataDir); err != nil { return err @@ -386,6 +390,8 @@ func (s *Server) newEndpointsServer(ctx context.Context, catalog catalog.Catalog Catalog: catalog, ServerCA: serverCA, Log: s.config.Log.WithField(telemetry.SubsystemName, telemetry.Endpoints), + RootLog: s.config.Log, + LaunchLogLevel: s.config.LaunchLogLevel, Metrics: metrics, JWTKeyPublisher: jwtKeyPublisher, RateLimit: s.config.RateLimit,