Skip to content

Commit

Permalink
Add upstreamauthority commands and leverage the `UpstreamAuthorityS…
Browse files Browse the repository at this point in the history
…ubjectKeyId` field in `AuthorityState` messages (#5518)

- Add `upstreamauthority revoke` and `upstreamauthority taint` commands
- Leverage the `UpstreamAuthoritySubjectKeyId` field in `AuthorityState` messages

Signed-off-by: Agustín Martínez Fayó <amartinezfayo@gmail.com>
  • Loading branch information
amartinezfayo committed Sep 27, 2024
1 parent e9179be commit cfb994a
Show file tree
Hide file tree
Showing 35 changed files with 765 additions and 322 deletions.
171 changes: 16 additions & 155 deletions cmd/spire-server/cli/authoritycommon/authoritycommon.go
Original file line number Diff line number Diff line change
@@ -1,170 +1,31 @@
package authoritycommon_test
package authoritycommon

import (
"bytes"
"context"
"testing"
"time"

"github.com/mitchellh/cli"
localauthorityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/localauthority/v1"
"github.com/spiffe/spire/cmd/spire-server/cli/common"
commoncli "github.com/spiffe/spire/pkg/common/cli"
"github.com/spiffe/spire/test/spiretest"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)

var (
AvailableFormats = []string{"pretty", "json"}
)

type localAuthorityTest struct {
Stdin *bytes.Buffer
Stdout *bytes.Buffer
Stderr *bytes.Buffer
Args []string
Server *fakeLocalAuthorityServer
Client cli.Command
func PrettyPrintJWTAuthorityState(env *commoncli.Env, authorityState *localauthorityv1.AuthorityState) {
prettyPrintAuthorityState(env, authorityState, false)
}

func (s *localAuthorityTest) afterTest(t *testing.T) {
t.Logf("TEST:%s", t.Name())
t.Logf("STDOUT:\n%s", s.Stdout.String())
t.Logf("STDIN:\n%s", s.Stdin.String())
t.Logf("STDERR:\n%s", s.Stderr.String())
func PrettyPrintX509AuthorityState(env *commoncli.Env, authorityState *localauthorityv1.AuthorityState) {
prettyPrintAuthorityState(env, authorityState, true)
}

func SetupTest(t *testing.T, newClient func(*commoncli.Env) cli.Command) *localAuthorityTest {
server := &fakeLocalAuthorityServer{}

addr := spiretest.StartGRPCServer(t, func(s *grpc.Server) {
localauthorityv1.RegisterLocalAuthorityServer(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 := &localAuthorityTest{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Args: []string{common.AddrArg, common.GetAddr(addr)},
Server: server,
Client: client,
func prettyPrintAuthorityState(env *commoncli.Env, authorityState *localauthorityv1.AuthorityState, includeUpstreamAuthority bool) {
env.Printf(" Authority ID: %s\n", authorityState.AuthorityId)
env.Printf(" Expires at: %s\n", time.Unix(authorityState.ExpiresAt, 0).UTC())
if !includeUpstreamAuthority {
return
}

t.Cleanup(func() {
test.afterTest(t)
})

return test
}

type fakeLocalAuthorityServer struct {
localauthorityv1.UnsafeLocalAuthorityServer

ActiveJWT,
PreparedJWT,
OldJWT,
ActiveX509,
PreparedX509,
OldX509,
TaintedX509,
RevokedX509,
TaintedJWT,
RevokedJWT *localauthorityv1.AuthorityState

Err error
}

func (s *fakeLocalAuthorityServer) GetJWTAuthorityState(context.Context, *localauthorityv1.GetJWTAuthorityStateRequest) (*localauthorityv1.GetJWTAuthorityStateResponse, error) {
return &localauthorityv1.GetJWTAuthorityStateResponse{
Active: s.ActiveJWT,
Prepared: s.PreparedJWT,
Old: s.OldJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) PrepareJWTAuthority(context.Context, *localauthorityv1.PrepareJWTAuthorityRequest) (*localauthorityv1.PrepareJWTAuthorityResponse, error) {
return &localauthorityv1.PrepareJWTAuthorityResponse{
PreparedAuthority: s.PreparedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) ActivateJWTAuthority(context.Context, *localauthorityv1.ActivateJWTAuthorityRequest) (*localauthorityv1.ActivateJWTAuthorityResponse, error) {
return &localauthorityv1.ActivateJWTAuthorityResponse{
ActivatedAuthority: s.ActiveJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintJWTAuthority(context.Context, *localauthorityv1.TaintJWTAuthorityRequest) (*localauthorityv1.TaintJWTAuthorityResponse, error) {
return &localauthorityv1.TaintJWTAuthorityResponse{
TaintedAuthority: s.TaintedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeJWTAuthority(context.Context, *localauthorityv1.RevokeJWTAuthorityRequest) (*localauthorityv1.RevokeJWTAuthorityResponse, error) {
return &localauthorityv1.RevokeJWTAuthorityResponse{
RevokedAuthority: s.RevokedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) GetX509AuthorityState(context.Context, *localauthorityv1.GetX509AuthorityStateRequest) (*localauthorityv1.GetX509AuthorityStateResponse, error) {
return &localauthorityv1.GetX509AuthorityStateResponse{
Active: s.ActiveX509,
Prepared: s.PreparedX509,
Old: s.OldX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) PrepareX509Authority(context.Context, *localauthorityv1.PrepareX509AuthorityRequest) (*localauthorityv1.PrepareX509AuthorityResponse, error) {
return &localauthorityv1.PrepareX509AuthorityResponse{
PreparedAuthority: s.PreparedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) ActivateX509Authority(context.Context, *localauthorityv1.ActivateX509AuthorityRequest) (*localauthorityv1.ActivateX509AuthorityResponse, error) {
return &localauthorityv1.ActivateX509AuthorityResponse{
ActivatedAuthority: s.ActiveX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintX509Authority(context.Context, *localauthorityv1.TaintX509AuthorityRequest) (*localauthorityv1.TaintX509AuthorityResponse, error) {
return &localauthorityv1.TaintX509AuthorityResponse{
TaintedAuthority: s.TaintedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintX509UpstreamAuthority(context.Context, *localauthorityv1.TaintX509UpstreamAuthorityRequest) (*localauthorityv1.TaintX509UpstreamAuthorityResponse, error) {
return &localauthorityv1.TaintX509UpstreamAuthorityResponse{}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeX509Authority(context.Context, *localauthorityv1.RevokeX509AuthorityRequest) (*localauthorityv1.RevokeX509AuthorityResponse, error) {
return &localauthorityv1.RevokeX509AuthorityResponse{
RevokedAuthority: s.RevokedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeX509UpstreamAuthority(context.Context, *localauthorityv1.RevokeX509UpstreamAuthorityRequest) (*localauthorityv1.RevokeX509UpstreamAuthorityResponse, error) {
return &localauthorityv1.RevokeX509UpstreamAuthorityResponse{}, s.Err
}

func RequireOutputBasedOnFormat(t *testing.T, format, stdoutString string, expectedStdoutPretty, expectedStdoutJSON string) {
switch format {
case "pretty":
require.Contains(t, stdoutString, expectedStdoutPretty)
case "json":
if expectedStdoutJSON != "" {
require.JSONEq(t, expectedStdoutJSON, stdoutString)
} else {
require.Empty(t, stdoutString)
}
if authorityState.UpstreamAuthoritySubjectKeyId != "" {
env.Printf(" Upstream authority Subject Key ID: %s\n", authorityState.UpstreamAuthoritySubjectKeyId)
return
}

env.Println(" Upstream authority ID: No upstream authority")
}
176 changes: 176 additions & 0 deletions cmd/spire-server/cli/authoritycommon/test/authoritycommontest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package authoritycommontest

import (
"bytes"
"context"
"testing"

"github.com/mitchellh/cli"
localauthorityv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/localauthority/v1"
"github.com/spiffe/spire/cmd/spire-server/cli/common"
commoncli "github.com/spiffe/spire/pkg/common/cli"
"github.com/spiffe/spire/test/spiretest"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)

var (
AvailableFormats = []string{"pretty", "json"}
)

type localAuthorityTest struct {
Stdin *bytes.Buffer
Stdout *bytes.Buffer
Stderr *bytes.Buffer
Args []string
Server *fakeLocalAuthorityServer
Client cli.Command
}

func (s *localAuthorityTest) afterTest(t *testing.T) {
t.Logf("TEST:%s", t.Name())
t.Logf("STDOUT:\n%s", s.Stdout.String())
t.Logf("STDIN:\n%s", s.Stdin.String())
t.Logf("STDERR:\n%s", s.Stderr.String())
}

func SetupTest(t *testing.T, newClient func(*commoncli.Env) cli.Command) *localAuthorityTest {
server := &fakeLocalAuthorityServer{}

addr := spiretest.StartGRPCServer(t, func(s *grpc.Server) {
localauthorityv1.RegisterLocalAuthorityServer(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 := &localAuthorityTest{
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
}

type fakeLocalAuthorityServer struct {
localauthorityv1.UnsafeLocalAuthorityServer

ActiveJWT,
PreparedJWT,
OldJWT,
ActiveX509,
PreparedX509,
OldX509,
TaintedX509,
RevokedX509,
TaintedJWT,
RevokedJWT *localauthorityv1.AuthorityState

TaintedUpstreamAuthoritySubjectKeyId,
RevokedUpstreamAuthoritySubjectKeyId string
Err error
}

func (s *fakeLocalAuthorityServer) GetJWTAuthorityState(context.Context, *localauthorityv1.GetJWTAuthorityStateRequest) (*localauthorityv1.GetJWTAuthorityStateResponse, error) {
return &localauthorityv1.GetJWTAuthorityStateResponse{
Active: s.ActiveJWT,
Prepared: s.PreparedJWT,
Old: s.OldJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) PrepareJWTAuthority(context.Context, *localauthorityv1.PrepareJWTAuthorityRequest) (*localauthorityv1.PrepareJWTAuthorityResponse, error) {
return &localauthorityv1.PrepareJWTAuthorityResponse{
PreparedAuthority: s.PreparedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) ActivateJWTAuthority(context.Context, *localauthorityv1.ActivateJWTAuthorityRequest) (*localauthorityv1.ActivateJWTAuthorityResponse, error) {
return &localauthorityv1.ActivateJWTAuthorityResponse{
ActivatedAuthority: s.ActiveJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintJWTAuthority(context.Context, *localauthorityv1.TaintJWTAuthorityRequest) (*localauthorityv1.TaintJWTAuthorityResponse, error) {
return &localauthorityv1.TaintJWTAuthorityResponse{
TaintedAuthority: s.TaintedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeJWTAuthority(context.Context, *localauthorityv1.RevokeJWTAuthorityRequest) (*localauthorityv1.RevokeJWTAuthorityResponse, error) {
return &localauthorityv1.RevokeJWTAuthorityResponse{
RevokedAuthority: s.RevokedJWT,
}, s.Err
}

func (s *fakeLocalAuthorityServer) GetX509AuthorityState(context.Context, *localauthorityv1.GetX509AuthorityStateRequest) (*localauthorityv1.GetX509AuthorityStateResponse, error) {
return &localauthorityv1.GetX509AuthorityStateResponse{
Active: s.ActiveX509,
Prepared: s.PreparedX509,
Old: s.OldX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) PrepareX509Authority(context.Context, *localauthorityv1.PrepareX509AuthorityRequest) (*localauthorityv1.PrepareX509AuthorityResponse, error) {
return &localauthorityv1.PrepareX509AuthorityResponse{
PreparedAuthority: s.PreparedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) ActivateX509Authority(context.Context, *localauthorityv1.ActivateX509AuthorityRequest) (*localauthorityv1.ActivateX509AuthorityResponse, error) {
return &localauthorityv1.ActivateX509AuthorityResponse{
ActivatedAuthority: s.ActiveX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintX509Authority(context.Context, *localauthorityv1.TaintX509AuthorityRequest) (*localauthorityv1.TaintX509AuthorityResponse, error) {
return &localauthorityv1.TaintX509AuthorityResponse{
TaintedAuthority: s.TaintedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) TaintX509UpstreamAuthority(context.Context, *localauthorityv1.TaintX509UpstreamAuthorityRequest) (*localauthorityv1.TaintX509UpstreamAuthorityResponse, error) {
return &localauthorityv1.TaintX509UpstreamAuthorityResponse{
UpstreamAuthoritySubjectKeyId: s.TaintedUpstreamAuthoritySubjectKeyId,
}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeX509Authority(context.Context, *localauthorityv1.RevokeX509AuthorityRequest) (*localauthorityv1.RevokeX509AuthorityResponse, error) {
return &localauthorityv1.RevokeX509AuthorityResponse{
RevokedAuthority: s.RevokedX509,
}, s.Err
}

func (s *fakeLocalAuthorityServer) RevokeX509UpstreamAuthority(context.Context, *localauthorityv1.RevokeX509UpstreamAuthorityRequest) (*localauthorityv1.RevokeX509UpstreamAuthorityResponse, error) {
return &localauthorityv1.RevokeX509UpstreamAuthorityResponse{
UpstreamAuthoritySubjectKeyId: s.RevokedUpstreamAuthoritySubjectKeyId,
}, s.Err
}

func RequireOutputBasedOnFormat(t *testing.T, format, stdoutString string, expectedStdoutPretty, expectedStdoutJSON string) {
switch format {
case "pretty":
require.Contains(t, stdoutString, expectedStdoutPretty)
case "json":
if expectedStdoutJSON != "" {
require.JSONEq(t, expectedStdoutJSON, stdoutString)
} else {
require.Empty(t, stdoutString)
}
}
}
7 changes: 7 additions & 0 deletions cmd/spire-server/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"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/upstreamauthority"
"github.com/spiffe/spire/cmd/spire-server/cli/validate"
"github.com/spiffe/spire/cmd/spire-server/cli/x509"
"github.com/spiffe/spire/pkg/common/fflag"
Expand Down Expand Up @@ -193,5 +194,11 @@ func addCommandsEnabledByFFlags(commands map[string]cli.CommandFactory) {
commands["localauthority jwt revoke"] = func() (cli.Command, error) {
return localauthority_jwt.NewJWTRevokeCommand(), nil
}
commands["upstreamauthority taint"] = func() (cli.Command, error) {
return upstreamauthority.NewTaintCommand(), nil
}
commands["upstreamauthority revoke"] = func() (cli.Command, error) {
return upstreamauthority.NewRevokeCommand(), nil
}
}
}
Loading

0 comments on commit cfb994a

Please sign in to comment.