From e0012bba1e47323628cba4e5f47aa632ca80c9ad Mon Sep 17 00:00:00 2001 From: Andrew Harding Date: Mon, 20 Aug 2018 13:24:17 -0600 Subject: [PATCH 1/2] cli support for registration entry "federates with" list Signed-off-by: Andrew Harding --- cmd/spire-server/cli/entry/create.go | 25 +++++++++++++++++-- cmd/spire-server/cli/entry/create_test.go | 15 ++++++++---- cmd/spire-server/cli/entry/show.go | 29 ++++++++++++++++++++++- cmd/spire-server/cli/entry/show_test.go | 24 +++++++++++++++---- cmd/spire-server/cli/entry/util.go | 15 +++++++----- cmd/spire-server/cli/entry/util_test.go | 4 ++-- 6 files changed, 92 insertions(+), 20 deletions(-) diff --git a/cmd/spire-server/cli/entry/create.go b/cmd/spire-server/cli/entry/create.go index 50b9885389..0425cf68a5 100644 --- a/cmd/spire-server/cli/entry/create.go +++ b/cmd/spire-server/cli/entry/create.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "github.com/spiffe/spire/cmd/spire-server/util" + "github.com/spiffe/spire/pkg/common/idutil" "github.com/spiffe/spire/proto/api/registration" "github.com/spiffe/spire/proto/common" @@ -24,11 +25,14 @@ type CreateConfig struct { // Type and value are delimited by a colon (:) // ex. "unix:uid:1000" or "spiffe_id:spiffe://example.org/foo" - Selectors SelectorFlag + Selectors StringsFlag ParentID string SpiffeID string Ttl int + + // List of SPIFFE IDs of trust domains the registration entry is federated with + FederatesWith StringsFlag } // Perform basic validation, even on fields that we @@ -59,6 +63,19 @@ func (rc *CreateConfig) Validate() error { return errors.New("a TTL is required") } + // make sure all SPIFFE ID's are well formed + if err := idutil.ValidateSpiffeID(rc.SpiffeID, idutil.AllowAny()); err != nil { + return err + } + if err := idutil.ValidateSpiffeID(rc.ParentID, idutil.AllowAny()); err != nil { + return err + } + for _, federatesWith := range rc.FederatesWith { + if err := idutil.ValidateSpiffeID(federatesWith, idutil.AllowAny()); err != nil { + return err + } + } + return nil } @@ -132,6 +149,7 @@ func (c CreateCLI) parseConfig(config *CreateConfig) ([]*common.RegistrationEntr } e.Selectors = selectors + e.FederatesWith = config.FederatesWith return []*common.RegistrationEntry{e}, nil } @@ -143,7 +161,9 @@ func (CreateCLI) parseFile(path string) ([]*common.RegistrationEntry, error) { return nil, err } - json.Unmarshal(dat, &entries) + if err := json.Unmarshal(dat, &entries); err != nil { + return nil, err + } return entries.Entries, nil } @@ -175,6 +195,7 @@ func (CreateCLI) newConfig(args []string) (*CreateConfig, error) { f.StringVar(&c.Path, "data", "", "Path to a file containing registration JSON (optional)") f.Var(&c.Selectors, "selector", "A colon-delimeted type:value selector. Can be used more than once") + f.Var(&c.FederatesWith, "federatesWith", "SPIFFE ID of a trust domain to federate with. Can be used more than once") return c, f.Parse(args) } diff --git a/cmd/spire-server/cli/entry/create_test.go b/cmd/spire-server/cli/entry/create_test.go index 952a6d8058..0f08808d79 100644 --- a/cmd/spire-server/cli/entry/create_test.go +++ b/cmd/spire-server/cli/entry/create_test.go @@ -15,11 +15,12 @@ import ( // TODO: Test additional scenarios func TestCreateParseConfig(t *testing.T) { c := &CreateConfig{ - Addr: cmdutil.DefaultServerAddr, - ParentID: "spiffe://example.org/foo", - SpiffeID: "spiffe://example.org/bar", - Ttl: 60, - Selectors: SelectorFlag{"unix:uid:1000", "unix:gid:1000"}, + Addr: cmdutil.DefaultServerAddr, + ParentID: "spiffe://example.org/foo", + SpiffeID: "spiffe://example.org/bar", + Ttl: 60, + Selectors: StringsFlag{"unix:uid:1000", "unix:gid:1000"}, + FederatesWith: StringsFlag{"spiffe://domain1.test", "spiffe://domain2.test"}, } entries, err := CreateCLI{}.parseConfig(c) @@ -33,6 +34,10 @@ func TestCreateParseConfig(t *testing.T) { {Type: "unix", Value: "uid:1000"}, {Type: "unix", Value: "gid:1000"}, }, + FederatesWith: []string{ + "spiffe://domain1.test", + "spiffe://domain2.test", + }, } expectedEntries := []*common.RegistrationEntry{expectedEntry} diff --git a/cmd/spire-server/cli/entry/show.go b/cmd/spire-server/cli/entry/show.go index 149715534c..a6701cda7f 100644 --- a/cmd/spire-server/cli/entry/show.go +++ b/cmd/spire-server/cli/entry/show.go @@ -20,11 +20,13 @@ type ShowConfig struct { // Type and value are delimited by a colon (:) // ex. "unix:uid:1000" or "spiffe_id:spiffe://example.org/foo" - Selectors SelectorFlag + Selectors StringsFlag EntryID string ParentID string SpiffeID string + + FederatesWith StringsFlag } // Validate ensures that the values in ShowConfig are valid @@ -98,6 +100,7 @@ func (s *ShowCLI) Run(args []string) int { return 1 } + s.filterEntries() s.printEntries() return 0 } @@ -208,6 +211,15 @@ func (s *ShowCLI) filterEntries() { newSlice := []*common.RegistrationEntry{} // Map used to skip duplicated entries. matchingEntries := map[string]*common.RegistrationEntry{} + + var federatedIDs map[string]bool + if len(s.Config.FederatesWith) > 0 { + federatedIDs = make(map[string]bool) + for _, federatesWith := range s.Config.FederatesWith { + federatedIDs[federatesWith] = true + } + } + for _, e := range s.Entries { match, _ := hasSelectors(e, s.Config.Selectors) if !match { @@ -224,6 +236,20 @@ func (s *ShowCLI) filterEntries() { continue } + // If FederatesWith was specified, discard entries that don't match + if federatedIDs != nil { + found := false + for _, federatesWith := range e.FederatesWith { + if federatedIDs[federatesWith] { + found = true + break + } + } + if !found { + continue + } + } + // If this entry wasn't matched before, save it. if _, ok := matchingEntries[e.EntryId]; !ok { matchingEntries[e.EntryId] = e @@ -254,6 +280,7 @@ func (s *ShowCLI) loadConfig(args []string) error { f.StringVar(&c.SpiffeID, "spiffeID", "", "The SPIFFE ID of the records to show") f.Var(&c.Selectors, "selector", "A colon-delimeted type:value selector. Can be used more than once") + f.Var(&c.FederatesWith, "federatesWith", "SPIFFE ID of a trust domain an entry is federate with. Can be used more than once") err := f.Parse(args) if err != nil { diff --git a/cmd/spire-server/cli/entry/show_test.go b/cmd/spire-server/cli/entry/show_test.go index 9843b58bc9..14ae14ad39 100644 --- a/cmd/spire-server/cli/entry/show_test.go +++ b/cmd/spire-server/cli/entry/show_test.go @@ -144,6 +144,21 @@ func (s *ShowTestSuite) TestRunWithParentIDAndSelectors() { s.Assert().Equal(entries[0:1], s.cli.Entries) } +func (s *ShowTestSuite) TestRunWithFederatesWith() { + resp := &common.RegistrationEntries{ + Entries: s.registrationEntries(4), + } + s.mockClient.EXPECT().FetchEntries(gomock.Any(), &common.Empty{}).Return(resp, nil) + + args := []string{ + "-federatesWith", + "spiffe://domain.test", + } + + s.Require().Equal(0, s.cli.Run(args)) + s.Assert().Equal(s.registrationEntries(4)[2:3], s.cli.Entries) +} + // registrationEntries returns `count` registration entry records. At most 4. func (ShowTestSuite) registrationEntries(count int) []*common.RegistrationEntry { selectors := []*common.Selector{ @@ -165,10 +180,11 @@ func (ShowTestSuite) registrationEntries(count int) []*common.RegistrationEntry EntryId: "00000000-0000-0000-0000-000000000001", }, { - ParentId: "spiffe://example.org/mother", - SpiffeId: "spiffe://example.org/daughter", - Selectors: []*common.Selector{selectors[1], selectors[2]}, - EntryId: "00000000-0000-0000-0000-000000000002", + ParentId: "spiffe://example.org/mother", + SpiffeId: "spiffe://example.org/daughter", + Selectors: []*common.Selector{selectors[1], selectors[2]}, + EntryId: "00000000-0000-0000-0000-000000000002", + FederatesWith: []string{"spiffe://domain.test"}, }, { ParentId: "spiffe://example.org/mother", diff --git a/cmd/spire-server/cli/entry/util.go b/cmd/spire-server/cli/entry/util.go index 7454ce208d..66b01faad6 100644 --- a/cmd/spire-server/cli/entry/util.go +++ b/cmd/spire-server/cli/entry/util.go @@ -10,7 +10,7 @@ import ( // hasSelectors takes a registration entry and a selector flag set. It returns // true if the registration entry possesses all selectors in the set. An error // is returned if we run into trouble parsing the selector flags. -func hasSelectors(entry *common.RegistrationEntry, flags SelectorFlag) (bool, error) { +func hasSelectors(entry *common.RegistrationEntry, flags StringsFlag) (bool, error) { for _, f := range flags { selector, err := parseSelector(f) if err != nil { @@ -65,19 +65,22 @@ func printEntry(e *common.RegistrationEntry) { for _, s := range e.Selectors { fmt.Printf("Selector:\t%s:%s\n", s.Type, s.Value) } + for _, id := range e.FederatesWith { + fmt.Printf("FederatesWith:\t%s\n", id) + } fmt.Println() } -// Define a custom type for selectors. Doing -// this allows us to support repeatable flags -type SelectorFlag []string +// Define a custom type for string lists. Doing +// this allows us to support repeatable string flags. +type StringsFlag []string -func (s *SelectorFlag) String() string { +func (s *StringsFlag) String() string { return fmt.Sprint(*s) } -func (s *SelectorFlag) Set(val string) error { +func (s *StringsFlag) Set(val string) error { *s = append(*s, val) return nil } diff --git a/cmd/spire-server/cli/entry/util_test.go b/cmd/spire-server/cli/entry/util_test.go index f0a7f4decc..e5b467f685 100644 --- a/cmd/spire-server/cli/entry/util_test.go +++ b/cmd/spire-server/cli/entry/util_test.go @@ -35,8 +35,8 @@ func TestHasSelectors(t *testing.T) { a.False(hasSelectors(entry, selectorToFlag(selectors[2:4]))) } -func selectorToFlag(selectors []*common.Selector) SelectorFlag { - resp := SelectorFlag{} +func selectorToFlag(selectors []*common.Selector) StringsFlag { + resp := StringsFlag{} for _, s := range selectors { str := s.Type + ":" + s.Value resp.Set(str) From db236141957dc4a7c0dd98cb5c24a809cdc5ed4c Mon Sep 17 00:00:00 2001 From: Andrew Harding Date: Thu, 6 Sep 2018 09:03:41 -0600 Subject: [PATCH 2/2] update server CLI documentation Signed-off-by: Andrew Harding --- doc/spire_server.md | 50 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/doc/spire_server.md b/doc/spire_server.md index bb9d1d3cf4..3be8afa956 100644 --- a/doc/spire_server.md +++ b/doc/spire_server.md @@ -76,14 +76,15 @@ human-readable registration entry name in addition to the token-based entry. Creates registration entries. -| Command | Action | Default | -|:--------------|:-----------------------------------------------------------------------|:---------------| -| `-data` | Path to a file containing registration data in JSON format (optional). | | -| `-parentID` | The SPIFFE ID of this record's parent. | | -| `-selector` | A colon-delimeted type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | -| `-serverAddr` | Address of the SPIRE server. | localhost:8081 | -| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | -| `-ttl` | A TTL, in seconds, for any SVID issued as a result of this record. | 3600 | +| Command | Action | Default | +|:-----------------|:-----------------------------------------------------------------------|:---------------| +| `-data` | Path to a file containing registration data in JSON format (optional). | | +| `-parentID` | The SPIFFE ID of this record's parent. | | +| `-selector` | A colon-delimeted type:value selector used for attestation. This parameter can be used more than once, to specify multiple selectors that must be satisfied. | | +| `-serverAddr` | Address of the SPIRE server. | localhost:8081 | +| `-spiffeID` | The SPIFFE ID that this record represents and will be set to the SVID issued. | | +| `-ttl` | A TTL, in seconds, for any SVID issued as a result of this record. | 3600 | +| `-federatesWith` | A list of trust domain SPIFFE IDs representing the trust domains this registration entry federates with. A bundle for that trust domain must already exist | | ### `spire-server entry delete` @@ -107,6 +108,39 @@ Displays configured registration entries. | `-spiffeID` | The SPIFFE ID of the records to show. | | | `-selector` | A TTL, in seconds, for any SVID issued as a result of this record. | 3600 | +### `spire-server bundle show` + +Displays the bundle for the trust domain of the server. + +| Command | +|:--------------|:-------------------------------------------------------------------|:---------------| +| `-serverAddr` | Address of the SPIRE server. | localhost:8081 | + +### `spire-server bundle list` + +Displays bundles from other trust domains. + +| Command | Action | Default | +|:--------------|:-------------------------------------------------------------------|:---------------| +| `-id` | The trust domain SPIFFE ID of the bundle to show. If unset, all trust bundles are shown | | + +### `spire-server bundle set` + +Creates or updates bundle data for a trust domain. This command cannot be used to alter the server trust domain bundle, only bundles for other trust domains. + +| Command | Action | Default | +|:--------------|:-------------------------------------------------------------------|:---------------| +| `-id` | The trust domain SPIFFE ID of the bundle to set. | | +| `-path` | Path on disk to the file containing the bundle data. If unset, data is read from stdin. | | + +### `spire-server bundle delete` + +Deletes bundle data for a trust domain. This command cannot be used to delete the server trust domain bundle, only bundles for other trust domains. + +| Command | Action | Default | +|:--------------|:-------------------------------------------------------------------|:---------------| +| `-id` | The trust domain SPIFFE ID of the bundle to delete. | | + ## Architecture The server consists of a master process (spire-server) and five plugins - the CA, the Upstream CA,