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  }