Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Evict method #693

Merged
merged 12 commits into from
Feb 4, 2019
102 changes: 102 additions & 0 deletions cmd/spire-server/cli/agent/evict.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package agent

import (
"errors"
"flag"
"fmt"
"os"

"github.com/spiffe/spire/cmd/spire-server/util"
"github.com/spiffe/spire/pkg/common/idutil"
"github.com/spiffe/spire/proto/api/registration"

"golang.org/x/net/context"
)

//EvictConfig holds configuration for EvictCLI
type EvictConfig struct {
// Socket path of registration API
RegistrationUDSPath string
// SpiffeID of the agent being evicted
SpiffeID string
}

// Validate will perform a basic validation on config fields
func (c *EvictConfig) Validate() (err error) {
if c.RegistrationUDSPath == "" {
return errors.New("a socket path for registration api is required")
}

if c.SpiffeID == "" {
return errors.New("a SPIFFE ID is required")
}

// make sure SPIFFE ID is well formed
c.SpiffeID, err = idutil.NormalizeSpiffeID(c.SpiffeID, idutil.AllowAnyTrustDomainAgent())
if err != nil {
return err
}

return nil
}

//EvictCLI command for node eviction
type EvictCLI struct {
registrationClient registration.RegistrationClient
}

func (EvictCLI) Synopsis() string {
return "Evicts an attested agent given its SPIFFE ID"
}

func (c EvictCLI) Help() string {
_, err := c.parseConfig([]string{"-h"})
return err.Error()
}

//Run will evict an agent given its spiffeID
func (c EvictCLI) Run(args []string) int {
ctx := context.Background()

config, err := c.parseConfig(args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}

if err = config.Validate(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}

if c.registrationClient == nil {
c.registrationClient, err = util.NewRegistrationClient(config.RegistrationUDSPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error establishing connection to the Registration API: %v \n", err)
return 1
}
}
evictResponse, err := c.registrationClient.EvictAgent(ctx, &registration.EvictAgentRequest{SpiffeID: config.SpiffeID})
if err != nil {
fmt.Fprintf(os.Stderr, "Error evicting agent: %v \n", err)
return 1
}

if evictResponse.Node == nil {
fmt.Fprintln(os.Stderr, "Failed to evict agent")
return 1
}

fmt.Println("Agent evicted successfully")
return 0
}

func (EvictCLI) parseConfig(args []string) (*EvictConfig, error) {
f := flag.NewFlagSet("agent evict", flag.ContinueOnError)
c := &EvictConfig{}

f.StringVar(&c.RegistrationUDSPath, "registrationUDSPath", util.DefaultSocketPath, "Registration API UDS path")
f.StringVar(&c.SpiffeID, "spiffeID", "", "The SPIFFE ID of the agent to evict (agent identity)")

return c, f.Parse(args)
}
84 changes: 84 additions & 0 deletions cmd/spire-server/cli/agent/evict_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package agent

import (
"errors"
"testing"

"github.com/spiffe/spire/proto/common"

"github.com/spiffe/spire/proto/api/registration"

"github.com/golang/mock/gomock"
"github.com/spiffe/spire/test/mock/proto/api/registration"
"github.com/stretchr/testify/suite"
)

type EvictTestSuite struct {
suite.Suite
cli *EvictCLI
mockClient *mock_registration.MockRegistrationClient
mockCtrl *gomock.Controller
}

func (s *EvictTestSuite) SetupTest() {
s.mockCtrl = gomock.NewController(s.T())
s.mockClient = mock_registration.NewMockRegistrationClient(s.mockCtrl)
s.cli = &EvictCLI{
registrationClient: s.mockClient,
}
}

func (s *EvictTestSuite) TearDownTest() {
s.mockCtrl.Finish()
}

func TestEvictTestSuite(t *testing.T) {
suite.Run(t, new(EvictTestSuite))
}

func (s *EvictTestSuite) TestRun() {
spiffeIDToRemove := "spiffe://example.org/spire/agent/join_token/token_a"
args := []string{"-spiffeID", spiffeIDToRemove}

req := &registration.EvictAgentRequest{
SpiffeID: spiffeIDToRemove,
}

resp := &registration.EvictAgentResponse{
Node: &common.AttestedNode{SpiffeId: spiffeIDToRemove},
}

s.mockClient.EXPECT().EvictAgent(gomock.Any(), req).Return(resp, nil)
s.Require().Equal(0, s.cli.Run(args))
}

func (s *EvictTestSuite) TestRunExitsWithNonZeroCodeOnError() {
spiffeIDToRemove := "spiffe://example.org/spire/agent/join_token/token_a"
args := []string{"-spiffeID", spiffeIDToRemove}

req := &registration.EvictAgentRequest{
SpiffeID: spiffeIDToRemove,
}

s.mockClient.EXPECT().EvictAgent(gomock.Any(), req).Return(nil, errors.New("Some error"))
s.Require().Equal(1, s.cli.Run(args))
}

func (s *EvictTestSuite) TestRunExitsWithNonZeroCodeOnDeleteFailed() {
spiffeIDToRemove := "spiffe://example.org/spire/agent/join_token/token_a"
args := []string{"-spiffeID", spiffeIDToRemove}

req := &registration.EvictAgentRequest{
SpiffeID: spiffeIDToRemove,
}
resp := &registration.EvictAgentResponse{}

s.mockClient.EXPECT().EvictAgent(gomock.Any(), req).Return(resp, nil)
s.Require().Equal(1, s.cli.Run(args))
}

