diff --git a/cmd/spire-agent/cli/cli.go b/cmd/spire-agent/cli/cli.go index 87284dec45..d5da6de923 100644 --- a/cmd/spire-agent/cli/cli.go +++ b/cmd/spire-agent/cli/cli.go @@ -6,6 +6,7 @@ import ( "github.com/mitchellh/cli" "github.com/spiffe/spire/cmd/spire-agent/cli/api" + "github.com/spiffe/spire/cmd/spire-agent/cli/debug" "github.com/spiffe/spire/cmd/spire-agent/cli/healthcheck" "github.com/spiffe/spire/cmd/spire-agent/cli/logger" "github.com/spiffe/spire/cmd/spire-agent/cli/run" @@ -23,6 +24,9 @@ func (cc *CLI) Run(ctx context.Context, args []string) int { c := cli.NewCLI("spire-agent", version.Version()) c.Args = args c.Commands = map[string]cli.CommandFactory{ + "debug getinfo": func() (cli.Command, error) { + return debug.NewGetInfoCommand(), nil + }, "api fetch": func() (cli.Command, error) { return api.NewFetchX509Command(), nil }, diff --git a/cmd/spire-agent/cli/debug/debug.go b/cmd/spire-agent/cli/debug/debug.go new file mode 100644 index 0000000000..fd83177ebc --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug.go @@ -0,0 +1,117 @@ +package debug + +import ( + "context" + "errors" + "flag" + "fmt" + "time" + + "github.com/mitchellh/cli" + debugv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/agent/debug/v1" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/spiffe/spire/pkg/common/cliprinter" + "github.com/spiffe/spire/pkg/common/util" +) + +// NewGetInfoCommand creates a new "debug getinfo" subcommand for "debug" command. +func NewGetInfoCommand() cli.Command { + return newGetInfoCommand(commoncli.DefaultEnv) +} + +func newGetInfoCommand(env *commoncli.Env) *getInfoCommand { + return &getInfoCommand{ + env: env, + } +} + +type getInfoCommand struct { + getInfoCommandOS + + env *commoncli.Env + printer cliprinter.Printer +} + +func (c *getInfoCommand) Help() string { + _ = c.parseFlags([]string{"-h"}) + return "" +} + +func (c *getInfoCommand) Synopsis() string { + return "Prints debug information about the agent" +} + +func (c *getInfoCommand) Run(args []string) int { + if err := c.parseFlags(args); err != nil { + return 1 + } + if err := c.run(); err != nil { + _ = c.env.ErrPrintf("Error: %v\n", err) + return 1 + } + return 0 +} + +func (c *getInfoCommand) parseFlags(args []string) error { + fs := flag.NewFlagSet("debug getinfo", flag.ContinueOnError) + fs.SetOutput(c.env.Stderr) + c.addOSFlags(fs) + cliprinter.AppendFlagWithCustomPretty(&c.printer, fs, c.env, prettyPrintGetInfo) + return fs.Parse(args) +} + +func (c *getInfoCommand) run() error { + addr, err := c.getAddr() + if err != nil { + return err + } + target, err := util.GetTargetName(addr) + if err != nil { + return err + } + conn, err := util.NewGRPCClient(target) + if err != nil { + return err + } + defer conn.Close() + + debugClient := debugv1.NewDebugClient(conn) + resp, err := debugClient.GetInfo(context.Background(), &debugv1.GetInfoRequest{}) + if err != nil { + return err + } + + return c.printer.PrintProto(resp) +} + +func prettyPrintGetInfo(env *commoncli.Env, results ...any) error { + resp, ok := results[0].(*debugv1.GetInfoResponse) + if !ok { + return errors.New("internal error: cli printer; please report this bug") + } + + env.Printf("Agent Debug Info:\n") + env.Printf(" Uptime: %s\n", (time.Duration(resp.Uptime) * time.Second).String()) + env.Printf(" Last Sync Success: %s\n", time.Unix(resp.LastSyncSuccess, 0).UTC().Format(time.RFC3339)) + env.Printf(" Cached X.509 SVIDs: %d\n", resp.CachedX509SvidsCount) + env.Printf(" Cached JWT SVIDs: %d\n", resp.CachedJwtSvidsCount) + env.Printf(" Cached SVID Store X.509 SVIDs: %d\n", resp.CachedSvidstoreX509SvidsCount) + + if len(resp.SvidChain) > 0 { + env.Printf(" SVID Chain:\n") + for i, cert := range resp.SvidChain { + env.Printf(" [%d] SPIFFE ID: %s\n", i, spiffeIDString(cert)) + env.Printf(" Subject: %s\n", cert.Subject) + env.Printf(" Expires At: %s\n", time.Unix(cert.ExpiresAt, 0).UTC().Format(time.RFC3339)) + } + } + + return nil +} + +func spiffeIDString(cert *debugv1.GetInfoResponse_Cert) string { + if cert.Id == nil { + return "(none)" + } + return fmt.Sprintf("spiffe://%s%s", cert.Id.TrustDomain, cert.Id.Path) +} diff --git a/cmd/spire-agent/cli/debug/debug_posix.go b/cmd/spire-agent/cli/debug/debug_posix.go new file mode 100644 index 0000000000..712993769e --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug_posix.go @@ -0,0 +1,23 @@ +//go:build !windows + +package debug + +import ( + "flag" + "net" + + "github.com/spiffe/spire/cmd/spire-agent/cli/common" + "github.com/spiffe/spire/pkg/common/util" +) + +type getInfoCommandOS struct { + socketPath string +} + +func (c *getInfoCommandOS) addOSFlags(flags *flag.FlagSet) { + flags.StringVar(&c.socketPath, "socketPath", common.DefaultAdminSocketPath, "Path to the SPIRE Agent admin API Unix domain socket") +} + +func (c *getInfoCommandOS) getAddr() (net.Addr, error) { + return util.GetUnixAddrWithAbsPath(c.socketPath) +} diff --git a/cmd/spire-agent/cli/debug/debug_posix_test.go b/cmd/spire-agent/cli/debug/debug_posix_test.go new file mode 100644 index 0000000000..e395a9f322 --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug_posix_test.go @@ -0,0 +1,25 @@ +//go:build !windows + +package debug + +import ( + "testing" + + "github.com/spiffe/spire/test/spiretest" + "google.golang.org/grpc" +) + +var ( + usage = `Usage of debug getinfo: + -output value + Desired output format (pretty, json); default: pretty. + -socketPath string + Path to the SPIRE Agent admin API Unix domain socket (default "/tmp/spire-agent/private/admin.sock") +` + socketAddrArg = "-socketPath" + socketAddrUnavailable = "/tmp/doesnotexist.sock" +) + +func startGRPCSocketServer(t *testing.T, registerFn func(srv *grpc.Server)) string { + return spiretest.StartGRPCServer(t, registerFn).String() +} diff --git a/cmd/spire-agent/cli/debug/debug_test.go b/cmd/spire-agent/cli/debug/debug_test.go new file mode 100644 index 0000000000..ffca1725bb --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug_test.go @@ -0,0 +1,137 @@ +package debug + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/mitchellh/cli" + debugv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/agent/debug/v1" + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +type debugTest struct { + stdin *bytes.Buffer + stdout *bytes.Buffer + stderr *bytes.Buffer + + cmd cli.Command +} + +func setupTest() *debugTest { + stdin := new(bytes.Buffer) + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + + cmd := newGetInfoCommand(&commoncli.Env{ + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + }) + + return &debugTest{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + cmd: cmd, + } +} + +func TestSynopsis(t *testing.T) { + test := setupTest() + require.Equal(t, "Prints debug information about the agent", test.cmd.Synopsis()) +} + +func TestHelp(t *testing.T) { + test := setupTest() + require.Empty(t, test.cmd.Help()) + require.Equal(t, usage, test.stderr.String()) +} + +func TestBadFlags(t *testing.T) { + test := setupTest() + + code := test.cmd.Run([]string{"-badflag"}) + require.NotEqual(t, 0, code) + require.Empty(t, test.stdout.String(), "stdout") + require.Equal(t, "flag provided but not defined: -badflag\n"+usage, test.stderr.String(), "stderr") +} + +func TestFailsOnUnavailable(t *testing.T) { + test := setupTest() + + code := test.cmd.Run([]string{socketAddrArg, socketAddrUnavailable}) + require.NotEqual(t, 0, code) + require.Empty(t, test.stdout.String(), "stdout") + require.Contains(t, test.stderr.String(), "Error:") +} + +func TestGetInfo(t *testing.T) { + now := time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC) + lastSync := now.Add(-30 * time.Second) + + fakeResp := &debugv1.GetInfoResponse{ + Uptime: 42, + LastSyncSuccess: lastSync.Unix(), + CachedX509SvidsCount: 3, + CachedJwtSvidsCount: 1, + CachedSvidstoreX509SvidsCount: 0, + SvidChain: []*debugv1.GetInfoResponse_Cert{ + { + Id: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/agent/foo"}, + ExpiresAt: now.Add(24 * time.Hour).Unix(), + Subject: "CN=agent", + }, + }, + } + + socketAddr := startGRPCSocketServer(t, func(srv *grpc.Server) { + debugv1.RegisterDebugServer(srv, &fakeDebugServer{resp: fakeResp}) + }) + + test := setupTest() + code := test.cmd.Run([]string{socketAddrArg, socketAddr}) + require.Equal(t, 0, code, "exit code; stderr: %s", test.stderr.String()) + require.Empty(t, test.stderr.String(), "stderr") + + out := test.stdout.String() + require.Contains(t, out, "Agent Debug Info:") + require.Contains(t, out, "42s") + require.Contains(t, out, "Cached X.509 SVIDs:") + require.Contains(t, out, "3") + require.Contains(t, out, "spiffe://example.org/spire/agent/foo") +} + +func TestGetInfoJSON(t *testing.T) { + fakeResp := &debugv1.GetInfoResponse{ + Uptime: 10, + LastSyncSuccess: 1705320000, + CachedX509SvidsCount: 2, + } + + socketAddr := startGRPCSocketServer(t, func(srv *grpc.Server) { + debugv1.RegisterDebugServer(srv, &fakeDebugServer{resp: fakeResp}) + }) + + test := setupTest() + code := test.cmd.Run([]string{socketAddrArg, socketAddr, "-output", "json"}) + require.Equal(t, 0, code, "exit code; stderr: %s", test.stderr.String()) + require.Empty(t, test.stderr.String(), "stderr") + + out := test.stdout.String() + require.Contains(t, out, `"uptime"`) + require.Contains(t, out, `"cached_x509_svids_count"`) +} + +type fakeDebugServer struct { + debugv1.UnimplementedDebugServer + resp *debugv1.GetInfoResponse +} + +func (s *fakeDebugServer) GetInfo(_ context.Context, _ *debugv1.GetInfoRequest) (*debugv1.GetInfoResponse, error) { + return s.resp, nil +} diff --git a/cmd/spire-agent/cli/debug/debug_windows.go b/cmd/spire-agent/cli/debug/debug_windows.go new file mode 100644 index 0000000000..d49e85c068 --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug_windows.go @@ -0,0 +1,23 @@ +//go:build windows + +package debug + +import ( + "flag" + "net" + + "github.com/spiffe/spire/cmd/spire-agent/cli/common" + "github.com/spiffe/spire/pkg/common/namedpipe" +) + +type getInfoCommandOS struct { + namedPipeName string +} + +func (c *getInfoCommandOS) addOSFlags(flags *flag.FlagSet) { + flags.StringVar(&c.namedPipeName, "namedPipeName", common.DefaultAdminNamedPipeName, "Pipe name of the SPIRE Agent admin API named pipe") +} + +func (c *getInfoCommandOS) getAddr() (net.Addr, error) { + return namedpipe.AddrFromName(c.namedPipeName), nil +} diff --git a/cmd/spire-agent/cli/debug/debug_windows_test.go b/cmd/spire-agent/cli/debug/debug_windows_test.go new file mode 100644 index 0000000000..675ff97288 --- /dev/null +++ b/cmd/spire-agent/cli/debug/debug_windows_test.go @@ -0,0 +1,26 @@ +//go:build windows + +package debug + +import ( + "testing" + + "github.com/spiffe/spire/pkg/common/namedpipe" + "github.com/spiffe/spire/test/spiretest" + "google.golang.org/grpc" +) + +var ( + usage = `Usage of debug getinfo: + -namedPipeName string + Pipe name of the SPIRE Agent admin API named pipe (default "\\spire-agent\\private\\admin") + -output value + Desired output format (pretty, json); default: pretty. +` + socketAddrArg = "-namedPipeName" + socketAddrUnavailable = "doesnotexist" +) + +func startGRPCSocketServer(t *testing.T, registerFn func(srv *grpc.Server)) string { + return namedpipe.GetPipeName(spiretest.StartGRPCServer(t, registerFn).String()) +} diff --git a/cmd/spire-server/cli/cli.go b/cmd/spire-server/cli/cli.go index 0e7c1636ac..86bc6cb8bb 100644 --- a/cmd/spire-server/cli/cli.go +++ b/cmd/spire-server/cli/cli.go @@ -10,6 +10,7 @@ import ( "github.com/mitchellh/cli" "github.com/spiffe/spire/cmd/spire-server/cli/agent" "github.com/spiffe/spire/cmd/spire-server/cli/bundle" + "github.com/spiffe/spire/cmd/spire-server/cli/debug" "github.com/spiffe/spire/cmd/spire-server/cli/entry" "github.com/spiffe/spire/cmd/spire-server/cli/federation" "github.com/spiffe/spire/cmd/spire-server/cli/healthcheck" @@ -39,6 +40,9 @@ func (cc *CLI) Run(ctx context.Context, args []string) int { c := cli.NewCLI("spire-server", version.Version()) c.Args = args c.Commands = map[string]cli.CommandFactory{ + "debug getinfo": func() (cli.Command, error) { + return debug.NewGetInfoCommand(), nil + }, "agent ban": func() (cli.Command, error) { return agent.NewBanCommand(), nil }, diff --git a/cmd/spire-server/cli/debug/debug.go b/cmd/spire-server/cli/debug/debug.go new file mode 100644 index 0000000000..8da9b39b19 --- /dev/null +++ b/cmd/spire-server/cli/debug/debug.go @@ -0,0 +1,83 @@ +package debug + +import ( + "context" + "errors" + "flag" + "fmt" + "time" + + "github.com/mitchellh/cli" + debugv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" + "github.com/spiffe/spire/cmd/spire-server/util" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/spiffe/spire/pkg/common/cliprinter" +) + +// NewGetInfoCommand creates a new "debug getinfo" subcommand for "debug" command. +func NewGetInfoCommand() cli.Command { + return NewGetInfoCommandWithEnv(commoncli.DefaultEnv) +} + +// NewGetInfoCommandWithEnv creates a new "debug getinfo" subcommand using the +// environment specified. +func NewGetInfoCommandWithEnv(env *commoncli.Env) cli.Command { + return util.AdaptCommand(env, &getInfoCommand{env: env}) +} + +type getInfoCommand struct { + env *commoncli.Env + printer cliprinter.Printer +} + +func (*getInfoCommand) Name() string { + return "debug getinfo" +} + +func (*getInfoCommand) Synopsis() string { + return "Prints debug information about the server" +} + +func (c *getInfoCommand) AppendFlags(fs *flag.FlagSet) { + cliprinter.AppendFlagWithCustomPretty(&c.printer, fs, c.env, prettyPrintGetInfo) +} + +func (c *getInfoCommand) Run(ctx context.Context, _ *commoncli.Env, client util.ServerClient) error { + debugClient := client.NewDebugClient() + resp, err := debugClient.GetInfo(ctx, &debugv1.GetInfoRequest{}) + if err != nil { + return err + } + return c.printer.PrintProto(resp) +} + +func prettyPrintGetInfo(env *commoncli.Env, results ...any) error { + resp, ok := results[0].(*debugv1.GetInfoResponse) + if !ok { + return errors.New("internal error: cli printer; please report this bug") + } + + env.Printf("Server Debug Info:\n") + env.Printf(" Uptime: %s\n", (time.Duration(resp.Uptime) * time.Second).String()) + env.Printf(" Registered Agents: %d\n", resp.AgentsCount) + env.Printf(" Registration Entries: %d\n", resp.EntriesCount) + env.Printf(" Federated Bundles: %d\n", resp.FederatedBundlesCount) + + if len(resp.SvidChain) > 0 { + env.Printf(" SVID Chain:\n") + for i, cert := range resp.SvidChain { + env.Printf(" [%d] SPIFFE ID: %s\n", i, spiffeIDString(cert)) + env.Printf(" Subject: %s\n", cert.Subject) + env.Printf(" Expires At: %s\n", time.Unix(cert.ExpiresAt, 0).UTC().Format(time.RFC3339)) + } + } + + return nil +} + +func spiffeIDString(cert *debugv1.GetInfoResponse_Cert) string { + if cert.Id == nil { + return "(none)" + } + return fmt.Sprintf("spiffe://%s%s", cert.Id.TrustDomain, cert.Id.Path) +} diff --git a/cmd/spire-server/cli/debug/debug_posix_test.go b/cmd/spire-server/cli/debug/debug_posix_test.go new file mode 100644 index 0000000000..4d43415672 --- /dev/null +++ b/cmd/spire-server/cli/debug/debug_posix_test.go @@ -0,0 +1,12 @@ +//go:build !windows + +package debug_test + +var getInfoUsage = `Usage of debug getinfo: + -instance string + Instance name to substitute into socket templates (env SPIRE_SERVER_PRIVATE_SOCKET_TEMPLATE). + -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/debug/debug_test.go b/cmd/spire-server/cli/debug/debug_test.go new file mode 100644 index 0000000000..e1d8de3762 --- /dev/null +++ b/cmd/spire-server/cli/debug/debug_test.go @@ -0,0 +1,140 @@ +package debug_test + +import ( + "bytes" + "context" + "testing" + "time" + + "github.com/mitchellh/cli" + debugv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/debug/v1" + "github.com/spiffe/spire-api-sdk/proto/spire/api/types" + "github.com/spiffe/spire/cmd/spire-server/cli/debug" + commoncli "github.com/spiffe/spire/pkg/common/cli" + "github.com/spiffe/spire/test/clitest" + "github.com/spiffe/spire/test/spiretest" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +type debugTest struct { + stdin *bytes.Buffer + stdout *bytes.Buffer + stderr *bytes.Buffer + args []string + server *fakeDebugServer + client cli.Command +} + +func setupTest(t *testing.T) *debugTest { + server := &fakeDebugServer{} + + addr := spiretest.StartGRPCServer(t, func(s *grpc.Server) { + debugv1.RegisterDebugServer(s, server) + }) + + stdin := new(bytes.Buffer) + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + + client := debug.NewGetInfoCommandWithEnv(&commoncli.Env{ + Stdin: stdin, + Stdout: stdout, + Stderr: stderr, + }) + + return &debugTest{ + stdin: stdin, + stdout: stdout, + stderr: stderr, + args: []string{clitest.AddrArg, clitest.GetAddr(addr)}, + server: server, + client: client, + } +} + +func TestSynopsis(t *testing.T) { + test := setupTest(t) + require.Equal(t, "Prints debug information about the server", test.client.Synopsis()) +} + +func TestHelp(t *testing.T) { + test := setupTest(t) + require.Equal(t, "flag: help requested", test.client.Help()) + require.Equal(t, getInfoUsage, test.stderr.String()) +} + +func TestBadFlags(t *testing.T) { + test := setupTest(t) + + code := test.client.Run([]string{"-badflag"}) + require.NotEqual(t, 0, code) + require.Empty(t, test.stdout.String(), "stdout") + require.Equal(t, "flag provided but not defined: -badflag\n"+getInfoUsage, test.stderr.String(), "stderr") +} + +func TestFailsOnUnavailable(t *testing.T) { + test := setupTest(t) + + code := test.client.Run([]string{clitest.AddrArg, clitest.AddrValue}) + require.NotEqual(t, 0, code) + require.Empty(t, test.stdout.String(), "stdout") + spiretest.AssertHasPrefix(t, test.stderr.String(), "Error: ") +} + +func TestGetInfo(t *testing.T) { + now := time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC) + + test := setupTest(t) + test.server.resp = &debugv1.GetInfoResponse{ + Uptime: 42, + AgentsCount: 10, + EntriesCount: 50, + FederatedBundlesCount: 2, + SvidChain: []*debugv1.GetInfoResponse_Cert{ + { + Id: &types.SPIFFEID{TrustDomain: "example.org", Path: "/spire/server"}, + ExpiresAt: now.Add(24 * time.Hour).Unix(), + Subject: "CN=server", + }, + }, + } + + code := test.client.Run(test.args) + require.Equal(t, 0, code, "exit code; stderr: %s", test.stderr.String()) + require.Empty(t, test.stderr.String(), "stderr") + + out := test.stdout.String() + require.Contains(t, out, "Server Debug Info:") + require.Contains(t, out, "42s") + require.Contains(t, out, "10") + require.Contains(t, out, "50") + require.Contains(t, out, "spiffe://example.org/spire/server") +} + +func TestGetInfoJSON(t *testing.T) { + test := setupTest(t) + test.server.resp = &debugv1.GetInfoResponse{ + Uptime: 10, + AgentsCount: 3, + EntriesCount: 7, + } + + code := test.client.Run(append(test.args, "-output", "json")) + require.Equal(t, 0, code, "exit code; stderr: %s", test.stderr.String()) + require.Empty(t, test.stderr.String(), "stderr") + + out := test.stdout.String() + require.Contains(t, out, `"uptime"`) + require.Contains(t, out, `"agents_count"`) + require.Contains(t, out, `"entries_count"`) +} + +type fakeDebugServer struct { + debugv1.UnimplementedDebugServer + resp *debugv1.GetInfoResponse +} + +func (s *fakeDebugServer) GetInfo(_ context.Context, _ *debugv1.GetInfoRequest) (*debugv1.GetInfoResponse, error) { + return s.resp, nil +} diff --git a/cmd/spire-server/cli/debug/debug_windows_test.go b/cmd/spire-server/cli/debug/debug_windows_test.go new file mode 100644 index 0000000000..cc99e05583 --- /dev/null +++ b/cmd/spire-server/cli/debug/debug_windows_test.go @@ -0,0 +1,10 @@ +//go:build windows + +package debug_test + +var getInfoUsage = `Usage of debug getinfo: + -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/util/util.go b/cmd/spire-server/util/util.go index edc154f1bd..ad2e58d1c1 100644 --- a/cmd/spire-server/util/util.go +++ b/cmd/spire-server/util/util.go @@ -11,6 +11,7 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" 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" + 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" localauthorityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/localauthority/v1" loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" @@ -44,6 +45,7 @@ type ServerClient interface { Release() NewAgentClient() agentv1.AgentClient NewBundleClient() bundlev1.BundleClient + NewDebugClient() debugv1.DebugClient NewEntryClient() entryv1.EntryClient NewLoggerClient() loggerv1.LoggerClient NewSVIDClient() svidv1.SVIDClient @@ -76,6 +78,10 @@ func (c *serverClient) NewBundleClient() bundlev1.BundleClient { return bundlev1.NewBundleClient(c.conn) } +func (c *serverClient) NewDebugClient() debugv1.DebugClient { + return debugv1.NewDebugClient(c.conn) +} + func (c *serverClient) NewEntryClient() entryv1.EntryClient { return entryv1.NewEntryClient(c.conn) } diff --git a/conf/agent/agent.conf b/conf/agent/agent.conf index cf1fcb353a..42734416b4 100644 --- a/conf/agent/agent.conf +++ b/conf/agent/agent.conf @@ -4,6 +4,7 @@ agent { server_address = "127.0.0.1" server_port = "8081" socket_path ="/tmp/spire-agent/public/api.sock" + admin_socket_path ="/tmp/spire-agent/private/admin.sock" trust_bundle_path = "./conf/agent/dummy_root_ca.crt" trust_domain = "example.org" } diff --git a/doc/spire_agent.md b/doc/spire_agent.md index 091ce15c4b..5bc612789d 100644 --- a/doc/spire_agent.md +++ b/doc/spire_agent.md @@ -385,6 +385,16 @@ Attaches to the workload API and watches for X509-SVID updates, printing details |---------------|------------------------------------|----------------------------------| | `-socketPath` | Path to the SPIRE Agent API socket | /tmp/spire-agent/public/api.sock | +### `spire-agent debug getinfo` + +Prints debug information about the agent, including uptime, last successful +sync time, cached SVID counts, and the agent's own SVID chain. + +| Command | Action | Default | +|:--------------|:------------------------------------------|:------------------------------------| +| `-output` | Desired output format (`pretty`, `json`) | `pretty` | +| `-socketPath` | Path to the SPIRE Agent admin API socket | /tmp/spire-agent/private/admin.sock | + ### `spire-agent healthcheck` Checks SPIRE agent's health. diff --git a/doc/spire_server.md b/doc/spire_server.md index 11edf4de4a..57a335661a 100644 --- a/doc/spire_server.md +++ b/doc/spire_server.md @@ -628,6 +628,16 @@ Displays the details (including node selectors) of an attested node given its sp | `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | | `-spiffeID` | The SPIFFE ID of the agent to show (agent identity) | | +### `spire-server debug getinfo` + +Prints debug information about the server, including uptime, registered +agent/entry/federated bundle counts, and the server's own SVID chain. + +| Command | Action | Default | +|:--------------|:-----------------------------------------|:-----------------------------------| +| `-output` | Desired output format (`pretty`, `json`) | `pretty` | +| `-socketPath` | Path to the SPIRE Server API socket | /tmp/spire-server/private/api.sock | + ### `spire-server healthcheck` Checks SPIRE server's health.