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  }