Skip to content

Commit

Permalink
New spire-server command to print its bundle to stdout in PEM format (#…
Browse files Browse the repository at this point in the history
…399)

Fixes #393
  • Loading branch information
martincapello authored Mar 26, 2018
1 parent dbd7d96 commit 544faa4
Show file tree
Hide file tree
Showing 16 changed files with 733 additions and 351 deletions.
95 changes: 95 additions & 0 deletions cmd/spire-server/cli/bundle/show.go
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
}
171 changes: 171 additions & 0 deletions cmd/spire-server/cli/bundle/show_test.go
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 := &registration.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 := &registration.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 := &registration.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()
}
4 changes: 4 additions & 0 deletions cmd/spire-server/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log"

"github.com/mitchellh/cli"
"github.com/spiffe/spire/cmd/spire-server/cli/bundle"
"github.com/spiffe/spire/cmd/spire-server/cli/entry"
"github.com/spiffe/spire/cmd/spire-server/cli/run"
"github.com/spiffe/spire/cmd/spire-server/cli/token"
Expand All @@ -13,6 +14,9 @@ func Run(args []string) int {
c := cli.NewCLI("spire-server", "0.0.1") //TODO expose version configuration
c.Args = args
c.Commands = map[string]cli.CommandFactory{
"bundle show": func() (cli.Command, error) {
return bundle.NewShowCommand(), nil
},
"entry create": func() (cli.Command, error) {
return &entry.CreateCLI{}, nil
},
Expand Down
5 changes: 3 additions & 2 deletions pkg/server/endpoints/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,9 @@ func (e *endpoints) registerRegistrationAPI(gs *grpc.Server, hs *http.Server) er
}

r := &registration.Handler{
Log: e.c.Log.WithField("subsystem_name", "registration_api"),
Catalog: e.c.Catalog,
Log: e.c.Log.WithField("subsystem_name", "registration_api"),
Catalog: e.c.Catalog,
TrustDomain: e.c.TrustDomain,
}

// Register the handler with gRPC first
Expand Down
7 changes: 0 additions & 7 deletions pkg/server/endpoints/node/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,6 @@ func (h *Handler) FetchSVID(server node.Node_FetchSVIDServer) (err error) {
}
}

//TODO
func (h *Handler) FetchCPBundle(
ctx context.Context, request *node.FetchCPBundleRequest) (
response *node.FetchCPBundleResponse, err error) {
return response, nil
}

//TODO
func (h *Handler) FetchFederatedBundle(
ctx context.Context, request *node.FetchFederatedBundleRequest) (
Expand Down
22 changes: 20 additions & 2 deletions pkg/server/endpoints/registration/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package registration
import (
"errors"
"fmt"
"net/url"
"time"

"github.com/satori/go.uuid"
Expand All @@ -17,8 +18,9 @@ import (
//Service is used to register SPIFFE IDs, and the attestation logic that should
//be performed on a workload before those IDs can be issued.
type Handler struct {
Log logrus.FieldLogger
Catalog catalog.Catalog
Log logrus.FieldLogger
Catalog catalog.Catalog
TrustDomain url.URL
}

//Creates an entry in the Registration table,
Expand Down Expand Up @@ -211,3 +213,19 @@ func (h *Handler) CreateJoinToken(

return request, nil
}

// FetchBundle retrieves the CA bundle.
func (h *Handler) FetchBundle(
ctx context.Context, request *common.Empty) (
response *registration.Bundle, err error) {
ds := h.Catalog.DataStores()[0]
req := &datastore.Bundle{
TrustDomain: h.TrustDomain.String(),
}
b, err := ds.FetchBundle(req)
if err != nil {
return nil, fmt.Errorf("get bundle from datastore: %v", err)
}

return &registration.Bundle{CaCerts: b.CaCerts}, nil
}
Loading

0 comments on commit 544faa4

Please sign in to comment.