github.com/kbehouse/nsc@v0.0.6/cmd/nkeyconfigbuilder.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 "bytes" 20 "errors" 21 "fmt" 22 "strings" 23 24 "github.com/nats-io/jwt/v2" 25 ) 26 27 type NKeyConfigBuilder struct { 28 accounts accounts 29 accountToName map[string]string 30 accountClaims map[string]*jwt.AccountClaims 31 userClaims map[string][]*jwt.UserClaims 32 srcToPrivateImports map[string][]jwt.Import 33 } 34 35 func NewNKeyConfigBuilder() *NKeyConfigBuilder { 36 cb := NKeyConfigBuilder{} 37 cb.accounts.Accounts = make(map[string]account) 38 cb.accountToName = make(map[string]string) 39 cb.accountClaims = make(map[string]*jwt.AccountClaims) 40 cb.userClaims = make(map[string][]*jwt.UserClaims) 41 cb.srcToPrivateImports = make(map[string][]jwt.Import) 42 return &cb 43 } 44 45 func (cb *NKeyConfigBuilder) SetOutputDir(fp string) error { 46 return errors.New("nkey configurations don't support directory output") 47 } 48 49 func (cb *NKeyConfigBuilder) SetSystemAccount(id string) error { 50 return errors.New("nkey configurations don't support system account") 51 } 52 53 func (cb *NKeyConfigBuilder) Add(rawClaim []byte) error { 54 token := string(rawClaim) 55 gc, err := jwt.DecodeGeneric(token) 56 if err != nil { 57 return err 58 } 59 switch gc.ClaimType() { 60 case jwt.AccountClaim: 61 ac, err := jwt.DecodeAccountClaims(token) 62 if err != nil { 63 return err 64 } 65 cb.AddClaim(ac) 66 case jwt.UserClaim: 67 uc, err := jwt.DecodeUserClaims(token) 68 if err != nil { 69 return err 70 } 71 cb.AddClaim(uc) 72 } 73 return nil 74 } 75 76 func (cb *NKeyConfigBuilder) AddClaim(c jwt.Claims) { 77 ac, ok := c.(*jwt.AccountClaims) 78 if ok { 79 cb.addAccountClaim(ac) 80 return 81 } 82 uc, ok := c.(*jwt.UserClaims) 83 if ok { 84 cb.addUserClaim(uc) 85 return 86 } 87 } 88 89 func (cb *NKeyConfigBuilder) addAccountClaim(ac *jwt.AccountClaims) { 90 cb.accountClaims[ac.Subject] = ac 91 cb.accountToName[ac.Subject] = ac.Name 92 for _, i := range ac.Imports { 93 if i.Token != "" { 94 imps := cb.srcToPrivateImports[i.Account] 95 if imps == nil { 96 imps = make([]jwt.Import, 0) 97 } 98 imps = append(imps, *i) 99 cb.srcToPrivateImports[i.Account] = imps 100 } 101 } 102 } 103 104 func (cb *NKeyConfigBuilder) addUserClaim(uc *jwt.UserClaims) { 105 apk := uc.Issuer 106 if uc.IssuerAccount != "" { 107 apk = uc.IssuerAccount 108 } 109 users := cb.userClaims[apk] 110 if users == nil { 111 users = []*jwt.UserClaims{} 112 } 113 users = append(users, uc) 114 cb.userClaims[apk] = users 115 } 116 117 func (cb *NKeyConfigBuilder) Generate() ([]byte, error) { 118 if err := cb.parse(); err != nil { 119 return nil, err 120 } 121 return cb.serialize() 122 } 123 124 func (cb *NKeyConfigBuilder) parse() error { 125 for _, ac := range cb.accountClaims { 126 var a account 127 users := cb.userClaims[ac.Subject] 128 for _, uc := range users { 129 a.Users = append(a.Users, user{Nkey: uc.Subject}) 130 } 131 132 for _, exports := range ac.Exports { 133 var e export 134 if exports.IsStream() { 135 e.Stream = exports.Subject 136 } else { 137 e.Service = exports.Subject 138 } 139 140 if exports.TokenReq { 141 if e.Accounts == nil { 142 accts := make([]string, 0) 143 e.Accounts = &accts 144 } 145 146 a := *e.Accounts 147 imprts := cb.srcToPrivateImports[ac.Subject] 148 for _, imprt := range imprts { 149 if imprt.Type == exports.Type && imprt.Subject.IsContainedIn(exports.Subject) { 150 ac, err := jwt.DecodeActivationClaims(imprt.Token) 151 if err != nil { 152 return err 153 } 154 an := cb.accountToName[ac.Subject] 155 a = append(a, an) 156 } 157 158 } 159 e.Accounts = &a 160 } 161 a.Exports = append(a.Exports, e) 162 } 163 164 for _, x := range ac.Imports { 165 var e imprt 166 var src source 167 src.Subject = x.Subject 168 src.Account = cb.accountToName[x.Account] 169 if src.Account == "" { 170 return fmt.Errorf("unable to resolve account %q in import under current operator", x.Account) 171 } 172 if x.IsStream() { 173 e.Stream = &src 174 if x.LocalSubject != "" { 175 e.LocalSubject = x.LocalSubject 176 } else if x.GetTo() != "" { 177 e.Prefix = jwt.Subject(x.GetTo()) 178 } 179 } else { 180 e.Service = &src 181 if x.LocalSubject != "" { 182 e.LocalSubject = x.LocalSubject 183 } else if x.GetTo() != "" { 184 e.To = jwt.Subject(x.GetTo()) 185 } 186 } 187 a.Imports = append(a.Imports, e) 188 } 189 190 cb.accounts.Accounts[ac.Name] = a 191 } 192 return nil 193 } 194 195 func (cb *NKeyConfigBuilder) serialize() ([]byte, error) { 196 return []byte(cb.accounts.String()), nil 197 } 198 199 type accounts struct { 200 Accounts map[string]account `json:"accounts,omitempty"` 201 } 202 203 func (a *accounts) String() string { 204 var buf bytes.Buffer 205 buf.WriteString("accounts: {\n") 206 for k, v := range a.Accounts { 207 buf.WriteString(fmt.Sprintf(" %s: {\n", k)) 208 buf.WriteString(v.String()) 209 buf.WriteString(" }\n") 210 } 211 buf.WriteString("}\n") 212 return buf.String() 213 } 214 215 type account struct { 216 Users []user `json:"users,omitempty"` 217 Exports []export `json:"exports,omitempty"` 218 Imports []imprt `json:"imports,omitempty"` 219 } 220 221 func (a *account) String() string { 222 var buf bytes.Buffer 223 224 if len(a.Users) > 0 { 225 buf.WriteString(" users: [\n") 226 for _, u := range a.Users { 227 buf.WriteString(fmt.Sprintf(" %s\n", u.String())) 228 } 229 buf.WriteString(" ]\n") 230 } 231 232 if a.Exports != nil { 233 buf.WriteString(" exports: [\n") 234 for _, e := range a.Exports { 235 buf.WriteString(fmt.Sprintf(" %s\n", e.String())) 236 } 237 buf.WriteString(" ]\n") 238 } 239 if len(a.Imports) > 0 { 240 buf.WriteString(" imports: [\n") 241 for _, i := range a.Imports { 242 buf.WriteString(fmt.Sprintf(" %s\n", i.String())) 243 } 244 buf.WriteString(" ]\n") 245 } 246 return buf.String() 247 } 248 249 type user struct { 250 Nkey string `json:"nkey,omitempty"` 251 } 252 253 func (u *user) String() string { 254 return fmt.Sprintf("{ nkey: %s }", u.Nkey) 255 } 256 257 type export struct { 258 Stream jwt.Subject `json:"stream,omitempty"` 259 Service jwt.Subject `json:"service,omitempty"` 260 Accounts *[]string `json:"accounts,omitempty"` 261 } 262 263 func (ex *export) String() string { 264 var buf bytes.Buffer 265 buf.WriteString("{ ") 266 if ex.Stream != "" { 267 buf.WriteString("stream: ") 268 buf.WriteString(string(ex.Stream)) 269 } else { 270 buf.WriteString("service: ") 271 buf.WriteString(string(ex.Service)) 272 } 273 if ex.Accounts != nil { 274 buf.WriteString(", accounts: [") 275 buf.WriteString(strings.Join(*ex.Accounts, ",")) 276 buf.WriteString("]") 277 } 278 buf.WriteString(" }") 279 return buf.String() 280 } 281 282 type source struct { 283 Account string `json:"account,omitempty"` 284 Subject jwt.Subject `json:"subject,omitempty"` 285 } 286 287 func (s *source) String() string { 288 return fmt.Sprintf("{ account: %s, subject: %s }", s.Account, string(s.Subject)) 289 } 290 291 type imprt struct { 292 Stream *source `json:"stream,omitempty"` 293 Service *source `json:"service,omitempty"` 294 Prefix jwt.Subject `json:"prefix,omitempty"` 295 To jwt.Subject `json:"to,omitempty"` 296 LocalSubject jwt.RenamingSubject `json:"local_subject,omitempty"` 297 } 298 299 func (im *imprt) String() string { 300 var buf bytes.Buffer 301 buf.WriteString("{ ") 302 if im.Service != nil { 303 buf.WriteString("service: ") 304 buf.WriteString(im.Service.String()) 305 if im.To != "" { 306 buf.WriteString(", to: ") 307 buf.WriteString(string(im.To)) 308 } else if im.LocalSubject != "" { 309 buf.WriteString(", to: ") 310 buf.WriteString(string(im.LocalSubject)) 311 } 312 } else { 313 buf.WriteString("stream: ") 314 buf.WriteString(im.Stream.String()) 315 if im.Prefix != "" { 316 buf.WriteString(", prefix: ") 317 buf.WriteString(string(im.Prefix)) 318 } else if im.LocalSubject != "" { 319 buf.WriteString(", to: ") 320 buf.WriteString(string(im.LocalSubject)) 321 } 322 } 323 buf.WriteString("}") 324 325 return buf.String() 326 }