github.com/nats-io/jwt/v2@v2.5.6/operator_claims.go (about)

     1  /*
     2   * Copyright 2018 The NATS Authors
     3   * Licensed under the Apache License, Version 2.0 (the "License");
     4   * you may not use this file except in compliance with the License.
     5   * You may obtain a copy of the License at
     6   *
     7   * http://www.apache.org/licenses/LICENSE-2.0
     8   *
     9   * Unless required by applicable law or agreed to in writing, software
    10   * distributed under the License is distributed on an "AS IS" BASIS,
    11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12   * See the License for the specific language governing permissions and
    13   * limitations under the License.
    14   */
    15  
    16  package jwt
    17  
    18  import (
    19  	"errors"
    20  	"fmt"
    21  	"net/url"
    22  	"strconv"
    23  	"strings"
    24  
    25  	"github.com/nats-io/nkeys"
    26  )
    27  
    28  // Operator specific claims
    29  type Operator struct {
    30  	// Slice of other operator NKeys that can be used to sign on behalf of the main
    31  	// operator identity.
    32  	SigningKeys StringList `json:"signing_keys,omitempty"`
    33  	// AccountServerURL is a partial URL like "https://host.domain.org:<port>/jwt/v1"
    34  	// tools will use the prefix and build queries by appending /accounts/<account_id>
    35  	// or /operator to the path provided. Note this assumes that the account server
    36  	// can handle requests in a nats-account-server compatible way. See
    37  	// https://github.com/nats-io/nats-account-server.
    38  	AccountServerURL string `json:"account_server_url,omitempty"`
    39  	// A list of NATS urls (tls://host:port) where tools can connect to the server
    40  	// using proper credentials.
    41  	OperatorServiceURLs StringList `json:"operator_service_urls,omitempty"`
    42  	// Identity of the system account
    43  	SystemAccount string `json:"system_account,omitempty"`
    44  	// Min Server version
    45  	AssertServerVersion string `json:"assert_server_version,omitempty"`
    46  	// Signing of subordinate objects will require signing keys
    47  	StrictSigningKeyUsage bool `json:"strict_signing_key_usage,omitempty"`
    48  	GenericFields
    49  }
    50  
    51  func ParseServerVersion(version string) (int, int, int, error) {
    52  	if version == "" {
    53  		return 0, 0, 0, nil
    54  	}
    55  	split := strings.Split(version, ".")
    56  	if len(split) != 3 {
    57  		return 0, 0, 0, fmt.Errorf("asserted server version must be of the form <major>.<minor>.<update>")
    58  	} else if major, err := strconv.Atoi(split[0]); err != nil {
    59  		return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[0])
    60  	} else if minor, err := strconv.Atoi(split[1]); err != nil {
    61  		return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[1])
    62  	} else if update, err := strconv.Atoi(split[2]); err != nil {
    63  		return 0, 0, 0, fmt.Errorf("asserted server version cant parse %s to int", split[2])
    64  	} else if major < 0 || minor < 0 || update < 0 {
    65  		return 0, 0, 0, fmt.Errorf("asserted server version can'b contain negative values: %s", version)
    66  	} else {
    67  		return major, minor, update, nil
    68  	}
    69  }
    70  
    71  // Validate checks the validity of the operators contents
    72  func (o *Operator) Validate(vr *ValidationResults) {
    73  	if err := o.validateAccountServerURL(); err != nil {
    74  		vr.AddError(err.Error())
    75  	}
    76  
    77  	for _, v := range o.validateOperatorServiceURLs() {
    78  		if v != nil {
    79  			vr.AddError(v.Error())
    80  		}
    81  	}
    82  
    83  	for _, k := range o.SigningKeys {
    84  		if !nkeys.IsValidPublicOperatorKey(k) {
    85  			vr.AddError("%s is not an operator public key", k)
    86  		}
    87  	}
    88  	if o.SystemAccount != "" {
    89  		if !nkeys.IsValidPublicAccountKey(o.SystemAccount) {
    90  			vr.AddError("%s is not an account public key", o.SystemAccount)
    91  		}
    92  	}
    93  	if _, _, _, err := ParseServerVersion(o.AssertServerVersion); err != nil {
    94  		vr.AddError("assert server version error: %s", err)
    95  	}
    96  }
    97  
    98  func (o *Operator) validateAccountServerURL() error {
    99  	if o.AccountServerURL != "" {
   100  		// We don't care what kind of URL it is so long as it parses
   101  		// and has a protocol. The account server may impose additional
   102  		// constraints on the type of URLs that it is able to notify to
   103  		u, err := url.Parse(o.AccountServerURL)
   104  		if err != nil {
   105  			return fmt.Errorf("error parsing account server url: %v", err)
   106  		}
   107  		if u.Scheme == "" {
   108  			return fmt.Errorf("account server url %q requires a protocol", o.AccountServerURL)
   109  		}
   110  	}
   111  	return nil
   112  }
   113  
   114  // ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url.
   115  func ValidateOperatorServiceURL(v string) error {
   116  	// should be possible for the service url to not be expressed
   117  	if v == "" {
   118  		return nil
   119  	}
   120  	u, err := url.Parse(v)
   121  	if err != nil {
   122  		return fmt.Errorf("error parsing operator service url %q: %v", v, err)
   123  	}
   124  
   125  	if u.User != nil {
   126  		return fmt.Errorf("operator service url %q - credentials are not supported", v)
   127  	}
   128  
   129  	if u.Path != "" {
   130  		return fmt.Errorf("operator service url %q - paths are not supported", v)
   131  	}
   132  
   133  	lcs := strings.ToLower(u.Scheme)
   134  	switch lcs {
   135  	case "nats":
   136  		return nil
   137  	case "tls":
   138  		return nil
   139  	case "ws":
   140  		return nil
   141  	case "wss":
   142  		return nil
   143  	default:
   144  		return fmt.Errorf("operator service url %q - protocol not supported (only 'nats', 'tls', 'ws', 'wss' only)", v)
   145  	}
   146  }
   147  
   148  func (o *Operator) validateOperatorServiceURLs() []error {
   149  	var errs []error
   150  	for _, v := range o.OperatorServiceURLs {
   151  		if v != "" {
   152  			if err := ValidateOperatorServiceURL(v); err != nil {
   153  				errs = append(errs, err)
   154  			}
   155  		}
   156  	}
   157  	return errs
   158  }
   159  
   160  // OperatorClaims define the data for an operator JWT
   161  type OperatorClaims struct {
   162  	ClaimsData
   163  	Operator `json:"nats,omitempty"`
   164  }
   165  
   166  // NewOperatorClaims creates a new operator claim with the specified subject, which should be an operator public key
   167  func NewOperatorClaims(subject string) *OperatorClaims {
   168  	if subject == "" {
   169  		return nil
   170  	}
   171  	c := &OperatorClaims{}
   172  	c.Subject = subject
   173  	c.Issuer = subject
   174  	return c
   175  }
   176  
   177  // DidSign checks the claims against the operator's public key and its signing keys
   178  func (oc *OperatorClaims) DidSign(op Claims) bool {
   179  	if op == nil {
   180  		return false
   181  	}
   182  	issuer := op.Claims().Issuer
   183  	if issuer == oc.Subject {
   184  		if !oc.StrictSigningKeyUsage {
   185  			return true
   186  		}
   187  		return op.Claims().Subject == oc.Subject
   188  	}
   189  	return oc.SigningKeys.Contains(issuer)
   190  }
   191  
   192  // Encode the claims into a JWT string
   193  func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) {
   194  	if !nkeys.IsValidPublicOperatorKey(oc.Subject) {
   195  		return "", errors.New("expected subject to be an operator public key")
   196  	}
   197  	err := oc.validateAccountServerURL()
   198  	if err != nil {
   199  		return "", err
   200  	}
   201  	oc.Type = OperatorClaim
   202  	return oc.ClaimsData.encode(pair, oc)
   203  }
   204  
   205  func (oc *OperatorClaims) ClaimType() ClaimType {
   206  	return oc.Type
   207  }
   208  
   209  // DecodeOperatorClaims tries to create an operator claims from a JWt string
   210  func DecodeOperatorClaims(token string) (*OperatorClaims, error) {
   211  	claims, err := Decode(token)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	oc, ok := claims.(*OperatorClaims)
   216  	if !ok {
   217  		return nil, errors.New("not operator claim")
   218  	}
   219  	return oc, nil
   220  }
   221  
   222  func (oc *OperatorClaims) String() string {
   223  	return oc.ClaimsData.String(oc)
   224  }
   225  
   226  // Payload returns the operator specific data for an operator JWT
   227  func (oc *OperatorClaims) Payload() interface{} {
   228  	return &oc.Operator
   229  }
   230  
   231  // Validate the contents of the claims
   232  func (oc *OperatorClaims) Validate(vr *ValidationResults) {
   233  	oc.ClaimsData.Validate(vr)
   234  	oc.Operator.Validate(vr)
   235  }
   236  
   237  // ExpectedPrefixes defines the nkey types that can sign operator claims, operator
   238  func (oc *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte {
   239  	return []nkeys.PrefixByte{nkeys.PrefixByteOperator}
   240  }
   241  
   242  // Claims returns the generic claims data
   243  func (oc *OperatorClaims) Claims() *ClaimsData {
   244  	return &oc.ClaimsData
   245  }
   246  
   247  func (oc *OperatorClaims) updateVersion() {
   248  	oc.GenericFields.Version = libVersion
   249  }
   250  
   251  func (oc *OperatorClaims) GetTags() TagList {
   252  	return oc.Operator.Tags
   253  }