From 832696e760385b956b826fdc2c4754c374b66956 Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Wed, 13 Jul 2022 17:55:17 -0400 Subject: [PATCH 1/2] prettier formatting for posture checks from CLI --- go.mod | 1 + go.sum | 2 + ziti/cmd/ziti/cmd/edge/list.go | 363 ++++++++++++++++++++++----------- 3 files changed, 244 insertions(+), 122 deletions(-) diff --git a/go.mod b/go.mod index b527e050f..4d57a49b3 100644 --- a/go.mod +++ b/go.mod @@ -103,6 +103,7 @@ require ( github.com/hashicorp/raft v1.3.9 // indirect github.com/hashicorp/raft-boltdb v0.0.0-20220329195025-15018e9b97e0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jedib0t/go-pretty v4.3.0+incompatible // indirect github.com/jessevdk/go-flags v1.5.0 // indirect github.com/jinzhu/copier v0.3.5 // indirect github.com/josharian/intern v1.0.0 // indirect diff --git a/go.sum b/go.sum index 6403f94e7..1ecd7b000 100644 --- a/go.sum +++ b/go.sum @@ -479,6 +479,8 @@ github.com/influxdata/influxdb-client-go/v2 v2.2.2/go.mod h1:fa/d1lAdUHxuc1jedx3 github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= +github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= diff --git a/ziti/cmd/ziti/cmd/edge/list.go b/ziti/cmd/ziti/cmd/edge/list.go index a55d3149e..52014a502 100644 --- a/ziti/cmd/ziti/cmd/edge/list.go +++ b/ziti/cmd/ziti/cmd/edge/list.go @@ -17,12 +17,14 @@ package edge import ( + "bytes" "fmt" + "github.com/go-openapi/runtime" "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" + "github.com/openziti/foundation/v2/stringz" "io" "net/url" - "reflect" "sort" "strconv" "strings" @@ -30,7 +32,6 @@ import ( "github.com/Jeffail/gabs" "github.com/openziti/edge/rest_management_api_client/certificate_authority" "github.com/openziti/edge/rest_model" - "github.com/openziti/foundation/v2/errorz" "github.com/openziti/ziti/ziti/cmd/ziti/cmd/api" "github.com/openziti/ziti/ziti/cmd/ziti/cmd/common" cmdhelper "github.com/openziti/ziti/ziti/cmd/ziti/cmd/helpers" @@ -291,7 +292,7 @@ func newSubListCmdForEntityType(entityType string, subType string, outputF outpu Run: func(cmd *cobra.Command, args []string) { options.Cmd = cmd options.Args = args - err := runListChilden(entityType, subType, options, outputF) + err := runListChildren(entityType, subType, options, outputF) cmdhelper.CheckErr(err) }, SuggestFor: []string{}, @@ -339,20 +340,6 @@ func ListEntitiesOfType(entityType string, params url.Values, logJSON bool, out return children, api.GetPaging(jsonParsed), err } -func toInt64(c *gabs.Container, path string, errorHolder errorz.ErrorHolder) int64 { - data := c.S(path).Data() - if data == nil { - errorHolder.SetError(errors.Errorf("%v not found", path)) - return 0 - } - val, ok := data.(float64) - if !ok { - errorHolder.SetError(errors.Errorf("%v not a number, it's a %v", path, reflect.TypeOf(data))) - return 0 - } - return int64(val) -} - // ListEntitiesOfType queries the Ziti Controller for entities of the given type func filterSubEntitiesOfType(entityType, subType, entityId, filter string, o *api.Options) ([]*gabs.Container, *api.Paging, error) { jsonParsed, err := util.EdgeControllerListSubEntities(entityType, subType, entityId, filter, o.OutputJSONResponse, o.Out, o.Timeout, o.Verbose) @@ -802,118 +789,264 @@ func outputIdentities(o *api.Options, children []*gabs.Container, pagingInfo *ap return nil } -func outputPostureCheck(o *api.Options, entity *gabs.Container) error { - id, _ := entity.Path("id").Data().(string) - typeId, _ := entity.Path("typeId").Data().(string) - name, _ := entity.Path("name").Data().(string) - roleAttributes := entity.Path("roleAttributes").String() - - config := "" - - switch typeId { - case "MFA": - timeoutFloat, _ := entity.Path("timeoutSeconds").Data().(float64) - timeout := int64(timeoutFloat) - promptOnWake, _ := entity.Path("promptOnWake").Data().(bool) - promptOnUnlock, _ := entity.Path("promptOnUnlock").Data().(bool) - ignoreLegacyEndpoints, _ := entity.Path("ignoreLegacyEndpoints").Data().(bool) - config = fmt.Sprintf("timeout: %d, wake: %t, unlock: %t, ignore: %t", timeout, promptOnWake, promptOnUnlock, ignoreLegacyEndpoints) - case "MAC": - containers, _ := entity.Path("macAddresses").Children() - config = containerArrayToString(containers, 4) - case "DOMAIN": - containers, _ := entity.Path("domains").Children() - config = containerArrayToString(containers, 4) - case "OS": - operatingSystems, _ := entity.Path("operatingSystems").Children() - config = strings.Join(postureCheckOsToStrings(operatingSystems), ",") - case "PROCESS_MULTI": - postureCheck := rest_model.PostureCheckProcessMultiDetail{} - if err := postureCheck.UnmarshalJSON(entity.Bytes()); err != nil { - return err - } +func getEllipsesString(val string, lead, lag int) string { + total := lead + lag + 3 - baseConfig := fmt.Sprintf("(SEMANTIC: %s)", *postureCheck.Semantic) + if len(val) <= total { + return val + } - if _, err := fmt.Fprintf(o.Out, "id: %-10v type: %-10v name: %-15v role attributes: %-10s param: %v\n", id, typeId, name, roleAttributes, baseConfig); err != nil { - return err - } + return val[0:lead] + "..." + val[len(val)-lag:] +} - for _, process := range postureCheck.Processes { - process.SignerFingerprints = getEllipsesStrings(process.SignerFingerprints, 4, 2) - process.Hashes = getEllipsesStrings(process.Hashes, 4, 2) - _, _ = fmt.Fprintf(o.Out, "\t(OS: %s, PATH: %s, HASHES: %s, SIGNER: %s)\n", *process.OsType, *process.Path, strings.Join(process.Hashes, ","), strings.Join(process.SignerFingerprints, ", ")) +func strSliceToStr(strs []string, width int) string { + builder := strings.Builder{} + for i, str := range strs { + if i != 0 { + if i%width == 0 { + //builder.WriteRune('\n') + } else { + builder.WriteRune(' ') + } } - return nil + builder.WriteString(str) + } - case "PROCESS": - process := entity.Path("process") + return builder.String() +} - os := process.Path("osType").Data().(string) - path := process.Path("path").Data().(string) +func strSliceToStrEllipses(strs []string, width, lead, lag int) string { + var ret []string + for _, str := range strs { + ret = append(ret, getEllipsesString(str, lead, lag)) + } + return strSliceToStr(ret, width) +} - var hashStrings []string - if val := process.Path("hashes").Data(); val != nil { - hashes := val.([]interface{}) +func WrapHardEllipses(str string, wrapLen int) string { + newStr := text.WrapHard(str, wrapLen) - for _, hash := range hashes { - hashStr := hash.(string) - hashStrings = append(hashStrings, getEllipsesString(hashStr, 4, 2)) - } - } - signerFingerprint := "N/A" - if val := process.Path("signerFingerprint").Data(); val != nil { - if valStr := val.(string); valStr != "" { - signerFingerprint = getEllipsesString(valStr, 4, 2) - } - } + if newStr != str { + newStr = newStr[:len(newStr)-3] + "..." + } - if len(hashStrings) == 0 { - hashStrings = append(hashStrings, "N/A") - } + return newStr +} - config = fmt.Sprintf("\n\t(OS: %s, PATH: %s, HASHES: %s, SIGNER: %s)", os, path, strings.Join(hashStrings, ","), signerFingerprint) +func outputPostureChecks(options *api.Options, children []*gabs.Container, pagingInfo *api.Paging) error { + if options.OutputJSONResponse { + return nil } - if _, err := fmt.Fprintf(o.Out, "id: %-10v type: %-10v name: %-15v role attributes: %-10s param: %v\n", id, typeId, name, roleAttributes, config); err != nil { - return err - } + outTable := table.NewWriter() + outTable.SetStyle(table.StyleRounded) + outTable.Style().Options.SeparateRows = true - return nil -} + rowConfigAutoMerge := table.RowConfig{AutoMerge: true} -func getEllipsesString(val string, lead, lag int) string { - total := lead + lag + 3 + outTable.AppendHeader(table.Row{"ID", "Name", "Type", "Attributes", "Configuration", "Configuration", "Configuration"}, rowConfigAutoMerge) - if len(val) <= total { - return val - } + outTable.SetColumnConfigs([]table.ColumnConfig{ + {Number: 1, AutoMerge: true}, + {Number: 2, AutoMerge: true}, + {Number: 3, AutoMerge: true, WidthMax: 20, WidthMaxEnforcer: WrapHardEllipses}, + {Number: 4, AutoMerge: true, WidthMax: 20, WidthMaxEnforcer: WrapHardEllipses}, + {Number: 5, WidthMax: 20, WidthMaxEnforcer: WrapHardEllipses}, + {Number: 6, WidthMax: 50, WidthMaxEnforcer: WrapHardEllipses}, + {Number: 7, WidthMax: 50, WidthMaxEnforcer: WrapHardEllipses}, + }) - return val[0:lead] + "..." + val[len(val)-lag:] -} + for i, entity := range children { + json := entity.EncodeJSON() + detail, err := rest_model.UnmarshalPostureCheckDetail(bytes.NewBuffer(json), runtime.JSONConsumer()) -func getEllipsesStrings(values []string, lead, lag int) []string { - var ret []string - for _, val := range values { - ret = append(ret, getEllipsesString(val, lead, lag)) - } + id := stringz.OrEmpty(detail.ID()) + name := stringz.OrEmpty(detail.Name()) + timeout := "never" - return ret -} + roleAttributes := strSliceToStr(*detail.RoleAttributes(), 1) -func outputPostureChecks(o *api.Options, children []*gabs.Container, pagingInfo *api.Paging) error { - if o.OutputJSONResponse { - return nil - } + if roleAttributes == "" { + roleAttributes = "" + } - for _, entity := range children { - if err := outputPostureCheck(o, entity); err != nil { - return err + typeStr := detail.TypeID() + + //defeat cell merging by adding a space every other row + if i%2 == 0 { + roleAttributes = roleAttributes + " " + typeStr = typeStr + " " } - } - pagingInfo.Output(o) + if err != nil { + msg := "Error unmarshalling index " + strconv.Itoa(i) + ": " + err.Error() + _, _ = options.ErrOutputWriter().Write([]byte(msg)) + } else { + switch detail.TypeID() { + case "MFA": + mfaDetail := detail.(*rest_model.PostureCheckMfaDetail) + + if mfaDetail.TimeoutSeconds > 0 { + timeout = fmt.Sprintf("%ds", mfaDetail.TimeoutSeconds) + } + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Timeout", + timeout, + timeout, + }, rowConfigAutoMerge) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Prompt On Wake", + mfaDetail.PromptOnWake, + mfaDetail.PromptOnWake, + }, rowConfigAutoMerge) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Prompt On Unlock", + mfaDetail.PromptOnUnlock, + mfaDetail.PromptOnUnlock, + }, rowConfigAutoMerge) + + case "MAC": + macDetail := detail.(*rest_model.PostureCheckMacAddressDetail) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "MAC Address", + strSliceToStrEllipses(macDetail.MacAddresses, 3, 3, 3), + strSliceToStrEllipses(macDetail.MacAddresses, 3, 3, 3), + }, rowConfigAutoMerge) + case "DOMAIN": + domainDetails := detail.(*rest_model.PostureCheckDomainDetail) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Windows Domain", + strSliceToStr(domainDetails.Domains, 3), + strSliceToStr(domainDetails.Domains, 3), + }, rowConfigAutoMerge) + case "OS": + osDetails := detail.(*rest_model.PostureCheckOperatingSystemDetail) + + for _, os := range osDetails.OperatingSystems { + osType := string(*os.Type) + + for _, version := range os.Versions { + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + osType, + version, + version, + }, rowConfigAutoMerge) + } + } + + case "PROCESS_MULTI": + procMultiDetail := detail.(*rest_model.PostureCheckProcessMultiDetail) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Semantic", + string(*procMultiDetail.Semantic), + string(*procMultiDetail.Semantic), + }, rowConfigAutoMerge) + + for _, process := range procMultiDetail.Processes { + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Path", + stringz.OrEmpty(process.Path), + stringz.OrEmpty(process.Path), + }, rowConfigAutoMerge) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + " ", + "Hashes", + strSliceToStrEllipses(process.Hashes, 3, 4, 4), + }) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + " ", + "Signers", + strSliceToStrEllipses(process.SignerFingerprints, 1, 4, 4), + }) + } + + case "PROCESS": + procDetail := detail.(*rest_model.PostureCheckProcessDetail) + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Path", + stringz.OrEmpty(procDetail.Process.Path), + stringz.OrEmpty(procDetail.Process.Path), + }, rowConfigAutoMerge) + + for _, hash := range procDetail.Process.Hashes { + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Hash", + getEllipsesString(hash, 4, 4), + getEllipsesString(hash, 4, 4), + }, rowConfigAutoMerge) + } + + outTable.AppendRow(table.Row{ + id, + name, + typeStr, + roleAttributes, + "Signer", + getEllipsesString(procDetail.Process.SignerFingerprint, 4, 4), + getEllipsesString(procDetail.Process.SignerFingerprint, 4, 4), + }, rowConfigAutoMerge) + } + } + } + api.RenderTable(options, outTable, pagingInfo) return nil } @@ -1183,7 +1316,7 @@ func runListRoleAttributes(entityType string, o *api.Options) error { return nil } -func runListChilden(parentType, childType string, o *api.Options, outputF outputFunction) error { +func runListChildren(parentType, childType string, o *api.Options, outputF outputFunction) error { idOrName := o.Args[0] parentId, err := mapNameToID(parentType, idOrName, *o) if err != nil { @@ -1257,20 +1390,6 @@ func runListSummary(o *api.Options) error { return nil } -func containerArrayToString(containers []*gabs.Container, limit int) string { - var values []string - for _, container := range containers { - value := container.Data().(string) - values = append(values, value) - } - valuesLength := len(values) - if valuesLength > limit { - values = values[:limit-1] - values = append(values, fmt.Sprintf(" and %d more", valuesLength-limit)) - } - return strings.Join(values, ",") -} - func runListPostureCheckTypes(o *api.Options) error { children, pagingInfo, err := listEntitiesWithOptions("posture-check-types", o) @@ -1351,7 +1470,7 @@ func outputAuthPolicies(options *api.Options, children []*gabs.Container, info * err := detail.UnmarshalJSON(json) if err != nil { - msg := "Error unmarshaling index " + strconv.Itoa(i) + ": " + err.Error() + msg := "Error unmarshalling index " + strconv.Itoa(i) + ": " + err.Error() _, _ = options.ErrOutputWriter().Write([]byte(msg)) } else { secondarySigner := "" From c559542660f820ce848ade7108e0978a11d413df Mon Sep 17 00:00:00 2001 From: Andrew Martinez Date: Wed, 13 Jul 2022 18:02:36 -0400 Subject: [PATCH 2/2] re-enable new lines and comma between items --- ziti/cmd/ziti/cmd/edge/list.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ziti/cmd/ziti/cmd/edge/list.go b/ziti/cmd/ziti/cmd/edge/list.go index 52014a502..ea472832b 100644 --- a/ziti/cmd/ziti/cmd/edge/list.go +++ b/ziti/cmd/ziti/cmd/edge/list.go @@ -804,9 +804,9 @@ func strSliceToStr(strs []string, width int) string { for i, str := range strs { if i != 0 { if i%width == 0 { - //builder.WriteRune('\n') + builder.WriteRune('\n') } else { - builder.WriteRune(' ') + builder.WriteString(", ") } }