github.com/nats-io/nsc@v0.0.0-20221206222106-35db9400b257/cmd/editoperator.go (about) 1 /* 2 * Copyright 2018-2022 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/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/nats 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 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 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 = ctx.StoreCtx().PickAccount("") 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) Validate(ctx ActionCtx) error { 193 var err error 194 if err = p.GenericClaimsParams.Valid(); err != nil { 195 return err 196 } 197 198 for _, v := range p.serviceURLs { 199 if err := jwt.ValidateOperatorServiceURL(v); err != nil { 200 return err 201 } 202 } 203 if p.sysAcc != "" { 204 if !nkeys.IsValidPublicAccountKey(p.sysAcc) { 205 if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil { 206 return err 207 } else { 208 p.sysAcc = acc.Subject 209 } 210 } 211 } 212 if p.reqSk { 213 accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts) 214 if err != nil { 215 return err 216 } 217 for _, accName := range accounts { 218 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName) 219 if err != nil { 220 return err 221 } 222 if ac.Issuer == p.claim.Subject { 223 return fmt.Errorf("account %q needs to be issued with a signing key first", accName) 224 } 225 usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users) 226 for _, usrName := range usrs { 227 uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName) 228 if err != nil { 229 return err 230 } 231 if uc.Issuer == ac.Subject { 232 return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName) 233 } 234 } 235 } 236 } 237 if err = p.signingKeys.Valid(); err != nil { 238 return err 239 } 240 if err = p.SignerParams.Resolve(ctx); err != nil { 241 return err 242 } 243 return nil 244 } 245 246 func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) { 247 r := store.NewDetailedReport(true) 248 r.ReportSum = false 249 250 var err error 251 if err = p.GenericClaimsParams.Run(ctx, p.claim, r); err != nil { 252 return nil, err 253 } 254 keys, _ := p.signingKeys.PublicKeys() 255 if len(keys) > 0 { 256 p.claim.SigningKeys.Add(keys...) 257 for _, k := range keys { 258 r.AddOK("added signing key %q", k) 259 } 260 } 261 p.claim.SigningKeys.Remove(p.rmSigningKeys...) 262 for _, k := range p.rmSigningKeys { 263 r.AddOK("removed signing key %q", k) 264 } 265 266 if p.claim.StrictSigningKeyUsage != p.reqSk { 267 p.claim.StrictSigningKeyUsage = p.reqSk 268 r.AddOK("strict signing key usage set to: %t", p.reqSk) 269 } 270 flags := ctx.CurrentCmd().Flags() 271 p.claim.AccountServerURL = p.asu 272 if flags.Changed("account-jwt-server-url") { 273 r.AddOK("set account jwt server url to %q", p.asu) 274 } 275 276 if p.rmAsu { 277 p.claim.AccountServerURL = "" 278 } 279 if flags.Changed("rm-account-jwt-server-url") { 280 r.AddOK("removed account server url") 281 } 282 283 if p.claim.SystemAccount != p.sysAcc { 284 p.claim.SystemAccount = p.sysAcc 285 r.AddOK("set system account %q", p.sysAcc) 286 } 287 288 for _, v := range p.serviceURLs { 289 p.claim.OperatorServiceURLs.Add(strings.ToLower(v)) 290 r.AddOK("added service url %q", v) 291 } 292 for _, v := range p.rmServiceURLs { 293 p.claim.OperatorServiceURLs.Remove(strings.ToLower(v)) 294 r.AddOK("removed service url %q", v) 295 } 296 297 p.token, err = p.claim.Encode(p.signerKP) 298 if err != nil { 299 return nil, err 300 } 301 s, err := ctx.StoreCtx().Store.StoreClaim([]byte(p.token)) 302 if s != nil { 303 r.Add(s) 304 } 305 if err != nil { 306 r.AddFromError(err) 307 } 308 r.AddOK("edited operator %q", p.claim.Name) 309 310 return r, nil 311 }