github.com/kbehouse/nsc@v0.0.6/cmd/describer.go (about) 1 /* 2 * Copyright 2018-2021 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 "fmt" 21 "strings" 22 23 "github.com/dustin/go-humanize" 24 "github.com/nats-io/jwt/v2" 25 "github.com/xlab/tablewriter" 26 ) 27 28 type Describer interface { 29 Describe() string 30 } 31 32 type AccountDescriber struct { 33 jwt.AccountClaims 34 } 35 36 func NewAccountDescriber(ac jwt.AccountClaims) *AccountDescriber { 37 return &AccountDescriber{AccountClaims: ac} 38 } 39 40 func (a *AccountDescriber) Describe() string { 41 var buf bytes.Buffer 42 43 table := tablewriter.CreateTable() 44 table.AddTitle("Account Details") 45 AddStandardClaimInfo(table, &a.AccountClaims) 46 table.AddSeparator() 47 48 info := false 49 if a.Description != "" { 50 table.AddRow("Description", strings.ReplaceAll(a.Description, "\n", " ")) 51 info = true 52 } 53 if a.InfoURL != "" { 54 table.AddRow("Info Url", a.InfoURL) 55 info = true 56 } 57 if info { 58 table.AddSeparator() 59 } 60 61 if len(a.SigningKeys) > 0 { 62 AddListValues(table, "Signing Keys", a.SigningKeys.Keys()) 63 table.AddSeparator() 64 } 65 66 addLimitRow := func(table *tablewriter.Table, name string, limit int64, inBytes bool) { 67 if limit > -1 { 68 val := fmt.Sprintf("%d", limit) 69 if inBytes { 70 val = fmt.Sprintf("%s (%d bytes)", humanize.Bytes(uint64(limit)), limit) 71 } 72 table.AddRow(name, val) 73 } else { 74 table.AddRow(name, "Unlimited") 75 } 76 } 77 78 lim := a.Limits 79 addLimitRow(table, "Max Connections", lim.Conn, false) 80 81 if lim.LeafNodeConn == 0 { 82 table.AddRow("Max Leaf Node Connections", "Not Allowed") 83 } else if lim.LeafNodeConn > 0 { 84 table.AddRow("Max Leaf Node Connections", fmt.Sprintf("%d", lim.LeafNodeConn)) 85 } else { 86 table.AddRow("Max Leaf Node Connections", "Unlimited") 87 } 88 89 addLimitRow(table, "Max Data", lim.Data, true) 90 addLimitRow(table, "Max Exports", lim.Exports, false) 91 addLimitRow(table, "Max Imports", lim.Imports, false) 92 addLimitRow(table, "Max Msg Payload", lim.Payload, true) 93 addLimitRow(table, "Max Subscriptions", lim.Subs, false) 94 95 we := "False" 96 if lim.WildcardExports { 97 we = "True" 98 } 99 table.AddRow("Exports Allows Wildcards", we) 100 101 AddPermissions(table, a.DefaultPermissions) 102 103 table.AddSeparator() 104 if a.Limits.DiskStorage == 0 && a.Limits.MemoryStorage == 0 { 105 table.AddRow("Jetstream", "Disabled") 106 } else { 107 table.AddRow("Jetstream", "Enabled") 108 switch { 109 case lim.DiskStorage > 0: 110 table.AddRow("Max Disk Storage", humanize.Bytes(uint64(lim.DiskStorage))) 111 case lim.DiskStorage == 0: 112 table.AddRow("Max Disk Storage", "Disabled") 113 default: 114 table.AddRow("Max Disk Storage", "Unlimited") 115 } 116 switch { 117 case lim.MemoryStorage > 0: 118 table.AddRow("Max Mem Storage", humanize.Bytes(uint64(lim.MemoryStorage))) 119 case lim.MemoryStorage == 0: 120 table.AddRow("Max Mem Storage", "Disabled") 121 default: 122 table.AddRow("Max Mem Storage", "Unlimited") 123 } 124 addLimitRow(table, "Max Streams", lim.Streams, false) 125 addLimitRow(table, "Max Consumer", lim.Consumer, false) 126 } 127 128 table.AddSeparator() 129 130 if len(a.Imports) == 0 { 131 table.AddRow("Imports", "None") 132 } 133 134 if len(a.Exports) == 0 { 135 table.AddRow("Exports", "None") 136 } 137 138 if len(a.Revocations) != 0 { 139 table.AddSeparator() 140 table.AddRow("Revocations", fmt.Sprintf("%d", len(a.Revocations))) 141 } 142 143 buf.WriteString(table.Render()) 144 145 if len(a.Exports) > 0 { 146 buf.WriteString("\n") 147 buf.WriteString(NewExportsDescriber(a.Exports).Describe()) 148 } 149 150 if len(a.Imports) > 0 { 151 buf.WriteString("\n") 152 buf.WriteString(NewImportsDescriber(a.Imports).Describe()) 153 } 154 155 if len(a.Mappings) > 0 { 156 buf.WriteString("\n") 157 buf.WriteString(NewMappingsDescriber(a.Mappings).Describe()) 158 } 159 160 if len(a.SigningKeys) > 0 { 161 for _, v := range a.SigningKeys { 162 if v == nil { 163 continue 164 } 165 buf.WriteString("\n") 166 buf.WriteString(NewScopedSkDescriber(v.(*jwt.UserScope)).Describe()) 167 } 168 table.AddSeparator() 169 } 170 171 return buf.String() 172 } 173 174 type ExportsDescriber struct { 175 jwt.Exports 176 } 177 178 func NewExportsDescriber(exports jwt.Exports) *ExportsDescriber { 179 var e ExportsDescriber 180 e.Exports = exports 181 return &e 182 } 183 184 func toYesNo(tf bool) string { 185 v := "Yes" 186 if !tf { 187 v = "No" 188 } 189 return v 190 } 191 192 func (e *ExportsDescriber) Describe() string { 193 table := tablewriter.CreateTable() 194 table.AddTitle("Exports") 195 table.AddHeaders("Name", "Type", "Subject", "Public", "Revocations", "Tracking") 196 for _, v := range e.Exports { 197 mon := "N/A" 198 rt := "" 199 if v.Type == jwt.Service { 200 if v.Latency != nil { 201 mon = fmt.Sprintf("%s (%d%%)", v.Latency.Results, v.Latency.Sampling) 202 } else { 203 mon = "-" 204 } 205 switch v.ResponseType { 206 case jwt.ResponseTypeStream: 207 rt = fmt.Sprintf(" [%s]", jwt.ResponseTypeStream) 208 case jwt.ResponseTypeChunked: 209 rt = fmt.Sprintf(" [%s]", jwt.ResponseTypeChunked) 210 } 211 } 212 213 st := strings.Title(v.Type.String()) 214 k := fmt.Sprintf("%s%s", st, rt) 215 table.AddRow(v.Name, k, v.Subject, toYesNo(!v.TokenReq), len(v.Revocations), mon) 216 } 217 218 tableDesc := tablewriter.CreateTable() 219 tableDesc.AddTitle("Exports - Descriptions") 220 tableDesc.AddHeaders("Name", "Description", "Info Url") 221 hasContent := false 222 for _, v := range e.Exports { 223 if v.Description == "" && v.InfoURL == "" { 224 continue 225 } 226 hasContent = true 227 tableDesc.AddRow(v.Name, strings.ReplaceAll(v.Description, "\n", " "), v.InfoURL) 228 } 229 230 ret := table.Render() 231 if hasContent { 232 ret = fmt.Sprintf("%s\n%s", ret, tableDesc.Render()) 233 } 234 235 return ret 236 } 237 238 type MappingsDescriber jwt.Mapping 239 240 func NewMappingsDescriber(m jwt.Mapping) *MappingsDescriber { 241 d := MappingsDescriber(m) 242 return &d 243 } 244 245 func (i *MappingsDescriber) Describe() string { 246 table := tablewriter.CreateTable() 247 table.AddTitle("Mappings") 248 table.AddHeaders("From", "To", "Weight (%)") 249 for k, v := range *i { 250 wSum := uint8(0) 251 for i, m := range v { 252 wSum += m.GetWeight() 253 if i == 0 { 254 table.AddRow(k, m.Subject, m.GetWeight()) 255 } else { 256 table.AddRow("", m.Subject, m.Weight) 257 } 258 } 259 table.AddRow("", "", fmt.Sprintf("sum=%d", wSum)) 260 } 261 return table.Render() 262 } 263 264 type ScopedSkDescriber jwt.UserScope 265 266 func NewScopedSkDescriber(m *jwt.UserScope) *ScopedSkDescriber { 267 return (*ScopedSkDescriber)(m) 268 } 269 270 func (s *ScopedSkDescriber) Describe() string { 271 var buf bytes.Buffer 272 buf.WriteString("\n") 273 table := tablewriter.CreateTable() 274 table.AddTitle("Scoped Signing Key - Details") 275 table.AddRow("Key", s.Key) 276 table.AddRow("role", s.Role) 277 AddPermissions(table, s.Template.Permissions) 278 AddLimits(table, s.Template.Limits) 279 table.AddRow("Bearer Token", toYesNo(s.Template.BearerToken)) 280 if len(s.Template.AllowedConnectionTypes) > 0 { 281 table.AddSeparator() 282 AddListValues(table, "Allowed Connection Types", s.Template.AllowedConnectionTypes) 283 } 284 return table.Render() 285 } 286 287 type ImportsDescriber struct { 288 jwt.Imports 289 } 290 291 func NewImportsDescriber(imports jwt.Imports) *ImportsDescriber { 292 var d ImportsDescriber 293 d.Imports = imports 294 return &d 295 } 296 297 func (i *ImportsDescriber) Describe() string { 298 table := tablewriter.CreateTable() 299 table.AddTitle("Imports") 300 table.AddHeaders("Name", "Type", "Remote", "Local", "Expires", "From Account", "Public") 301 302 for _, v := range i.Imports { 303 NewImportDescriber(*v).Brief(table) 304 } 305 306 return table.Render() 307 } 308 309 type ImportDescriber struct { 310 jwt.Import 311 } 312 313 func NewImportDescriber(im jwt.Import) *ImportDescriber { 314 return &ImportDescriber{im} 315 } 316 317 func (i *ImportDescriber) Brief(table *tablewriter.Table) { 318 local := i.GetTo() 319 remote := string(i.Subject) 320 321 if i.Type == jwt.Service && local != "" { 322 local, remote = remote, local 323 } else { 324 local = string(i.LocalSubject) 325 } 326 327 if i.Token == "" { 328 table.AddRow(i.Name, strings.Title(i.Type.String()), remote, local, "", Wide(i.Account), "Yes") 329 return 330 } 331 expiration := "" 332 ac, err := i.LoadActivation() 333 if err != nil { 334 expiration = fmt.Sprintf("error decoding: %v", err.Error()) 335 } else { 336 expiration = RenderDate(ac.Expires) 337 } 338 table.AddRow(i.Name, strings.Title(i.Type.String()), remote, local, expiration, Wide(i.Account), "No") 339 } 340 341 func (i *ImportDescriber) IsRemoteImport() bool { 342 return IsURL(i.Token) 343 } 344 345 func (i *ImportDescriber) LoadActivation() (*jwt.ActivationClaims, error) { 346 var token string 347 if i.IsRemoteImport() { 348 d, err := LoadFromURL(i.Token) 349 if err != nil { 350 return nil, err 351 } 352 token = string(d) 353 } else { 354 token = i.Token 355 } 356 return jwt.DecodeActivationClaims(token) 357 } 358 359 func AddStandardClaimInfo(table *tablewriter.Table, claims jwt.Claims) { 360 label := "Account ID" 361 issuer := "" 362 var tags jwt.TagList 363 if ac, ok := claims.(*jwt.ActivationClaims); ok { 364 if ac.IssuerAccount != "" { 365 issuer = ac.IssuerAccount 366 } 367 tags = ac.Tags 368 } 369 if acc, ok := claims.(*jwt.ActivationClaims); ok { 370 if acc.IssuerAccount != "" { 371 issuer = acc.IssuerAccount 372 } 373 tags = acc.Tags 374 } 375 if uc, ok := claims.(*jwt.UserClaims); ok { 376 label = "User ID" 377 if uc.IssuerAccount != "" { 378 issuer = uc.IssuerAccount 379 } 380 tags = uc.Tags 381 } 382 if oc, ok := claims.(*jwt.OperatorClaims); ok { 383 label = "Operator ID" 384 tags = oc.Tags 385 } 386 387 cd := claims.Claims() 388 if cd.Name != "" { 389 table.AddRow("Name", cd.Name) 390 } 391 table.AddRow(label, cd.Subject) 392 table.AddRow("Issuer ID", cd.Issuer) 393 if issuer != "" { 394 table.AddRow("Issuer Account", issuer) 395 } 396 table.AddRow("Issued", RenderDate(cd.IssuedAt)) 397 table.AddRow("Expires", RenderDate(cd.Expires)) 398 if len(tags) > 0 { 399 AddListValues(table, "Tags", tags) 400 } 401 } 402 403 type ActivationDescriber struct { 404 jwt.ActivationClaims 405 } 406 407 func NewActivationDescriber(a jwt.ActivationClaims) *ActivationDescriber { 408 return &ActivationDescriber{ActivationClaims: a} 409 } 410 411 func (c *ActivationDescriber) Describe() string { 412 hash, _ := c.HashID() 413 414 table := tablewriter.CreateTable() 415 table.AddTitle("Activation") 416 AddStandardClaimInfo(table, &c.ActivationClaims) 417 table.AddSeparator() 418 table.AddRow("Hash ID", hash) 419 table.AddSeparator() 420 table.AddRow("Import Type", strings.Title(c.ImportType.String())) 421 table.AddRow("Import Subject", string(c.ImportSubject)) 422 table.AddSeparator() 423 424 return table.Render() 425 } 426 427 func AddLimits(table *tablewriter.Table, lim jwt.Limits) { 428 if lim.Payload > 0 { 429 v := fmt.Sprintf("%d bytes (≈%s)", lim.Payload, humanize.Bytes(uint64(lim.Payload))) 430 table.AddRow("Max Msg Payload", v) 431 } else { 432 table.AddRow("Max Msg Payload", "Unlimited") 433 } 434 435 if lim.Data > 0 { 436 v := fmt.Sprintf("%d bytes (≈%s)", lim.Data, humanize.Bytes(uint64(lim.Data))) 437 table.AddRow("Max Data", v) 438 } else { 439 table.AddRow("Max Data", "Unlimited") 440 } 441 442 if lim.Subs > 0 { 443 v := fmt.Sprintf("%d", lim.Subs) 444 table.AddRow("Max Subs", v) 445 } else { 446 table.AddRow("Max Subs", "Unlimited") 447 } 448 449 if len(lim.Src) != 0 { 450 table.AddRow("Network Src", lim.Src) 451 } else { 452 table.AddRow("Network Src", "Any") 453 } 454 455 if len(lim.Times) > 0 { 456 for i, v := range lim.Times { 457 if i == 0 { 458 table.AddRow("Time", fmt.Sprintf("%s-%s", v.Start, v.End)) 459 } else { 460 table.AddRow("", fmt.Sprintf("%s-%s", v.Start, v.End)) 461 } 462 } 463 } else { 464 table.AddRow("Time", "Any") 465 } 466 } 467 468 func AddListValues(table *tablewriter.Table, label string, values []string) { 469 if len(values) > 0 { 470 for i, v := range values { 471 if i == 0 { 472 table.AddRow(label, string(v)) 473 } else { 474 table.AddRow("", string(v)) 475 } 476 } 477 } 478 } 479 480 type UserDescriber struct { 481 jwt.UserClaims 482 } 483 484 func NewUserDescriber(u jwt.UserClaims) *UserDescriber { 485 return &UserDescriber{UserClaims: u} 486 } 487 488 func AddPermissions(table *tablewriter.Table, u jwt.Permissions) { 489 if len(u.Pub.Allow) > 0 || len(u.Pub.Deny) > 0 || 490 len(u.Sub.Allow) > 0 || len(u.Sub.Deny) > 0 { 491 table.AddSeparator() 492 AddListValues(table, "Pub Allow", u.Pub.Allow) 493 AddListValues(table, "Pub Deny", u.Pub.Deny) 494 AddListValues(table, "Sub Allow", u.Sub.Allow) 495 AddListValues(table, "Sub Deny", u.Sub.Deny) 496 } 497 if u.Resp == nil { 498 table.AddRow("Response Permissions", "Not Set") 499 } else { 500 table.AddRow("Max Responses", u.Resp.MaxMsgs) 501 table.AddRow("Response Permission TTL", u.Resp.Expires.String()) 502 } 503 } 504 505 func (u *UserDescriber) Describe() string { 506 table := tablewriter.CreateTable() 507 table.AddTitle("User") 508 AddStandardClaimInfo(table, &u.UserClaims) 509 if u.HasEmptyPermissions() { 510 table.AddRow("Issuer Scoped", "Yes") 511 } else { 512 table.AddRow("Bearer Token", toYesNo(u.BearerToken)) 513 AddPermissions(table, u.Permissions) 514 table.AddSeparator() 515 AddLimits(table, u.Limits) 516 517 if len(u.AllowedConnectionTypes) > 0 { 518 table.AddSeparator() 519 AddListValues(table, "Allowed Connection Types", u.AllowedConnectionTypes) 520 } 521 } 522 return table.Render() 523 } 524 525 type OperatorDescriber struct { 526 jwt.OperatorClaims 527 } 528 529 func NewOperatorDescriber(o jwt.OperatorClaims) *OperatorDescriber { 530 return &OperatorDescriber{OperatorClaims: o} 531 } 532 533 func (o *OperatorDescriber) Describe() string { 534 table := tablewriter.CreateTable() 535 table.AddTitle("Operator Details") 536 AddStandardClaimInfo(table, &o.OperatorClaims) 537 if o.AccountServerURL != "" { 538 table.AddRow("Account JWT Server", o.AccountServerURL) 539 } 540 541 AddListValues(table, "Operator Service URLs", o.OperatorServiceURLs) 542 543 if o.SystemAccount != "" { 544 decoration := "" 545 if fn, err := friendlyNames(o.Name); err == nil { 546 if name, ok := fn[o.SystemAccount]; ok { 547 decoration = " / " + name 548 } 549 } 550 table.AddRow("System Account", o.SystemAccount+decoration) 551 } 552 table.AddRow("Require Signing Keys", o.StrictSigningKeyUsage) 553 554 if len(o.SigningKeys) > 0 { 555 table.AddSeparator() 556 AddListValues(table, "Signing Keys", o.SigningKeys) 557 } 558 559 return table.Render() 560 }