github.com/thanos-io/thanos@v0.32.5/pkg/tenancy/tenancy.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package tenancy
     5  
     6  import (
     7  	"context"
     8  	"net/http"
     9  	"path"
    10  
    11  	"google.golang.org/grpc/metadata"
    12  
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  type contextKey int
    17  
    18  const (
    19  	// DefaultTenantHeader is the default header used to designate the tenant making a request.
    20  	DefaultTenantHeader = "THANOS-TENANT"
    21  	// DefaultTenant is the default value used for when no tenant is passed via the tenant header.
    22  	DefaultTenant = "default-tenant"
    23  	// DefaultTenantLabel is the default label-name with which the tenant is announced in stored metrics.
    24  	DefaultTenantLabel = "tenant_id"
    25  	// This key is used to pass tenant information using Context.
    26  	TenantKey contextKey = 0
    27  )
    28  
    29  // Allowed fields in client certificates.
    30  const (
    31  	CertificateFieldOrganization       = "organization"
    32  	CertificateFieldOrganizationalUnit = "organizationalUnit"
    33  	CertificateFieldCommonName         = "commonName"
    34  )
    35  
    36  func IsTenantValid(tenant string) error {
    37  	if tenant != path.Base(tenant) {
    38  		return errors.New("Tenant name not valid")
    39  	}
    40  	return nil
    41  }
    42  
    43  // GetTenantFromHTTP extracts the tenant from a http.Request object.
    44  func GetTenantFromHTTP(r *http.Request, tenantHeader string, defaultTenantID string, certTenantField string) (string, error) {
    45  	var err error
    46  	tenant := r.Header.Get(tenantHeader)
    47  	if tenant == "" {
    48  		tenant = defaultTenantID
    49  	}
    50  
    51  	if certTenantField != "" {
    52  		tenant, err = getTenantFromCertificate(r, certTenantField)
    53  		if err != nil {
    54  			// This must hard fail to ensure hard tenancy when feature is enabled.
    55  			return "", err
    56  		}
    57  	}
    58  
    59  	err = IsTenantValid(tenant)
    60  	if err != nil {
    61  		return "", err
    62  	}
    63  	return tenant, nil
    64  }
    65  
    66  // getTenantFromCertificate extracts the tenant value from a client's presented certificate. The x509 field to use as
    67  // value can be configured with Options.TenantField. An error is returned when the extraction has not succeeded.
    68  func getTenantFromCertificate(r *http.Request, certTenantField string) (string, error) {
    69  	var tenant string
    70  
    71  	if len(r.TLS.PeerCertificates) == 0 {
    72  		return "", errors.New("could not get required certificate field from client cert")
    73  	}
    74  
    75  	// First cert is the leaf authenticated against.
    76  	cert := r.TLS.PeerCertificates[0]
    77  
    78  	switch certTenantField {
    79  
    80  	case CertificateFieldOrganization:
    81  		if len(cert.Subject.Organization) == 0 {
    82  			return "", errors.New("could not get organization field from client cert")
    83  		}
    84  		tenant = cert.Subject.Organization[0]
    85  
    86  	case CertificateFieldOrganizationalUnit:
    87  		if len(cert.Subject.OrganizationalUnit) == 0 {
    88  			return "", errors.New("could not get organizationalUnit field from client cert")
    89  		}
    90  		tenant = cert.Subject.OrganizationalUnit[0]
    91  
    92  	case CertificateFieldCommonName:
    93  		if cert.Subject.CommonName == "" {
    94  			return "", errors.New("could not get commonName field from client cert")
    95  		}
    96  		tenant = cert.Subject.CommonName
    97  
    98  	default:
    99  		return "", errors.New("tls client cert field requested is not supported")
   100  	}
   101  
   102  	return tenant, nil
   103  }
   104  
   105  func GetTenantFromGRPCMetadata(ctx context.Context) (string, bool) {
   106  	md, ok := metadata.FromIncomingContext(ctx)
   107  	if !ok || len(md.Get(DefaultTenantHeader)) == 0 {
   108  		return DefaultTenant, false
   109  	}
   110  	return md.Get(DefaultTenantHeader)[0], true
   111  }