func (s *EvictTestSuite) TestRunValidatesSpiffeID() {
spiffeIDToRemove := "not//an//spiffe/id"
args := []string{"-spiffeID", spiffeIDToRemove}
s.Require().Equal(1, s.cli.Run(args))
}
104 changes: 104 additions & 0 deletions cmd/spire-server/cli/agent/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package agent

import (
"errors"
"flag"
"fmt"
"os"
"time"

"github.com/spiffe/spire/cmd/spire-server/util"
"github.com/spiffe/spire/proto/api/registration"
"github.com/spiffe/spire/proto/common"

"golang.org/x/net/context"
)

//ListConfig holds configuration for ListCLI
type ListConfig struct {
// Socket path of registration API
RegistrationUDSPath string
}

// Validate will perform a basic validation on config fields
func (c *ListConfig) Validate() (err error) {
if c.RegistrationUDSPath == "" {
return errors.New("a socket path for registration api is required")
}
return nil
}

//ListCLI command for listing attested nodes
type ListCLI struct {
registrationClient registration.RegistrationClient
nodeList []*common.AttestedNode
}

func (ListCLI) Synopsis() string {
return "Lists attested agents and their SPIFFE IDs"
}

func (c ListCLI) Help() string {
_, err := c.parseConfig([]string{"-h"})
return err.Error()
}

//Run will lists attested agents
func (c *ListCLI) Run(args []string) int {
ctx := context.Background()

config, err := c.parseConfig(args)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}

if err = config.Validate(); err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
}

if c.registrationClient == nil {
c.registrationClient, err = util.NewRegistrationClient(config.RegistrationUDSPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error establishing connection to the Registration API: %v \n", err)
return 1
}
}

listResponse, err := c.registrationClient.ListAgents(ctx, &registration.ListAgentsRequest{})
if err != nil {
fmt.Fprintf(os.Stderr, "Error listing attested agents: %v \n", err)
return 1
}
c.nodeList = listResponse.Nodes
c.printAttestedNodes()
return 0
}

func (ListCLI) parseConfig(args []string) (*ListConfig, error) {
f := flag.NewFlagSet("agent list", flag.ContinueOnError)
c := &ListConfig{}

f.StringVar(&c.RegistrationUDSPath, "registrationUDSPath", util.DefaultSocketPath, "Registration API UDS path")

return c, f.Parse(args)
}

func (c ListCLI) printAttestedNodes() {
msg := fmt.Sprintf("Found %d attested ", len(c.nodeList))
msg = util.Pluralizer(msg, "agent", "agents", len(c.nodeList))
fmt.Printf(msg + ":\n\n")

if len(c.nodeList) == 0 {
return
}

for _, node := range c.nodeList {
fmt.Printf("Spiffe ID : %s\n", node.SpiffeId)
fmt.Printf("Attestation type : %s\n", node.AttestationDataType)
fmt.Printf("Expiration time : %s\n", time.Unix(node.CertNotAfter, 0))
fmt.Printf("Serial number : %s\n", node.CertSerialNumber)
fmt.Println()
}
}
62 changes: 62 additions & 0 deletions cmd/spire-server/cli/agent/list_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package agent

import (
"errors"
"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/stretchr/testify/suite"
)

type ListTestSuite struct {
suite.Suite
cli *ListCLI
mockClient *mock_registration.MockRegistrationClient
mockCtrl *gomock.Controller
}

func (s *ListTestSuite) SetupTest() {
s.mockCtrl = gomock.NewController(s.T())
s.mockClient = mock_registration.NewMockRegistrationClient(s.mockCtrl)
s.cli = &ListCLI{
registrationClient: s.mockClient,
}
}

func (s *ListTestSuite) TearDownTest() {
s.mockCtrl.Finish()
}

func TestListTestSuite(t *testing.T) {
suite.Run(t, new(ListTestSuite))
}

func (s *ListTestSuite) TestRun() {
req := &registration.ListAgentsRequest{}
resp := &registration.ListAgentsResponse{
Nodes: []*common.AttestedNode{
&common.AttestedNode{SpiffeId: "spiffe://example.org/spire/agent/join_token/token_a"},
},
}
s.mockClient.EXPECT().ListAgents(gomock.Any(), req).Return(resp, nil)
s.Require().Equal(0, s.cli.Run([]string{}))
s.Assert().Equal(resp.Nodes, s.cli.nodeList)
}

func (s *ListTestSuite) TestRunWithNoAgentsInDatastore() {
req := &registration.ListAgentsRequest{}
resp := &registration.ListAgentsResponse{}
s.mockClient.EXPECT().ListAgents(gomock.Any(), req).Return(resp, nil)
s.Require().Equal(0, s.cli.Run([]string{}))
s.Assert().Equal(resp.Nodes, s.cli.nodeList)
}

func (s *ListTestSuite) TestRunExitsWithNonZeroCodeOnFailure() {
req := &registration.ListAgentsRequest{}
s.mockClient.EXPECT().ListAgents(gomock.Any(), req).Return(nil, errors.New("Some error"))
s.Require().Equal(1, s.cli.Run([]string{}))
s.Assert().Nil(s.cli.nodeList)
}
7 changes: 7 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/agent"
"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"
Expand All @@ -15,6 +16,12 @@ func Run(args []string) int {
c := cli.NewCLI("spire-server", version.Version())
c.Args = args
c.Commands = map[string]cli.CommandFactory{
"agent evict": func() (cli.Command, error) {
return &agent.EvictCLI{}, nil
},
"agent list": func() (cli.Command, error) {
return &agent.ListCLI{}, nil
},
"bundle show": func() (cli.Command, error) {
return bundle.NewShowCommand(), nil
},
Expand Down
Loading