github.com/kbehouse/nsc@v0.0.6/cmd/editoperator.go (about) 1 /* 2 * Copyright 2018-2019 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 "github.com/kbehouse/nsc/cmd/store" 23 cli "github.com/nats-io/cliprompts/v2" 24 "github.com/nats-io/jwt/v2" 25 "github.com/nats-io/nkeys" 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 params.TimeParams.BindFlags(cmd) 50 51 return cmd 52 } 53 54 func init() { 55 editCmd.AddCommand(CreateEditOperatorCmd()) 56 } 57 58 type EditOperatorParams struct { 59 SignerParams 60 GenericClaimsParams 61 claim *jwt.OperatorClaims 62 token string 63 asu string 64 sysAcc string 65 serviceURLs []string 66 rmServiceURLs []string 67 signingKeys SigningKeysParams 68 rmSigningKeys []string 69 reqSk bool 70 } 71 72 func (p *EditOperatorParams) SetDefaults(ctx ActionCtx) error { 73 p.SignerParams.SetDefaults(nkeys.PrefixByteOperator, false, ctx) 74 75 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") { 76 ctx.CurrentCmd().SilenceUsage = false 77 return fmt.Errorf("specify an edit option") 78 } 79 return nil 80 } 81 82 func (p *EditOperatorParams) PreInteractive(ctx ActionCtx) error { 83 return nil 84 } 85 86 func (p *EditOperatorParams) Load(ctx ActionCtx) error { 87 var err error 88 89 name := ctx.StoreCtx().Store.GetName() 90 if !ctx.StoreCtx().Store.Has(store.JwtName(name)) { 91 return fmt.Errorf("no operator %q found", name) 92 } 93 94 d, err := ctx.StoreCtx().Store.Read(store.JwtName(name)) 95 if err != nil { 96 return err 97 } 98 99 oc, err := jwt.DecodeOperatorClaims(string(d)) 100 if err != nil { 101 return err 102 } 103 104 if p.asu == "" { 105 p.asu = oc.AccountServerURL 106 } 107 108 if p.sysAcc == "" { 109 p.sysAcc = oc.SystemAccount 110 } 111 112 if !p.reqSk { 113 p.reqSk = oc.StrictSigningKeyUsage 114 } 115 116 p.claim = oc 117 return nil 118 } 119 120 func (p *EditOperatorParams) PostInteractive(ctx ActionCtx) error { 121 var err error 122 if p.claim.NotBefore > 0 { 123 p.TimeParams.Start = UnixToDate(p.claim.NotBefore) 124 } 125 if p.claim.Expires > 0 { 126 p.TimeParams.Expiry = UnixToDate(p.claim.Expires) 127 } 128 if err = p.GenericClaimsParams.Edit(p.claim.Tags); err != nil { 129 return err 130 } 131 p.asu, err = cli.Prompt("account jwt server url", p.asu) 132 if err != nil { 133 return err 134 } 135 p.asu = strings.TrimSpace(p.asu) 136 137 ok, err := cli.Confirm("add a service url", true) 138 if err != nil { 139 return err 140 } 141 if ok { 142 for { 143 v, err := cli.Prompt("operator service url", "", cli.Val(jwt.ValidateOperatorServiceURL)) 144 if err != nil { 145 return err 146 } 147 // the list will prune empty urls 148 p.serviceURLs = append(p.serviceURLs, v) 149 ok, err := cli.Confirm("add another service url", true) 150 if err != nil { 151 return err 152 } 153 if !ok { 154 break 155 } 156 } 157 } 158 if len(p.claim.OperatorServiceURLs) > 0 { 159 ok, err = cli.Confirm("remove any service urls", true) 160 if err != nil { 161 return err 162 } 163 if ok { 164 idx, err := cli.MultiSelect("select service urls to remove", p.claim.OperatorServiceURLs) 165 if err != nil { 166 return err 167 } 168 for _, v := range idx { 169 p.rmServiceURLs = append(p.rmServiceURLs, p.claim.OperatorServiceURLs[v]) 170 } 171 } 172 } 173 174 if ok, err := cli.Confirm("Set system account", false); err != nil { 175 return err 176 } else if ok { 177 p.sysAcc, err = ctx.StoreCtx().PickAccount("") 178 if err != nil { 179 return err 180 } 181 } 182 183 if err := p.signingKeys.Edit(); err != nil { 184 return err 185 } 186 187 return p.SignerParams.Edit(ctx) 188 } 189 190 func (p *EditOperatorParams) Validate(ctx ActionCtx) error { 191 var err error 192 if err = p.GenericClaimsParams.Valid(); err != nil { 193 return err 194 } 195 196 for _, v := range p.serviceURLs { 197 if err := jwt.ValidateOperatorServiceURL(v); err != nil { 198 return err 199 } 200 } 201 if p.sysAcc != "" { 202 if !nkeys.IsValidPublicAccountKey(p.sysAcc) { 203 if acc, err := ctx.StoreCtx().Store.ReadAccountClaim(p.sysAcc); err != nil { 204 return err 205 } else { 206 p.sysAcc = acc.Subject 207 } 208 } 209 } 210 if p.reqSk { 211 accounts, err := ctx.StoreCtx().Store.ListSubContainers(store.Accounts) 212 if err != nil { 213 return err 214 } 215 for _, accName := range accounts { 216 ac, err := ctx.StoreCtx().Store.ReadAccountClaim(accName) 217 if err != nil { 218 return err 219 } 220 if ac.Issuer == p.claim.Subject { 221 return fmt.Errorf("account %q needs to be issued with a signing key first", accName) 222 } 223 usrs, _ := ctx.StoreCtx().Store.ListEntries(store.Accounts, accName, store.Users) 224 for _, usrName := range usrs { 225 uc, err := ctx.StoreCtx().Store.ReadUserClaim(accName, usrName) 226 if err != nil { 227 return err 228 } 229 if uc.Issuer == ac.Subject { 230 return fmt.Errorf("user %q in account %q needs to be issued with a signing key first", usrName, accName) 231 } 232 } 233 } 234 } 235 if err = p.signingKeys.Valid(); err != nil { 236 return err 237 } 238 if err = p.SignerParams.Resolve(ctx); err != nil { 239 return err 240 } 241 return nil 242 } 243 244 func (p *EditOperatorParams) Run(ctx ActionCtx) (store.Status, error) { 245 r := store.NewDetailedReport(true) 246 r.ReportSum = false 247 248 var err error 249 if err = p.GenericClaimsParams.Run(ctx, p.claim, r); err != nil { 250 return nil, err 251 } 252 keys, _ := p.signingKeys.PublicKeys() 253 if len(keys) > 0 { 254 p.claim.SigningKeys.Add(keys...) 255 for _, k := range keys { 256 r.AddOK("added signing key %q", k) 257 } 258 } 259 p.claim.SigningKeys.Remove(p.rmSigningKeys...) 260 for _, k := range p.rmSigningKeys { 261 r.AddOK("removed signing key %q", k) 262 } 263 264 if p.claim.StrictSigningKeyUsage != p.reqSk { 265 p.claim.StrictSigningKeyUsage = p.reqSk 266 r.AddOK("strict signing key usage set to: %t", p.reqSk) 267 } 268 flags := ctx.CurrentCmd().Flags() 269 p.claim.AccountServerURL = p.asu 270 if flags.Changed("account-jwt-server-url") { 271 r.AddOK("set account jwt server url to %q", p.asu) 272 } 273 274 if p.claim.SystemAccount != p.sysAcc { 275 p.claim.SystemAccount = p.sysAcc 276 r.AddOK("set system account %q", p.sysAcc) 277 } 278 279 for _, v := range p.serviceURLs { 280 p.claim.OperatorServiceURLs.Add(strings.ToLower(v)) 281 r.AddOK("added service url %q", v) 282 } 283 for _, v := range p.rmServiceURLs { 284 p.claim.OperatorServiceURLs.Remove(strings.ToLower(v)) 285 r.AddOK("removed service url %q", v) 286 } 287 288 p.token, err = p.claim.Encode(p.signerKP) 289 if err != nil { 290 return nil, err 291 } 292 s, err := ctx.StoreCtx().Store.StoreClaim([]byte(p.token)) 293 if s != nil { 294 r.Add(s) 295 } 296 if err != nil { 297 r.AddFromError(err) 298 } 299 r.AddOK("edited operator %q", p.claim.Name) 300 301 return r, nil 302 }