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  }