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