mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
207 lines
5.9 KiB
207 lines
5.9 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package connect |
|
|
|
import ( |
|
"fmt" |
|
"net/url" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
// CertURI represents a Connect-valid URI value for a TLS certificate. |
|
// The user should type switch on the various implementations in this |
|
// package to determine the type of URI and the data encoded within it. |
|
// |
|
// Note that the current implementations of this are all also SPIFFE IDs. |
|
// 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 { |
|
// URI is the valid URI value used in the cert. |
|
URI() *url.URL |
|
} |
|
|
|
var ( |
|
spiffeIDWorkloadIdentityRegexp = regexp.MustCompile( |
|
`^(?:/ap/([^/]+))/ns/([^/]+)/identity/([^/]+)$`) |
|
spiffeIDServiceRegexp = regexp.MustCompile( |
|
`^(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/]+)$`) |
|
spiffeIDAgentRegexp = regexp.MustCompile( |
|
`^(?:/ap/([^/]+))?/agent/client/dc/([^/]+)/id/([^/]+)$`) |
|
spiffeIDServerRegexp = regexp.MustCompile( |
|
`^/agent/server/dc/([^/]+)$`) |
|
spiffeIDMeshGatewayRegexp = regexp.MustCompile( |
|
`^(?:/ap/([^/]+))?/gateway/mesh/dc/([^/]+)$`) |
|
) |
|
|
|
// ParseCertURIFromString attempts to parse a string representation of a |
|
// certificate URI as a convenience helper around ParseCertURI. |
|
func ParseCertURIFromString(input string) (CertURI, error) { |
|
// Parse the certificate URI from the string |
|
uriRaw, err := url.Parse(input) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return ParseCertURI(uriRaw) |
|
} |
|
|
|
// ParseCertURI parses a the URI value from a TLS certificate. |
|
func ParseCertURI(input *url.URL) (CertURI, error) { |
|
if input.Scheme != "spiffe" { |
|
return nil, fmt.Errorf("SPIFFE ID must have 'spiffe' scheme") |
|
} |
|
|
|
// Path is the raw value of the path without url decoding values. |
|
// RawPath is empty if there were no encoded values so we must |
|
// check both. |
|
path := input.Path |
|
if input.RawPath != "" { |
|
path = input.RawPath |
|
} |
|
|
|
// Test for service IDs |
|
if v := spiffeIDServiceRegexp.FindStringSubmatch(path); v != nil { |
|
// Determine the values. We assume they're reasonable to save cycles, |
|
// but if the raw path is not empty that means that something is |
|
// URL encoded so we go to the slow path. |
|
ap := v[1] |
|
ns := v[2] |
|
dc := v[3] |
|
service := v[4] |
|
if input.RawPath != "" { |
|
var err error |
|
if ap, err = url.PathUnescape(v[1]); err != nil { |
|
return nil, fmt.Errorf("Invalid admin partition: %s", err) |
|
} |
|
if ns, err = url.PathUnescape(v[2]); err != nil { |
|
return nil, fmt.Errorf("Invalid namespace: %s", err) |
|
} |
|
if dc, err = url.PathUnescape(v[3]); err != nil { |
|
return nil, fmt.Errorf("Invalid datacenter: %s", err) |
|
} |
|
if service, err = url.PathUnescape(v[4]); err != nil { |
|
return nil, fmt.Errorf("Invalid service: %s", err) |
|
} |
|
} |
|
|
|
if ap == "" { |
|
ap = "default" |
|
} |
|
|
|
return &SpiffeIDService{ |
|
Host: input.Host, |
|
Partition: ap, |
|
Namespace: ns, |
|
Datacenter: dc, |
|
Service: service, |
|
}, nil |
|
} else if v := spiffeIDWorkloadIdentityRegexp.FindStringSubmatch(path); v != nil { |
|
// Determine the values. We assume they're reasonable to save cycles, |
|
// but if the raw path is not empty that means that something is |
|
// URL encoded so we go to the slow path. |
|
ap := v[1] |
|
ns := v[2] |
|
workloadIdentity := v[3] |
|
if input.RawPath != "" { |
|
var err error |
|
if ap, err = url.PathUnescape(v[1]); err != nil { |
|
return nil, fmt.Errorf("Invalid admin partition: %s", err) |
|
} |
|
if ns, err = url.PathUnescape(v[2]); err != nil { |
|
return nil, fmt.Errorf("Invalid namespace: %s", err) |
|
} |
|
if workloadIdentity, err = url.PathUnescape(v[3]); err != nil { |
|
return nil, fmt.Errorf("Invalid workload identity: %s", err) |
|
} |
|
} |
|
|
|
return &SpiffeIDWorkloadIdentity{ |
|
TrustDomain: input.Host, |
|
Partition: ap, |
|
Namespace: ns, |
|
WorkloadIdentity: workloadIdentity, |
|
}, nil |
|
} else if v := spiffeIDAgentRegexp.FindStringSubmatch(path); v != nil { |
|
// Determine the values. We assume they're reasonable to save cycles, |
|
// but if the raw path is not empty that means that something is |
|
// URL encoded so we go to the slow path. |
|
ap := v[1] |
|
dc := v[2] |
|
agent := v[3] |
|
if input.RawPath != "" { |
|
var err error |
|
if ap, err = url.PathUnescape(v[1]); err != nil { |
|
return nil, fmt.Errorf("Invalid admin partition: %s", err) |
|
} |
|
if dc, err = url.PathUnescape(v[2]); err != nil { |
|
return nil, fmt.Errorf("Invalid datacenter: %s", err) |
|
} |
|
if agent, err = url.PathUnescape(v[3]); err != nil { |
|
return nil, fmt.Errorf("Invalid node: %s", err) |
|
} |
|
} |
|
|
|
if ap == "" { |
|
ap = "default" |
|
} |
|
|
|
return &SpiffeIDAgent{ |
|
Host: input.Host, |
|
Partition: ap, |
|
Datacenter: dc, |
|
Agent: agent, |
|
}, nil |
|
} else if v := spiffeIDMeshGatewayRegexp.FindStringSubmatch(path); v != nil { |
|
// Determine the values. We assume they're reasonable to save cycles, |
|
// but if the raw path is not empty that means that something is |
|
// URL encoded so we go to the slow path. |
|
ap := v[1] |
|
dc := v[2] |
|
if input.RawPath != "" { |
|
var err error |
|
if ap, err = url.PathUnescape(v[1]); err != nil { |
|
return nil, fmt.Errorf("Invalid admin partition: %s", err) |
|
} |
|
if dc, err = url.PathUnescape(v[2]); err != nil { |
|
return nil, fmt.Errorf("Invalid datacenter: %s", err) |
|
} |
|
} |
|
|
|
if ap == "" { |
|
ap = "default" |
|
} |
|
|
|
return &SpiffeIDMeshGateway{ |
|
Host: input.Host, |
|
Partition: ap, |
|
Datacenter: dc, |
|
}, nil |
|
} else if v := spiffeIDServerRegexp.FindStringSubmatch(path); v != nil { |
|
dc := v[1] |
|
if input.RawPath != "" { |
|
var err error |
|
if dc, err = url.PathUnescape(v[1]); err != nil { |
|
return nil, fmt.Errorf("Invalid datacenter: %s", err) |
|
} |
|
} |
|
|
|
return &SpiffeIDServer{ |
|
Host: input.Host, |
|
Datacenter: dc, |
|
}, nil |
|
} |
|
|
|
// Test for signing ID |
|
if input.Path == "" { |
|
idx := strings.Index(input.Host, ".") |
|
if idx > 0 { |
|
return &SpiffeIDSigning{ |
|
ClusterID: input.Host[:idx], |
|
Domain: input.Host[idx+1:], |
|
}, nil |
|
} |
|
} |
|
|
|
return nil, fmt.Errorf("SPIFFE ID is not in the expected format: %s", input.String()) |
|
}
|
|
|