github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/utilccl/licenseccl/license.go (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package licenseccl 10 11 import ( 12 "bytes" 13 "encoding/base64" 14 "strings" 15 "time" 16 17 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 19 "github.com/cockroachdb/cockroach/pkg/util/protoutil" 20 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 21 "github.com/cockroachdb/cockroach/pkg/util/uuid" 22 "github.com/cockroachdb/errors" 23 ) 24 25 // LicensePrefix is a prefix on license strings to make them easily recognized. 26 const LicensePrefix = "crl-0-" 27 28 // Encode serializes the License as a base64 string. 29 func (l License) Encode() (string, error) { 30 bytes, err := protoutil.Marshal(&l) 31 if err != nil { 32 return "", err 33 } 34 return LicensePrefix + base64.RawStdEncoding.EncodeToString(bytes), nil 35 } 36 37 // Decode attempts to read a base64 encoded License. 38 func Decode(s string) (*License, error) { 39 if s == "" { 40 return nil, nil 41 } 42 if !strings.HasPrefix(s, LicensePrefix) { 43 return nil, pgerror.Newf(pgcode.Syntax, "invalid license string") 44 } 45 s = strings.TrimPrefix(s, LicensePrefix) 46 data, err := base64.RawStdEncoding.DecodeString(s) 47 if err != nil { 48 return nil, pgerror.Wrapf(err, pgcode.Syntax, "invalid license string") 49 } 50 var lic License 51 if err := protoutil.Unmarshal(data, &lic); err != nil { 52 return nil, pgerror.Wrap(err, pgcode.Syntax, "invalid license string") 53 } 54 return &lic, nil 55 } 56 57 // Check returns an error if the license is empty or not currently valid. 58 func (l *License) Check(at time.Time, cluster uuid.UUID, org, feature string) error { 59 if l == nil { 60 // TODO(dt): link to some stable URL that then redirects to a helpful page 61 // that explains what to do here. 62 link := "https://cockroachlabs.com/pricing?cluster=" 63 return pgerror.Newf(pgcode.CCLValidLicenseRequired, 64 "use of %s requires an enterprise license. "+ 65 "see %s%s for details on how to enable enterprise features", 66 errors.Safe(feature), 67 link, 68 cluster.String(), 69 ) 70 } 71 72 // We extend some grace period to enterprise license holders rather than 73 // suddenly throwing errors at them. 74 if l.ValidUntilUnixSec > 0 && l.Type != License_Enterprise { 75 if expiration := timeutil.Unix(l.ValidUntilUnixSec, 0); at.After(expiration) { 76 licensePrefix := "" 77 switch l.Type { 78 case License_NonCommercial: 79 licensePrefix = "non-commercial " 80 case License_Evaluation: 81 licensePrefix = "evaluation " 82 } 83 return pgerror.Newf(pgcode.CCLValidLicenseRequired, 84 "Use of %s requires an enterprise license. Your %slicense expired on %s. If you're "+ 85 "interested in getting a new license, please contact subscriptions@cockroachlabs.com "+ 86 "and we can help you out.", 87 errors.Safe(feature), 88 licensePrefix, 89 expiration.Format("January 2, 2006"), 90 ) 91 } 92 } 93 94 if l.ClusterID == nil { 95 if strings.EqualFold(l.OrganizationName, org) { 96 return nil 97 } 98 return pgerror.Newf(pgcode.CCLValidLicenseRequired, 99 "license valid only for %q", l.OrganizationName) 100 } 101 102 for _, c := range l.ClusterID { 103 if cluster == c { 104 return nil 105 } 106 } 107 108 // no match, so compose an error message. 109 var matches bytes.Buffer 110 for i, c := range l.ClusterID { 111 if i > 0 { 112 matches.WriteString(", ") 113 } 114 matches.WriteString(c.String()) 115 } 116 return pgerror.Newf(pgcode.CCLValidLicenseRequired, 117 "license for cluster(s) %s is not valid for cluster %s", 118 matches.String(), cluster.String(), 119 ) 120 }