From 3ef0b9315938b643e8052836a067096f05fcc062 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 25 Mar 2018 14:39:18 -1000 Subject: [PATCH] agent/connect: Authorize for CertURI --- agent/connect/uri.go | 43 +++--------- agent/connect/uri_service.go | 42 ++++++++++++ agent/connect/uri_service_test.go | 104 ++++++++++++++++++++++++++++++ agent/connect/uri_signing.go | 29 +++++++++ agent/connect/uri_signing_test.go | 15 +++++ 5 files changed, 200 insertions(+), 33 deletions(-) create mode 100644 agent/connect/uri_service.go create mode 100644 agent/connect/uri_service_test.go create mode 100644 agent/connect/uri_signing.go create mode 100644 agent/connect/uri_signing_test.go diff --git a/agent/connect/uri.go b/agent/connect/uri.go index 3562f2d6ce..48bfd3686a 100644 --- a/agent/connect/uri.go +++ b/agent/connect/uri.go @@ -5,6 +5,8 @@ import ( "net/url" "regexp" "strings" + + "github.com/hashicorp/consul/agent/structs" ) // CertURI represents a Connect-valid URI value for a TLS certificate. @@ -15,6 +17,14 @@ import ( // However, we anticipate that we may accept URIs that are also not SPIFFE // compliant and therefore the interface is named as such. type CertURI interface { + // Authorize tests the authorization for this URI as a client + // for the given intention. The return value `auth` is only valid if + // the second value `match` is true. If the second value `match` is + // false, then the intention doesn't match this client and any + // result should be ignored. + Authorize(*structs.Intention) (auth bool, match bool) + + // URI is the valid URI value used in the cert. URI() *url.URL } @@ -79,36 +89,3 @@ func ParseCertURI(input *url.URL) (CertURI, error) { return nil, fmt.Errorf("SPIFFE ID is not in the expected format") } - -// SpiffeIDService is the structure to represent the SPIFFE ID for a service. -type SpiffeIDService struct { - Host string - Namespace string - Datacenter string - Service string -} - -// URI returns the *url.URL for this SPIFFE ID. -func (id *SpiffeIDService) URI() *url.URL { - var result url.URL - result.Scheme = "spiffe" - result.Host = id.Host - result.Path = fmt.Sprintf("/ns/%s/dc/%s/svc/%s", - id.Namespace, id.Datacenter, id.Service) - return &result -} - -// SpiffeIDSigning is the structure to represent the SPIFFE ID for a -// signing certificate (not a leaf service). -type SpiffeIDSigning struct { - ClusterID string // Unique cluster ID - Domain string // The domain, usually "consul" -} - -// URI returns the *url.URL for this SPIFFE ID. -func (id *SpiffeIDSigning) URI() *url.URL { - var result url.URL - result.Scheme = "spiffe" - result.Host = fmt.Sprintf("%s.%s", id.ClusterID, id.Domain) - return &result -} diff --git a/agent/connect/uri_service.go b/agent/connect/uri_service.go new file mode 100644 index 0000000000..3e53e8e367 --- /dev/null +++ b/agent/connect/uri_service.go @@ -0,0 +1,42 @@ +package connect + +import ( + "fmt" + "net/url" + + "github.com/hashicorp/consul/agent/structs" +) + +// SpiffeIDService is the structure to represent the SPIFFE ID for a service. +type SpiffeIDService struct { + Host string + Namespace string + Datacenter string + Service string +} + +// URI returns the *url.URL for this SPIFFE ID. +func (id *SpiffeIDService) URI() *url.URL { + var result url.URL + result.Scheme = "spiffe" + result.Host = id.Host + result.Path = fmt.Sprintf("/ns/%s/dc/%s/svc/%s", + id.Namespace, id.Datacenter, id.Service) + return &result +} + +// CertURI impl. +func (id *SpiffeIDService) Authorize(ixn *structs.Intention) (bool, bool) { + if ixn.SourceNS != structs.IntentionWildcard && ixn.SourceNS != id.Namespace { + // Non-matching namespace + return false, false + } + + if ixn.SourceName != structs.IntentionWildcard && ixn.SourceName != id.Service { + // Non-matching name + return false, false + } + + // Match, return allow value + return ixn.Action == structs.IntentionActionAllow, true +} diff --git a/agent/connect/uri_service_test.go b/agent/connect/uri_service_test.go new file mode 100644 index 0000000000..ac21bca28e --- /dev/null +++ b/agent/connect/uri_service_test.go @@ -0,0 +1,104 @@ +package connect + +import ( + "testing" + + "github.com/hashicorp/consul/agent/structs" + "github.com/stretchr/testify/assert" +) + +func TestSpiffeIDServiceAuthorize(t *testing.T) { + ns := structs.IntentionDefaultNamespace + serviceWeb := &SpiffeIDService{ + Host: "1234.consul", + Namespace: structs.IntentionDefaultNamespace, + Datacenter: "dc01", + Service: "web", + } + + cases := []struct { + Name string + URI *SpiffeIDService + Ixn *structs.Intention + Auth bool + Match bool + }{ + { + "exact source, not matching namespace", + serviceWeb, + &structs.Intention{ + SourceNS: "different", + SourceName: "db", + }, + false, + false, + }, + + { + "exact source, not matching name", + serviceWeb, + &structs.Intention{ + SourceNS: ns, + SourceName: "db", + }, + false, + false, + }, + + { + "exact source, allow", + serviceWeb, + &structs.Intention{ + SourceNS: serviceWeb.Namespace, + SourceName: serviceWeb.Service, + Action: structs.IntentionActionAllow, + }, + true, + true, + }, + + { + "exact source, deny", + serviceWeb, + &structs.Intention{ + SourceNS: serviceWeb.Namespace, + SourceName: serviceWeb.Service, + Action: structs.IntentionActionDeny, + }, + false, + true, + }, + + { + "exact namespace, wildcard service, deny", + serviceWeb, + &structs.Intention{ + SourceNS: serviceWeb.Namespace, + SourceName: structs.IntentionWildcard, + Action: structs.IntentionActionDeny, + }, + false, + true, + }, + + { + "exact namespace, wildcard service, allow", + serviceWeb, + &structs.Intention{ + SourceNS: serviceWeb.Namespace, + SourceName: structs.IntentionWildcard, + Action: structs.IntentionActionAllow, + }, + true, + true, + }, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + auth, match := tc.URI.Authorize(tc.Ixn) + assert.Equal(t, tc.Auth, auth) + assert.Equal(t, tc.Match, match) + }) + } +} diff --git a/agent/connect/uri_signing.go b/agent/connect/uri_signing.go new file mode 100644 index 0000000000..213f744d1f --- /dev/null +++ b/agent/connect/uri_signing.go @@ -0,0 +1,29 @@ +package connect + +import ( + "fmt" + "net/url" + + "github.com/hashicorp/consul/agent/structs" +) + +// SpiffeIDSigning is the structure to represent the SPIFFE ID for a +// signing certificate (not a leaf service). +type SpiffeIDSigning struct { + ClusterID string // Unique cluster ID + Domain string // The domain, usually "consul" +} + +// URI returns the *url.URL for this SPIFFE ID. +func (id *SpiffeIDSigning) URI() *url.URL { + var result url.URL + result.Scheme = "spiffe" + result.Host = fmt.Sprintf("%s.%s", id.ClusterID, id.Domain) + return &result +} + +// CertURI impl. +func (id *SpiffeIDSigning) Authorize(ixn *structs.Intention) (bool, bool) { + // Never authorize as a client. + return false, true +} diff --git a/agent/connect/uri_signing_test.go b/agent/connect/uri_signing_test.go new file mode 100644 index 0000000000..a9be3c5e2b --- /dev/null +++ b/agent/connect/uri_signing_test.go @@ -0,0 +1,15 @@ +package connect + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Signing ID should never authorize +func TestSpiffeIDSigningAuthorize(t *testing.T) { + var id SpiffeIDSigning + auth, ok := id.Authorize(nil) + assert.False(t, auth) + assert.True(t, ok) +}