github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/editoperator.go (about) 1 /* 2 * Copyright 2018-2023 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 cmd 17 18 import ( 19 "fmt" 20 "strings" 21 22 cli "github.com/nats-io/cliprompts/v2" 23 "github.com/nats-io/jwt/v2" 24 "github.com/nats-io/nkeys" 25 "github.com/nats-io/nsc/v2/cmd/store" 26 "github.com/spf13/cobra" 27 ) 28 29 func createEditOperatorCmd() *cobra.Command { 30 var params EditOperatorParams 31 cmd := &cobra.Command{ 32 Use: "operator", 33 Short: "Edit the operator", 34 Args: MaxArgs(0), 35 SilenceUsage: true, 36 RunE: func(cmd *cobra.Command, args []string) error { 37 return RunAction(cmd, args, ¶ms) 38 }, 39 } 40 params.signingKeys.BindFlags("sk", "", nkeys.PrefixByteOperator, cmd) 41 cmd.Flags().StringSliceVarP(¶ms.rmSigningKeys, "rm-sk", "", nil, "remove signing key - comma separated list or option can be specified multiple times") 42 cmd.Flags().StringSliceVarP(¶ms.tags, "tag", "", nil, "add tags for user - comma separated list or option can be specified multiple times") 43 cmd.Flags().StringSliceVarP(¶ms.rmTags, "rm-tag", "", nil, "remove tag - comma separated list or option can be specified multiple times") 44 cmd.Flags().StringVarP(¶ms.asu, "account-jwt-server-url", "u", "", "set account jwt server url for nsc sync (only http/https or nats service (nats/tls/ws/wss) urls supported if updating with nsc)") 45 cmd.Flags().StringVarP(¶ms.sysAcc, "system-account", "", "", "set system account by account by public key or name") 46 cmd.Flags().StringSliceVarP(¶ms.serviceURLs, "service-url", "n", nil, "add an operator service url for nsc where clients can access the NATS service (only nats/tls/ws/wss urls supported)") 47 cmd.Flags().StringSliceVarP(¶ms.rmServiceURLs, "rm-service-url", "", nil, "remove an operator service url for nsc where clients can access the NATS service (only nats/tls/ws/wss urls supported)") 48 cmd.Flags().BoolVarP(¶ms.reqSk, "require-signing-keys", "", false, "require accounts/user to be signed with a signing key") 49 cmd.Flags().BoolVarP(¶ms.rmAsu, "rm-account-jwt-server-url", "", false, "clear account server url") 50 params.TimeParams.BindFlags(cmd) 51 52 return cmd 53 } 54 55 func init() { 56 editCmd.AddCommand(createEditOperatorCmd()) 57 } 58 59 type EditOperatorParams struct { 60 SignerParams 61 GenericClaimsParams 62 claim *jwt.OperatorClaims 63 token string 64 asu string 65 rmAsu bool 66 sysAcc string 67 serviceURLs []string 68 rmServiceURLs []string 69 signingKeys SigningKeysParams 70 rmSigningKeys []string 71 reqSk bool 72 } 73 74 func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error { 75 p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx) 76 77 if !InteractiveFlag && ctx.NothingToDo("sk", "rm-sk", "start", "expiry", "tag", "rm-tag", "account-jwt-server-url", "service-url", "rm-service-url", "system-account", "require-signing-keys", "rm-account-jwt-server-url") { 78 ctx.CurrentCmd().SilenceUsage = false 79 return fmt.Errorf("specify an edit option") 80 } 81 return nil 82 } 83 84 func (p *EditOperatorParams) PreInteractive(ctx ActionCtx) error { 85 return nil 86 } 87 88 func (p *EditOperatorParams) Load(ctx ActionCtx) error { 89 var err error 90 91 name := ctx.StoreCtx().Store.GetName() 92 if !ctx.StoreCtx().Store.Has(store.JwtName(name)) { 93 return fmt.Errorf("no operator %q found", name) 94 } 95 96 d, err := ctx.StoreCtx().Store.Read(store.JwtName(name)) 97 if err != nil { 98 return err 99 } 100 101 oc, err := jwt.DecodeOperatorClaims(string(d)) 102 if err != nil { 103 return err 104 } 105 106 if p.asu == "" { 107 p.asu = oc.AccountServerURL 108 } 109 110 if p.sysAcc == "" { 111 p.sysAcc = oc.SystemAccount 112 } 113 114 if !p.reqSk { 115 p.reqSk = oc.StrictSigningKeyUsage 116 } 117 118 p.claim = oc 119 return nil 120 } 121 122 func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error { 123 var err error 124 if p.claim.NotBefore > 0 { 125 p.TimeParams.Start = UnixToDate(p.claim.NotBefore) 126 } 127 if p.claim.Expires > 0 { 128 p.TimeParams.Expiry = UnixToDate(p.claim.Expires) 129 } 130 if err = p.GenericClaimsParams.Edit(p.claim.Tags); err != nil { 131 return err 132 } 133 p.asu, err = cli.Prompt("account jwt server url", p.asu) 134 if err != nil { 135 return err 136 } 137 p.asu = strings.TrimSpace(p.asu) 138 139 ok, err := cli.Confirm("add a service url", true) 140 if err != nil { 141 return err 142 } 143 if ok { 144 for { 145 v, err := cli.Prompt("operator service url", "", cli.Val(jwt.ValidateOperatorServiceURL)) 146 if err != nil { 147 return err 148 } 149 // the list will prune empty urls 150 p.serviceURLs = append(p.serviceURLs, v) 151 ok, err := cli.Confirm("add another service url", true) 152 if err != nil { 153 return err 154 } 155 if !ok { 156 break 157 } 158 } 159 } 160 if len(p.claim.OperatorServiceURLs) > 0 { 161 ok, err = cli.Confirm("remove any service urls", true) 162 if err != nil { 163 return err 164 } 165 if ok { 166 idx, err := cli.MultiSelect("select service urls to remove", p.claim.OperatorServiceURLs) 167 if err != nil { 168 return err 169 } 170 for _, v := range idx { 171 p.rmServiceURLs = append(p.rmServiceURLs, p.claim.OperatorServiceURLs[v]) 172 } 173 } 174 } 175 176 if ok, err := cli.Confirm("Set system account", false); err != nil { 177 return err 178 } else if ok { 179 p.sysAcc, err = PickAccount(ctx.StoreCtx(), "") 180 if err != nil { 181 return err 182 } 183 } 184 185 if err := p.signingKeys.Edit(); err != nil { 186 return err 187 } 188 189 return p.SignerParams.Edit(ctx) 190 } 191 192 func (p *EditOperatorParams) setSystemAccount(ctx ActionCtx) error { 193 if p.sysAcc != "" { 194 if !nkeys.IsValidPublicAccountKey(p.sysAcc) { 195 if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil { 196 return err 197 } else { 198 if acc.Limits.JetStreamTieredLimits != nil { 199 return fmt.Errorf("system accounts cannot have tiered JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name)) 200 } 201 if acc.Limits.JetStreamLimits.Streams != 0 || 202 acc.Limits.JetStreamLimits.Consumer != 0 || 203 acc.Limits.JetStreamLimits.MaxAckPending != 0 || 204 acc.Limits.JetStreamLimits.DiskMaxStreamBytes != 0 || 205 acc.Limits.JetStreamLimits.MemoryMaxStreamBytes != 0 || 206 acc.Limits.JetStreamLimits.DiskStorage != 0 || 207 acc.Limits.JetStreamLimits.MemoryStorage != 0 || 208 acc.Limits.JetStreamLimits.MaxBytesRequired { 209 return fmt.Errorf("system accounts cannot have JetStream limits - run %q first", fmt.Sprintf("nsc edit account %s --js-disable", acc.Name)) 210 } 211 p.sysAcc = acc.Subject 212 } 213 } 214 } 215 return nil 216 } 217 218 func (p *EditOperatorParams) Validate(ctx ActionCtx) error { 219 var err error 220 if err = p.GenericClaimsParams.Valid(); err != nil { 221 return err 222 } 223 224 for _, v := range p.serviceURLs { 225 if err := jwt.ValidateOperatorServiceURL(v); err != nil { 226 return err 227 } 228 } 229 if err := p.setSystemAccount(ctx); err != nil { 230 return err 231 } 232 if p.reqSk { 233 accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts) 234 if err != nil { 235 return err 236 } 237 for _, accName := range accounts { 238 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName) 239 if err != nil { 240 return err 241 } 242 if ac.Issuer == p.claim.Subject { 243 return fmt.Errorf("account %q needs to be issued with a signing key first", accName) 244 } 245 usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users) 246 for _, usrName := range usrs { 247 uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName) 248 if err != nil { 249 return err 250 } 251 if uc.Issuer == ac.Subject { 252 return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName) 253 } 254 } 255 } 256 } 257 if err = p.signingKeys.Valid(); err != nil { 258 return err 259 } 260 if err = p.SignerParams.Resolve(ctx); err != nil { 261 return err 262 } 263 return nil 264 } 265 266 func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) { 267 r := store.NewDetailedReport(true) 268 r.ReportSum = false 269 270 var err error 271 if err = p.GenericClaimsParams.Run(ctx, p.claim, r); err != nil { 272 return nil, err 273 } 274 keys, _ := p.signingKeys.PublicKeys() 275 if len(keys) > 0 { 276 p.claim.SigningKeys.Add(keys...) 277 for _, k := range keys { 278 r.AddOK("added signing key %q", k) 279 } 280 } 281 p.claim.SigningKeys.Remove(p.rmSigningKeys...) 282 for _, k := range p.rmSigningKeys { 283 r.AddOK("removed signing key %q", k) 284 } 285 286 if p.claim.StrictSigningKeyUsage != p.reqSk { 287 p.claim.StrictSigningKeyUsage = p.reqSk 288 r.AddOK("strict signing key usage set to: %t", p.reqSk) 289 } 290 flags := ctx.CurrentCmd().Flags() 291 p.claim.AccountServerURL = p.asu 292 if flags.Changed("account-jwt-server-url") { 293 r.AddOK("set account jwt server url to %q", p.asu) 294 } 295 296 if p.rmAsu { 297 p.claim.AccountServerURL = "" 298 } 299 if flags.Changed("rm-account-jwt-server-url") { 300 r.AddOK("removed account server url") 301 } 302 303 if p.claim.SystemAccount != p.sysAcc { 304 p.claim.SystemAccount = p.sysAcc 305 r.AddOK("set system account %q", p.sysAcc) 306 } 307 308 for _, v := range p.serviceURLs { 309 p.claim.OperatorServiceURLs.Add(strings.ToLower(v)) 310 r.AddOK("added service url %q", v) 311 } 312 for _, v := range p.rmServiceURLs { 313 p.claim.OperatorServiceURLs.Remove(strings.ToLower(v)) 314 r.AddOK("removed service url %q", v) 315 } 316 317 p.token, err = p.claim.Encode(p.signerKP) 318 if err != nil { 319 return nil, err 320 } 321 s, err := ctx.StoreCtx().Store.StoreClaim([]byte(p.token)) 322 if s != nil { 323 r.Add(s) 324 } 325 if err != nil { 326 r.AddFromError(err) 327 } 328 r.AddOK("edited operator %q", p.claim.Name) 329 330 return r, nil 331 }