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

cli support for registration entry "federates with" list #582

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions cmd/spire-server/cli/entry/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -132,6 +149,7 @@ func (c CreateCLI) parseConfig(config *CreateConfig) ([]*common.RegistrationEntr
}

e.Selectors = selectors
e.FederatesWith = config.FederatesWith
return []*common.RegistrationEntry{e}, nil
}

Expand All @@ -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
}

Expand Down Expand Up @@ -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)
}
15 changes: 10 additions & 5 deletions cmd/spire-server/cli/entry/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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}
Expand Down
29 changes: 28 additions & 1 deletion cmd/spire-server/cli/entry/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -98,6 +100,7 @@ func (s *ShowCLI) Run(args []string) int {
return 1
}

s.filterEntries()
s.printEntries()
return 0
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 20 additions & 4 deletions cmd/spire-server/cli/entry/show_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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",
Expand Down
15 changes: 9 additions & 6 deletions cmd/spire-server/cli/entry/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions cmd/spire-server/cli/entry/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
50 changes: 42 additions & 8 deletions doc/spire_server.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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,
Expand Down