-
Notifications
You must be signed in to change notification settings - Fork 487
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New spire-server command to print its bundle to stdout in PEM format (#…
- Loading branch information
1 parent
dbd7d96
commit 544faa4
Showing
16 changed files
with
733 additions
and
351 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package bundle | ||
|
||
import ( | ||
"context" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"flag" | ||
"fmt" | ||
"io" | ||
"os" | ||
|
||
"github.com/mitchellh/cli" | ||
"github.com/spiffe/spire/cmd/spire-server/util" | ||
"github.com/spiffe/spire/proto/api/registration" | ||
"github.com/spiffe/spire/proto/common" | ||
) | ||
|
||
type showCLI struct { | ||
newRegistrationClient func(addr string) (registration.RegistrationClient, error) | ||
writer io.Writer | ||
} | ||
|
||
type showConfig struct { | ||
// Address of SPIRE server | ||
addr string | ||
} | ||
|
||
// NewShowCommand creates a new "show" subcommand for "bundle" command. | ||
func NewShowCommand() cli.Command { | ||
return &showCLI{ | ||
writer: os.Stdout, | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
return util.NewRegistrationClient(addr) | ||
}, | ||
} | ||
} | ||
|
||
func (*showCLI) Synopsis() string { | ||
return "Prints CA bundle to standard out" | ||
} | ||
|
||
func (s *showCLI) Help() string { | ||
_, err := s.newConfig([]string{"-h"}) | ||
return err.Error() | ||
} | ||
|
||
func (s *showCLI) Run(args []string) int { | ||
config, err := s.newConfig(args) | ||
if err != nil { | ||
fmt.Println(err.Error()) | ||
return 1 | ||
} | ||
|
||
c, err := s.newRegistrationClient(config.addr) | ||
if err != nil { | ||
fmt.Println(err.Error()) | ||
return 1 | ||
} | ||
|
||
bundle, err := c.FetchBundle(context.TODO(), &common.Empty{}) | ||
if err != nil { | ||
fmt.Println(err.Error()) | ||
return 1 | ||
} | ||
|
||
err = s.printBundleAsPEM(bundle) | ||
if err != nil { | ||
fmt.Println(err.Error()) | ||
return 1 | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
func (*showCLI) newConfig(args []string) (*showConfig, error) { | ||
f := flag.NewFlagSet("bundle show", flag.ContinueOnError) | ||
c := &showConfig{} | ||
f.StringVar(&c.addr, "serverAddr", util.DefaultServerAddr, "Address of the SPIRE server") | ||
return c, f.Parse(args) | ||
} | ||
|
||
func (s *showCLI) printBundleAsPEM(bundle *registration.Bundle) error { | ||
certs, err := x509.ParseCertificates(bundle.CaCerts) | ||
if err != nil { | ||
return fmt.Errorf("FAILED to parse bundle's ASN.1 DER data: %v", err) | ||
} | ||
|
||
for _, cert := range certs { | ||
err := pem.Encode(s.writer, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package bundle | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/pem" | ||
"errors" | ||
"fmt" | ||
"os" | ||
"testing" | ||
|
||
"github.com/golang/mock/gomock" | ||
"github.com/spiffe/spire/proto/api/registration" | ||
"github.com/spiffe/spire/proto/common" | ||
"github.com/spiffe/spire/test/mock/proto/api/registration" | ||
"github.com/spiffe/spire/test/util" | ||
"github.com/stretchr/testify/suite" | ||
) | ||
|
||
type ShowTestSuite struct { | ||
suite.Suite | ||
mockClient *mock_registration.MockRegistrationClient | ||
} | ||
|
||
func TestShowTestSuite(t *testing.T) { | ||
suite.Run(t, new(ShowTestSuite)) | ||
} | ||
|
||
func (s *ShowTestSuite) SetupTest() { | ||
mockCtrl := gomock.NewController(s.T()) | ||
defer mockCtrl.Finish() | ||
|
||
s.mockClient = mock_registration.NewMockRegistrationClient(mockCtrl) | ||
} | ||
|
||
func (s *ShowTestSuite) TestSynopsisAndHelp() { | ||
cmd := NewShowCommand() | ||
|
||
s.Assert().Equal("Prints CA bundle to standard out", cmd.Synopsis()) | ||
|
||
s.Assert().Equal("flag: help requested", cmd.Help()) | ||
} | ||
|
||
func (s *ShowTestSuite) TestRunWithDefaultArgs() { | ||
cli := &showCLI{ | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
return s.mockClient, nil | ||
}, | ||
writer: &bytes.Buffer{}, | ||
} | ||
|
||
ca, _, err := util.LoadCAFixture() | ||
s.Require().Nil(err) | ||
|
||
resp := ®istration.Bundle{CaCerts: ca.Raw} | ||
s.mockClient.EXPECT().FetchBundle(context.TODO(), &common.Empty{}).Return(resp, nil) | ||
|
||
args := []string{} | ||
s.Require().Equal(0, cli.Run(args)) | ||
|
||
bundleASN1 := transcodeBundleFromPEMToASN1DER(cli.writer.(*bytes.Buffer).Bytes()) | ||
|
||
s.Assert().Equal(ca.Raw, bundleASN1) | ||
} | ||
|
||
func (s *ShowTestSuite) TestRunWithDefaultArgsAndFailedNewRegClient() { | ||
expecterError := errors.New("error creating client") | ||
|
||
cli := &showCLI{ | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
return nil, expecterError | ||
}, | ||
} | ||
|
||
stdOutRedir := &util.OutputRedirection{} | ||
err := stdOutRedir.Start(os.Stdout) | ||
s.Require().Nil(err) | ||
|
||
args := []string{} | ||
s.Require().Equal(1, cli.Run(args)) | ||
|
||
output, err := stdOutRedir.Finish() | ||
s.Require().Nil(err) | ||
|
||
s.Assert().Equal(output, fmt.Sprintln(expecterError.Error())) | ||
} | ||
|
||
func (s *ShowTestSuite) TestRunWithDefaultArgsAndFailedFetchBundle() { | ||
expecterError := errors.New("error fetching bundle") | ||
|
||
cli := &showCLI{ | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
return s.mockClient, nil | ||
}, | ||
} | ||
|
||
s.mockClient.EXPECT().FetchBundle(context.TODO(), &common.Empty{}).Return(nil, expecterError) | ||
|
||
stdOutRedir := &util.OutputRedirection{} | ||
err := stdOutRedir.Start(os.Stdout) | ||
s.Require().Nil(err) | ||
|
||
args := []string{} | ||
s.Require().Equal(1, cli.Run(args)) | ||
|
||
output, err := stdOutRedir.Finish() | ||
s.Require().Nil(err) | ||
|
||
s.Assert().Equal(output, fmt.Sprintln(expecterError.Error())) | ||
} | ||
|
||
func (s *ShowTestSuite) TestRunWithArgs() { | ||
expecterAddr := "localhost:8080" | ||
|
||
cli := &showCLI{ | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
s.Assert().Equal(expecterAddr, addr) | ||
return s.mockClient, nil | ||
}, | ||
} | ||
|
||
resp := ®istration.Bundle{} | ||
s.mockClient.EXPECT().FetchBundle(context.TODO(), &common.Empty{}).Return(resp, nil) | ||
|
||
args := []string{"-serverAddr", expecterAddr} | ||
s.Require().Equal(0, cli.Run(args)) | ||
} | ||
|
||
func (s *ShowTestSuite) TestRunWithWrongArgs() { | ||
cli := &showCLI{ | ||
newRegistrationClient: func(addr string) (registration.RegistrationClient, error) { | ||
return s.mockClient, nil | ||
}, | ||
} | ||
|
||
resp := ®istration.Bundle{} | ||
s.mockClient.EXPECT().FetchBundle(context.TODO(), &common.Empty{}).Return(resp, nil) | ||
|
||
stdOutRedir := util.OutputRedirection{} | ||
stdErrRedir := util.OutputRedirection{} | ||
err := stdOutRedir.Start(os.Stdout) | ||
s.Require().Nil(err) | ||
err = stdErrRedir.Start(os.Stderr) | ||
s.Require().Nil(err) | ||
|
||
args := []string{"-someArg", "someValue"} | ||
s.Require().Equal(1, cli.Run(args)) | ||
|
||
output, err := stdOutRedir.Finish() | ||
s.Require().Nil(err) | ||
errOutput, err := stdErrRedir.Finish() | ||
s.Require().Nil(err) | ||
|
||
expectedOutput := "flag provided but not defined: -someArg\n" | ||
|
||
expectedErrOutput := "flag provided but not defined: -someArg\n" + | ||
"Usage of bundle show:\n" + | ||
" -serverAddr string\n" + | ||
" \tAddress of the SPIRE server (default \"localhost:8081\")\n" | ||
|
||
s.Assert().Equal(expectedOutput, output) | ||
s.Assert().Equal(expectedErrOutput, errOutput) | ||
} | ||
|
||
func transcodeBundleFromPEMToASN1DER(pemBundle []byte) []byte { | ||
result := &bytes.Buffer{} | ||
for p, r := pem.Decode(pemBundle); p != nil; p, r = pem.Decode(r) { | ||
result.Write(p.Bytes) | ||
} | ||
return result.Bytes() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.