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 }