github.com/nats-io/jwt/v2@v2.5.6/v1compat/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  	"strings"
    23  
    24  	"github.com/nats-io/nkeys"
    25  )
    26  
    27  // Operator specific claims
    28  type Operator struct {
    29  	// Slice of real identities (like websites) that can be used to identify the operator.
    30  	Identities []Identity `json:"identity,omitempty"`
    31  	// Slice of other operator NKeys that can be used to sign on behalf of the main
    32  	// operator identity.
    33  	SigningKeys StringList `json:"signing_keys,omitempty"`
    34  	// AccountServerURL is a partial URL like "https://host.domain.org:<port>/jwt/v1"
    35  	// tools will use the prefix and build queries by appending /accounts/<account_id>
    36  	// or /operator to the path provided. Note this assumes that the account server
    37  	// can handle requests in a nats-account-server compatible way. See
    38  	// https://github.com/nats-io/nats-account-server.
    39  	AccountServerURL string `json:"account_server_url,omitempty"`
    40  	// A list of NATS urls (tls://host:port) where tools can connect to the server
    41  	// using proper credentials.
    42  	OperatorServiceURLs StringList `json:"operator_service_urls,omitempty"`
    43  	// Identity of the system account
    44  	SystemAccount string `json:"system_account,omitempty"`
    45  }
    46  
    47  // Validate checks the validity of the operators contents
    48  func (o *Operator) Validate(vr *ValidationResults) {
    49  	if err := o.validateAccountServerURL(); err != nil {
    50  		vr.AddError(err.Error())
    51  	}
    52  
    53  	for _, v := range o.validateOperatorServiceURLs() {
    54  		if v != nil {
    55  			vr.AddError(v.Error())
    56  		}
    57  	}
    58  
    59  	for _, i := range o.Identities {
    60  		i.Validate(vr)
    61  	}
    62  
    63  	for _, k := range o.SigningKeys {
    64  		if !nkeys.IsValidPublicOperatorKey(k) {
    65  			vr.AddError("%s is not an operator public key", k)
    66  		}
    67  	}
    68  	if o.SystemAccount != "" {
    69  		if !nkeys.IsValidPublicAccountKey(o.SystemAccount) {
    70  			vr.AddError("%s is not an account public key", o.SystemAccount)
    71  		}
    72  	}
    73  }
    74  
    75  func (o *Operator) validateAccountServerURL() error {
    76  	if o.AccountServerURL != "" {
    77  		// We don't care what kind of URL it is so long as it parses
    78  		// and has a protocol. The account server may impose additional
    79  		// constraints on the type of URLs that it is able to notify to
    80  		u, err := url.Parse(o.AccountServerURL)
    81  		if err != nil {
    82  			return fmt.Errorf("error parsing account server url: %v", err)
    83  		}
    84  		if u.Scheme == "" {
    85  			return fmt.Errorf("account server url %q requires a protocol", o.AccountServerURL)
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  // ValidateOperatorServiceURL returns an error if the URL is not a valid NATS or TLS url.
    92  func ValidateOperatorServiceURL(v string) error {
    93  	// should be possible for the service url to not be expressed
    94  	if v == "" {
    95  		return nil
    96  	}
    97  	u, err := url.Parse(v)
    98  	if err != nil {
    99  		return fmt.Errorf("error parsing operator service url %q: %v", v, err)
   100  	}
   101  
   102  	if u.User != nil {
   103  		return fmt.Errorf("operator service url %q - credentials are not supported", v)
   104  	}
   105  
   106  	if u.Path != "" {
   107  		return fmt.Errorf("operator service url %q - paths are not supported", v)
   108  	}
   109  
   110  	lcs := strings.ToLower(u.Scheme)
   111  	switch lcs {
   112  	case "nats":
   113  		return nil
   114  	case "tls":
   115  		return nil
   116  	default:
   117  		return fmt.Errorf("operator service url %q - protocol not supported (only 'nats' or 'tls' only)", v)
   118  	}
   119  }
   120  
   121  func (o *Operator) validateOperatorServiceURLs() []error {
   122  	var errs []error
   123  	for _, v := range o.OperatorServiceURLs {
   124  		if v != "" {
   125  			if err := ValidateOperatorServiceURL(v); err != nil {
   126  				errs = append(errs, err)
   127  			}
   128  		}
   129  	}
   130  	return errs
   131  }
   132  
   133  // OperatorClaims define the data for an operator JWT
   134  type OperatorClaims struct {
   135  	ClaimsData
   136  	Operator `json:"nats,omitempty"`
   137  }
   138  
   139  // NewOperatorClaims creates a new operator claim with the specified subject, which should be an operator public key
   140  func NewOperatorClaims(subject string) *OperatorClaims {
   141  	if subject == "" {
   142  		return nil
   143  	}
   144  	c := &OperatorClaims{}
   145  	c.Subject = subject
   146  	return c
   147  }
   148  
   149  // DidSign checks the claims against the operator's public key and its signing keys
   150  func (oc *OperatorClaims) DidSign(op Claims) bool {
   151  	if op == nil {
   152  		return false
   153  	}
   154  	issuer := op.Claims().Issuer
   155  	if issuer == oc.Subject {
   156  		return true
   157  	}
   158  	return oc.SigningKeys.Contains(issuer)
   159  }
   160  
   161  // Deprecated: AddSigningKey, use claim.SigningKeys.Add()
   162  func (oc *OperatorClaims) AddSigningKey(pk string) {
   163  	oc.SigningKeys.Add(pk)
   164  }
   165  
   166  // Encode the claims into a JWT string
   167  func (oc *OperatorClaims) Encode(pair nkeys.KeyPair) (string, error) {
   168  	if !nkeys.IsValidPublicOperatorKey(oc.Subject) {
   169  		return "", errors.New("expected subject to be an operator public key")
   170  	}
   171  	err := oc.validateAccountServerURL()
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  	oc.ClaimsData.Type = OperatorClaim
   176  	return oc.ClaimsData.Encode(pair, oc)
   177  }
   178  
   179  // DecodeOperatorClaims tries to create an operator claims from a JWt string
   180  func DecodeOperatorClaims(token string) (*OperatorClaims, error) {
   181  	v := OperatorClaims{}
   182  	if err := Decode(token, &v); err != nil {
   183  		return nil, err
   184  	}
   185  	return &v, nil
   186  }
   187  
   188  func (oc *OperatorClaims) String() string {
   189  	return oc.ClaimsData.String(oc)
   190  }
   191  
   192  // Payload returns the operator specific data for an operator JWT
   193  func (oc *OperatorClaims) Payload() interface{} {
   194  	return &oc.Operator
   195  }
   196  
   197  // Validate the contents of the claims
   198  func (oc *OperatorClaims) Validate(vr *ValidationResults) {
   199  	oc.ClaimsData.Validate(vr)
   200  	oc.Operator.Validate(vr)
   201  }
   202  
   203  // ExpectedPrefixes defines the nkey types that can sign operator claims, operator
   204  func (oc *OperatorClaims) ExpectedPrefixes() []nkeys.PrefixByte {
   205  	return []nkeys.PrefixByte{nkeys.PrefixByteOperator}
   206  }
   207  
   208  // Claims returns the generic claims data
   209  func (oc *OperatorClaims) Claims() *ClaimsData {
   210  	return &oc.ClaimsData
   211  }