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 }