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  }