github.com/outbrain/consul@v1.4.5/agent/connect/uri.go (about) 1 package connect 2 3 import ( 4 "fmt" 5 "net/url" 6 "regexp" 7 "strings" 8 9 "github.com/hashicorp/consul/agent/structs" 10 ) 11 12 // CertURI represents a Connect-valid URI value for a TLS certificate. 13 // The user should type switch on the various implementations in this 14 // package to determine the type of URI and the data encoded within it. 15 // 16 // Note that the current implementations of this are all also SPIFFE IDs. 17 // However, we anticipate that we may accept URIs that are also not SPIFFE 18 // compliant and therefore the interface is named as such. 19 type CertURI interface { 20 // Authorize tests the authorization for this URI as a client 21 // for the given intention. The return value `auth` is only valid if 22 // the second value `match` is true. If the second value `match` is 23 // false, then the intention doesn't match this client and any 24 // result should be ignored. 25 Authorize(*structs.Intention) (auth bool, match bool) 26 27 // URI is the valid URI value used in the cert. 28 URI() *url.URL 29 } 30 31 var ( 32 spiffeIDServiceRegexp = regexp.MustCompile( 33 `^/ns/([^/]+)/dc/([^/]+)/svc/([^/]+)$`) 34 ) 35 36 // ParseCertURIFromString attempts to parse a string representation of a 37 // certificate URI as a convenience helper around ParseCertURI. 38 func ParseCertURIFromString(input string) (CertURI, error) { 39 // Parse the certificate URI from the string 40 uriRaw, err := url.Parse(input) 41 if err != nil { 42 return nil, err 43 } 44 return ParseCertURI(uriRaw) 45 } 46 47 // ParseCertURI parses a the URI value from a TLS certificate. 48 func ParseCertURI(input *url.URL) (CertURI, error) { 49 if input.Scheme != "spiffe" { 50 return nil, fmt.Errorf("SPIFFE ID must have 'spiffe' scheme") 51 } 52 53 // Path is the raw value of the path without url decoding values. 54 // RawPath is empty if there were no encoded values so we must 55 // check both. 56 path := input.Path 57 if input.RawPath != "" { 58 path = input.RawPath 59 } 60 61 // Test for service IDs 62 if v := spiffeIDServiceRegexp.FindStringSubmatch(path); v != nil { 63 // Determine the values. We assume they're sane to save cycles, 64 // but if the raw path is not empty that means that something is 65 // URL encoded so we go to the slow path. 66 ns := v[1] 67 dc := v[2] 68 service := v[3] 69 if input.RawPath != "" { 70 var err error 71 if ns, err = url.PathUnescape(v[1]); err != nil { 72 return nil, fmt.Errorf("Invalid namespace: %s", err) 73 } 74 if dc, err = url.PathUnescape(v[2]); err != nil { 75 return nil, fmt.Errorf("Invalid datacenter: %s", err) 76 } 77 if service, err = url.PathUnescape(v[3]); err != nil { 78 return nil, fmt.Errorf("Invalid service: %s", err) 79 } 80 } 81 82 return &SpiffeIDService{ 83 Host: input.Host, 84 Namespace: ns, 85 Datacenter: dc, 86 Service: service, 87 }, nil 88 } 89 90 // Test for signing ID 91 if input.Path == "" { 92 idx := strings.Index(input.Host, ".") 93 if idx > 0 { 94 return &SpiffeIDSigning{ 95 ClusterID: input.Host[:idx], 96 Domain: input.Host[idx+1:], 97 }, nil 98 } 99 } 100 101 return nil, fmt.Errorf("SPIFFE ID is not in the expected format") 102 }