diff --git a/doc/release-notes.md b/doc/release-notes.md index 5a9574465..40fded743 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -6,6 +6,7 @@ Ziti 0.13 includes the following: * New APIs to list existing role attributes used by edge routers, identities and services * New APIs to list entities related by polices (such as listing edge routers available to a service via service edge router policies) * Enhancements to the LIST APIs for edge routers, identities and services which allow one to filter by roles + * A policy advisor API, which helps analyze policies and current system state to figure out if an identity should be able to use a service and if not, why not * CA Auto Enrollment now allows identities to inherit role attributes from the validating CA * New `identityRole` attributes added to CA entities * New APIs to list and manage Transit Routers @@ -15,6 +16,8 @@ Ziti 0.13 includes the following: * Fabric enhancements * New Xlink framework encapsulating the router capabilities for creating overlay mesh links. * Adjustable Xgress MTU size. + * All Ziti Go projects are now being built with Go 1.14 + * See here for change to Go in 1.14 - https://golang.org/doc/go1.14 ## Making Policies More User Friendly ### Listing Role Attributes in Use @@ -139,7 +142,149 @@ Example: id: a949cf80-b11b-4cce-bbb7-d2e4767878a6 name: baz terminator strategy: role attributes: ["development","sales","support"] id: ad95ec7d-6c05-42b6-b278-2a98a7e502df name: bar terminator strategy: role attributes: ["four","three","two"] id: e9673c77-7463-4517-a642-641ef35855cf name: foo terminator strategy: role attributes: ["one","three","two"] + +## Policy Advisor +This adds a new operation to the /identities endpoint + + * Endpoint: /identities + * New operations + * Query related identities: GET /identities//policy-advice/ + +This will return the following information about the identity and service: + + * If the identity can dial the service + * If the identity can bind the service + * How many edge routers the identity has access to + * How many edge routers the service can be accessed through + * Which edge routers the identity and service have in common (if this is none, then the service can't be accessed by the identity) + * Which of the common edge routers are on-line +Example result: + + { + "meta": {}, + "data": { + "identityId": "700347c8-ca3a-4438-9060-68f7255ee4f8", + "identity": { + "entity": "identities", + "id": "700347c8-ca3a-4438-9060-68f7255ee4f8", + "name": "ssh-host", + "_links": { + "self": { + "href": "./identities/700347c8-ca3a-4438-9060-68f7255ee4f8" + } + } + }, + "serviceId": "8fa27a3e-ffb1-4bd1-befa-fcd38a6c26b3", + "service": { + "entity": "services", + "id": "8fa27a3e-ffb1-4bd1-befa-fcd38a6c26b3", + "name": "ssh", + "_links": { + "self": { + "href": "./services/8fa27a3e-ffb1-4bd1-befa-fcd38a6c26b3" + } + } + }, + "isBindAllowed": true, + "isDialAllowed": false, + "identityRouterCount": 2, + "serviceRouterCount": 2, + "commonRouters": [ + { + "entity": "edge-routers", + "id": "43d220d8-860e-4d80-a25c-97322a7326b4", + "name": "us-west-1", + "_links": { + "self": { + "href": "./edge-routers/43d220d8-860e-4d80-a25c-97322a7326b4" + } + }, + "isOnline": false + }, + { + "entity": "edge-routers", + "id": "8c118857-c12e-430d-9109-c31f535933f6", + "name": "us-east-1", + "_links": { + "self": { + "href": "./edge-routers/8c118857-c12e-430d-9109-c31f535933f6" + } + }, + "isOnline": true + } + ] + } + } + +The CLI has also been updated with a new policy-advisor common. + +Examples: + + # Inspect all identities for policy issues + ziti edge controller policy-advisor identities + + # Inspect just the jsmith-laptop identity for policy issues with all services that the identity can access + ziti edge controller policy-advisor identities jsmith-laptop + + # Inspect the jsmith-laptop identity for issues related to the ssh service + ziti edge controller policy-advisor identities jsmith-laptop ssh + + # Inspect all services for policy issues + ziti edge controller policy-advisor services + + # Inspect just the ssh service for policy issues for all identities the service can access + ziti edge controller policy-advisor services ssh + + # Inspect the ssh service for issues related to the jsmith-laptop identity + ziti edge controller policy-advisor identities ssh jsmith-laptop + +Some example output of the CLI: + + $ ec policy-advisor identities -q + ERROR: mlapin-laptop (1) -> ssh-backup (0) Common Routers: (0/0) Dial: Y Bind: N + - Service has no edge routers assigned. Adjust service edge router policies. + + ERROR: mlapin-laptop (1) -> ssh (2) Common Routers: (0/0) Dial: Y Bind: N + - Identity and services have no edge routers in common. Adjust edge router policies and/or service edge router policies. + + ERROR: ndaniels-laptop (1) -> ssh-backup (0) Common Routers: (0/0) Dial: Y Bind: N + - Service has no edge routers assigned. Adjust service edge router policies. + + ERROR: ndaniels-laptop (1) -> ssh (2) Common Routers: (0/1) Dial: Y Bind: N + - Common edge routers are all off-line. Bring routers back on-line or adjust edge router policies and/or service edge router policies to increase common router pool. + + ERROR: Default Admin + - Identity does not have access to any services. Adjust service policies. + + ERROR: jsmith-laptop (2) -> ssh-backup (0) Common Routers: (0/0) Dial: Y Bind: N + - Service has no edge routers assigned. Adjust service edge router policies. + + OKAY : jsmith-laptop (2) -> ssh (2) Common Routers: (1/2) Dial: Y Bind: N + + ERROR: ssh-host (2) -> ssh-backup (0) Common Routers: (0/0) Dial: N Bind: Y + - Service has no edge routers assigned. Adjust service edge router policies. + + OKAY : ssh-host (2) -> ssh (2) Common Routers: (1/2) Dial: N Bind: Y + + ERROR: aortega-laptop + - Identity does not have access to any services. Adjust service policies. + + ERROR: djones-laptop (0) -> ssh-backup (0) Common Routers: (0/0) Dial: Y Bind: N + - Identity has no edge routers assigned. Adjust edge router policies. + - Service has no edge routers assigned. Adjust service edge router policies. + + ERROR: djones-laptop (0) -> ssh (2) Common Routers: (0/0) Dial: Y Bind: N + - Identity has no edge routers assigned. Adjust edge router policies. + + $ ec policy-advisor identities aortega-laptop ssh-backup -q + Found identities with id 70567104-d4bd-45f1-8179-bd1e6ab8751f for name aortega-laptop + Found services with id 46e94977-0efc-4e7d-b9ae-cc8df1c95fc1 for name ssh-backup + ERROR: aortega-laptop (0) -> ssh-backup (0) Common Routers: (0/0) Dial: N Bind: N + - No access to service. Adjust service policies. + - Identity has no edge routers assigned. Adjust edge router policies. + - Service has no edge routers assigned. Adjust service edge router policies. + ## CA Auto Enrollment Identity Attributes Identities that are enrolled via a CA can now inherit a static list of identity role attributes. The normal create, diff --git a/go.mod b/go.mod index f9015491b..55777cc5c 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,14 @@ module github.com/netfoundry/ziti-cmd -go 1.13 +go 1.14 -//replace github.com/netfoundry/ziti-foundation => ../ziti-foundation +// replace github.com/netfoundry/ziti-foundation => ../ziti-foundation -//replace github.com/netfoundry/ziti-fabric => ../ziti-fabric +// replace github.com/netfoundry/ziti-fabric => ../ziti-fabric -//replace github.com/netfoundry/ziti-sdk-golang => ../ziti-sdk-golang -//replace github.com/netfoundry/ziti-edge => ../ziti-edge +// replace github.com/netfoundry/ziti-sdk-golang => ../ziti-sdk-golang + +// replace github.com/netfoundry/ziti-edge => ../ziti-edge require ( github.com/Jeffail/gabs v1.4.0 @@ -23,10 +24,10 @@ require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 github.com/michaelquigley/pfxlog v0.0.0-20190813191113-2be43bd0dccc - github.com/netfoundry/ziti-edge v0.12.26 - github.com/netfoundry/ziti-fabric v0.11.10 + github.com/netfoundry/ziti-edge v0.13.0 + github.com/netfoundry/ziti-fabric v0.11.11 github.com/netfoundry/ziti-foundation v0.9.6 - github.com/netfoundry/ziti-sdk-golang v0.11.12 + github.com/netfoundry/ziti-sdk-golang v0.11.14 github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.9.1 github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 diff --git a/go.sum b/go.sum index 7efc9ecdc..7982eae59 100644 --- a/go.sum +++ b/go.sum @@ -224,16 +224,14 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/netfoundry/secretstream v0.1.1 h1:z6T7B5DuOtr9O+SmKaS9DzwOoGLYtgvyTzuPuR5FO7I= github.com/netfoundry/secretstream v0.1.1/go.mod h1:uasYkYSp0MmNSlKOWJ2sVzxPms8e58TS4ENq4yro86k= -github.com/netfoundry/ziti-edge v0.12.26 h1:z8TsTiEUVZ00LBsJDU7JoOBFsI1OQwVXrad9mtH7YnE= -github.com/netfoundry/ziti-edge v0.12.26/go.mod h1:PmctiYp1M9bP5taqWChTFIhsVnXbZwN6cEY9/xql1qw= -github.com/netfoundry/ziti-fabric v0.11.10 h1:LLzsAAjUSBU3WAa+4RgSR52s34XI1ol+UF5sOIU7LzU= -github.com/netfoundry/ziti-fabric v0.11.10/go.mod h1:Zb4n8ZwksX60BfBATVTY7XtN+8zsALBaVyNn1VBeYCY= -github.com/netfoundry/ziti-foundation v0.9.4 h1:0IsdbPTKJLbGNgq5gWFFwj/FYq+n9pnee69RGfXKli0= -github.com/netfoundry/ziti-foundation v0.9.4/go.mod h1:GN+ALX+7zr1e4vcI2j6VRbaDVcF0wnuih0p9vj22Of0= +github.com/netfoundry/ziti-edge v0.13.0 h1:bSW00f6BdgIjwv1VnrJSEwpX0ROoJIRUy+NfQ7ck1VI= +github.com/netfoundry/ziti-edge v0.13.0/go.mod h1:OvbtCV8qdGUEMesirROfpF8jneuJkXC4Dye5Pw23n64= +github.com/netfoundry/ziti-fabric v0.11.11 h1:G6xaDziZUPO5W1bzxlSp7toJ95CkXpkhA9BIxdnsUcM= +github.com/netfoundry/ziti-fabric v0.11.11/go.mod h1:b4KUJ05JJkK9jmIlXy4TODXIgLHTdoruPe0EJ5HJHPg= github.com/netfoundry/ziti-foundation v0.9.6 h1:rsZlM05qAa1PERTqcM4YeCilbCd1i95GbGUzh9OdgKU= github.com/netfoundry/ziti-foundation v0.9.6/go.mod h1:desAxe5jN/V/+cYe99TGEqxtMVdlJ1HfSRzCmuPJD7M= -github.com/netfoundry/ziti-sdk-golang v0.11.12 h1:AopEehvVeW3sl9zCzWnGJThbcL3jTC4Sfu5VA3kWkIY= -github.com/netfoundry/ziti-sdk-golang v0.11.12/go.mod h1:Wjd4fZxjbo2/cVDyq17KxAatqGzAjtXKiyk9XoxDG8s= +github.com/netfoundry/ziti-sdk-golang v0.11.14 h1:C9vRKPW32Kuld/+vpRKEubIQferQNqlhq5FMUL5MLOM= +github.com/netfoundry/ziti-sdk-golang v0.11.14/go.mod h1:YJZ2s/W+o+zUuizRCfKQton+PaNP7l9R+xHVuzcIaAY= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oleiade/reflections v1.0.0 h1:0ir4pc6v8/PJ0yw5AEtMddfXpWBXg9cnG7SgSoJuCgY= github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w= diff --git a/ziti/cmd/ziti/cmd/edge_controller/common.go b/ziti/cmd/ziti/cmd/edge_controller/common.go index 2f8d6a588..ede232643 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/common.go +++ b/ziti/cmd/ziti/cmd/edge_controller/common.go @@ -51,6 +51,21 @@ func mapNameToID(entityType string, val string) (string, error) { return entityId, nil } +func mapIdToName(entityType string, val string) (string, error) { + list, err := filterEntitiesOfType(entityType, fmt.Sprintf(`id="%s"`, val), false, nil) + if err != nil { + return "", err + } + + if len(list) < 1 { + return "", errors.Errorf("no %v found for id %v", entityType, val) + } + + entity := list[0] + name, _ := entity.Path("name").Data().(string) + return name, nil +} + func mapNamesToIDs(entityType string, list ...string) ([]string, error) { var result []string for _, val := range list { diff --git a/ziti/cmd/ziti/cmd/edge_controller/create.go b/ziti/cmd/ziti/cmd/edge_controller/create.go index 7673bc9e2..57b41f3e9 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/create.go +++ b/ziti/cmd/ziti/cmd/edge_controller/create.go @@ -41,6 +41,7 @@ func newCreateCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Com cmd.AddCommand(newCreateAuthenticatorCmd(f, out, errOut)) cmd.AddCommand(newCreateCaCmd(f, out, errOut)) cmd.AddCommand(newCreateConfigCmd(f, out, errOut)) + cmd.AddCommand(newCreateConfigTypeCmd(f, out, errOut)) cmd.AddCommand(newCreateEdgeRouterCmd(f, out, errOut)) cmd.AddCommand(newCreateEdgeRouterPolicyCmd(f, out, errOut)) cmd.AddCommand(newCreateTerminatorCmd(f, out, errOut)) diff --git a/ziti/cmd/ziti/cmd/edge_controller/create_config_type.go b/ziti/cmd/ziti/cmd/edge_controller/create_config_type.go new file mode 100644 index 000000000..bd5c212e8 --- /dev/null +++ b/ziti/cmd/ziti/cmd/edge_controller/create_config_type.go @@ -0,0 +1,98 @@ +/* + Copyright NetFoundry, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package edge_controller + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/pkg/errors" + + "github.com/Jeffail/gabs" + "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/common" + cmdutil "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/factory" + cmdhelper "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/helpers" + "github.com/spf13/cobra" +) + +type createConfigTypeOptions struct { + commonOptions +} + +// newCreateConfigTypeCmd creates the 'edge controller create service-policy' command +func newCreateConfigTypeCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &createConfigTypeOptions{ + commonOptions: commonOptions{ + CommonOptions: common.CommonOptions{Factory: f, Out: out, Err: errOut}, + }, + } + + cmd := &cobra.Command{ + Use: "config-type ", + Short: "creates a config type managed by the Ziti Edge Controller", + Long: "creates a config type managed by the Ziti Edge Controller", + Args: cobra.RangeArgs(1, 2), + Run: func(cmd *cobra.Command, args []string) { + options.Cmd = cmd + options.Args = args + err := runCreateConfigType(options) + cmdhelper.CheckErr(err) + }, + SuggestFor: []string{}, + } + + // allow interspersing positional args and flags + cmd.Flags().SetInterspersed(true) + cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") + + return cmd +} + +// runCreateConfigType create a new configType on the Ziti Edge Controller +func runCreateConfigType(o *createConfigTypeOptions) error { + var schemaMap map[string]interface{} + + if len(o.Args) == 2 { + schemaMap = map[string]interface{}{} + if err := json.Unmarshal([]byte(o.Args[1]), &schemaMap); err != nil { + fmt.Printf("Attempted to parse: %v\n", o.Args[1]) + fmt.Printf("Failing parsing JSON: %+v\n", err) + return errors.Errorf("unable to parse data as json: %v", err) + } + } + + entityData := gabs.New() + setJSONValue(entityData, o.Args[0], "name") + if schemaMap != nil { + setJSONValue(entityData, schemaMap, "schema") + } + + result, err := createEntityOfType("config-types", entityData.String(), &o.commonOptions) + + if err != nil { + panic(err) + } + + configTypeId := result.S("data", "id").Data() + + if _, err := fmt.Fprintf(o.Out, "%v\n", configTypeId); err != nil { + panic(err) + } + + return err +} diff --git a/ziti/cmd/ziti/cmd/edge_controller/delete.go b/ziti/cmd/ziti/cmd/edge_controller/delete.go index d45ea5358..1ef04c57a 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/delete.go +++ b/ziti/cmd/ziti/cmd/edge_controller/delete.go @@ -54,6 +54,7 @@ func newDeleteCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Com cmd.AddCommand(newDeleteAuthenticatorCmd("authenticator", newOptions())) cmd.AddCommand(newDeleteCmdForEntityType("ca", runDeleteEntityOfType, newOptions())) cmd.AddCommand(newDeleteCmdForEntityType("config", runDeleteEntityOfType, newOptions())) + cmd.AddCommand(newDeleteCmdForEntityType("config-type", runDeleteEntityOfType, newOptions())) cmd.AddCommand(newDeleteCmdForEntityType("edge-router", runDeleteEntityOfType, newOptions())) cmd.AddCommand(newDeleteCmdForEntityType("edge-router-policy", runDeleteEntityOfType, newOptions())) cmd.AddCommand(newDeleteCmdForEntityType("identity", runDeleteEntityOfType, newOptions())) diff --git a/ziti/cmd/ziti/cmd/edge_controller/list.go b/ziti/cmd/ziti/cmd/edge_controller/list.go index 4748dba8a..e0683a0d5 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/list.go +++ b/ziti/cmd/ziti/cmd/edge_controller/list.go @@ -300,7 +300,11 @@ func filterSubEntitiesOfType(entityType, subType, entityId, filter string, o *co return nil, err } - return jsonParsed.S("data").Children() + children, err := jsonParsed.S("data").Children() + if err == gabs.ErrNotObjOrArray { + return nil, nil + } + return children, err } func runListEdgeRouters(roleFilters []string, roleSemantic string, options *commonOptions) error { diff --git a/ziti/cmd/ziti/cmd/edge_controller/policy_advisor.go b/ziti/cmd/ziti/cmd/edge_controller/policy_advisor.go new file mode 100644 index 000000000..9975ced45 --- /dev/null +++ b/ziti/cmd/ziti/cmd/edge_controller/policy_advisor.go @@ -0,0 +1,402 @@ +/* + Copyright NetFoundry, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package edge_controller + +import ( + "fmt" + "github.com/Jeffail/gabs" + "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/common" + cmdutil "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/factory" + cmdhelper "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/cmd/helpers" + "github.com/netfoundry/ziti-cmd/ziti/cmd/ziti/util" + "github.com/spf13/cobra" + "io" +) + +// newPolicyAdvisor creates a command object for the "controller policy-advisor" command +func newPolicyAdivsorCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "policy-advisor", + Short: "runs sanity checks on various policy related entities managed by the Ziti Edge Controller", + Run: func(cmd *cobra.Command, args []string) { + err := cmd.Help() + cmdhelper.CheckErr(err) + }, + } + + cmd.AddCommand(newPolicyAdvisorIdentitiesCmd(f, out, errOut)) + cmd.AddCommand(newPolicyAdvisorServicesCmd(f, out, errOut)) + + return cmd +} + +type policyAdvisorOptions struct { + commonOptions + quiet bool +} + +// newPolicyAdvisorIdentitiesCmd creates the 'edge controller policy-advisor identities' command +func newPolicyAdvisorIdentitiesCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &policyAdvisorOptions{ + commonOptions: commonOptions{ + CommonOptions: common.CommonOptions{Factory: f, Out: out, Err: errOut}, + }, + } + + cmd := &cobra.Command{ + Use: "identities ? ?", + Short: "checks policies/connectivity between identities and services", + Args: cobra.RangeArgs(0, 2), + Run: func(cmd *cobra.Command, args []string) { + options.Cmd = cmd + options.Args = args + err := runIdentitiesPolicyAdvisor(options) + cmdhelper.CheckErr(err) + }, + SuggestFor: []string{}, + } + + // allow interspersing positional args and flags + cmd.Flags().SetInterspersed(true) + cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") + cmd.Flags().BoolVarP(&options.quiet, "quiet", "q", false, "Minimize output by hiding header") + + return cmd +} + +// newPolicyAdvisorServicesCmd creates the 'edge controller policy-advisor services' command +func newPolicyAdvisorServicesCmd(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Command { + options := &policyAdvisorOptions{ + commonOptions: commonOptions{ + CommonOptions: common.CommonOptions{Factory: f, Out: out, Err: errOut}, + }, + } + + cmd := &cobra.Command{ + Use: "services ? ?", + Short: "checks policies/connectivity between services and identities ", + Args: cobra.RangeArgs(0, 2), + Run: func(cmd *cobra.Command, args []string) { + options.Cmd = cmd + options.Args = args + err := runServicesPolicyAdvisor(options) + cmdhelper.CheckErr(err) + }, + SuggestFor: []string{}, + } + + // allow interspersing positional args and flags + cmd.Flags().SetInterspersed(true) + cmd.Flags().BoolVarP(&options.OutputJSONResponse, "output-json", "j", false, "Output the full JSON response from the Ziti Edge Controller") + cmd.Flags().BoolVarP(&options.quiet, "quiet", "q", false, "Minimize output by hiding header") + + return cmd +} + +// runIdentitiesPolicyAdvisor create a new policyAdvisor on the Ziti Edge Controller +func runIdentitiesPolicyAdvisor(o *policyAdvisorOptions) error { + if len(o.Args) > 0 { + identityId, err := mapNameToID("identities", o.Args[0]) + if err != nil { + return err + } + if len(o.Args) > 1 { + serviceId, err := mapNameToID("services", o.Args[1]) + if err != nil { + return err + } + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForIdentityAndService(identityId, serviceId, o) + } + + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForIdentity(identityId, o) + } + + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForIdentities(o) +} + +// runServicesPolicyAdvisor create a new policyAdvisor on the Ziti Edge Controller +func runServicesPolicyAdvisor(o *policyAdvisorOptions) error { + if len(o.Args) > 0 { + serviceId, err := mapNameToID("services", o.Args[0]) + if err != nil { + return err + } + + if len(o.Args) > 1 { + identityId, err := mapNameToID("identities", o.Args[1]) + if err != nil { + return err + } + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForIdentityAndService(identityId, serviceId, o) + } + + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForService(serviceId, o) + } + + if err := outputHeader(o); err != nil { + return err + } + return runPolicyAdvisorForServices(o) +} + +func outputHeader(o *policyAdvisorOptions) error { + if o.quiet { + return nil + } + _, err := fmt.Fprintf(o.Out, "\n"+ + "Policy General Guidelines\n"+ + " In order for an identity to dial or bind a service, the following must be true:\n"+ + " - The identity must have access to the service via a service policy of the correct type (dial or bind)\n"+ + " - The identity must have acces to at least one on-line edge router via an edge router policy\n"+ + " - The service must have access to at least one on-line edge router via a service edge router policy\n"+ + " - There must be at least one on-line edge router that both the identity and service have access to.\n"+ + "\n"+ + "Policy Advisor Output Guide:\n"+ + " STATUS = The status of the identity -> service reachability. Will be OKAY or ERROR. \n"+ + " ID = identity name\n"+ + " ID ROUTERS = number of routers accessible to the identity via edge router policies.\n"+ + " - See edge router polices for an identity: ziti edge controller list identity edge-router-policies \n"+ + " SVC = service name\n"+ + " SVC ROUTERS = number of routers accessible to the service via service edge router policies.\n"+ + " - See service edge router policies for a service with: ziti edge controller list service service-edge-router-policies \n"+ + " ONLINE COMMON ROUTERS = number of routers the identity and service have in common which are online.\n"+ + " COMMON ROUTERS = number of routers (online or offline) the identity and service have in common.\n"+ + " DIAL_OK = indicates if the identity has permission to dial the service.\n"+ + " - See service polices for a service : ziti edge controller list service service-policies \n"+ + " - See service polices for an identity: ziti edge controller list identity service-policies \n"+ + " BIND_OK = indicates if the identity has permission to bind the service.\n"+ + " ERROR_LIST = if the status is ERROR, error details will be listed on the following lines\n"+ + "\n"+ + "Output format: STATUS: ID (ID ROUTERS) -> SVC (SVC ROUTERS) Common Routers: (ONLINE COMMON ROUTERS/COMMON ROUTERS) Dial: DIAL_OK Bind: BIND_OK. ERROR_LIST\n"+ + "-------------------------------------------------------------------------------\n") + return err +} + +func runPolicyAdvisorForIdentities(o *policyAdvisorOptions) error { + skip := 0 + done := false + for !done { + filter := fmt.Sprintf(`true skip %v limit 2`, skip) + children, err := filterEntitiesOfType("identities", filter, false, o.Out) + if err != nil { + panic(err) + } + + for _, child := range children { + identityId, _ := child.S("id").Data().(string) + if err := runPolicyAdvisorForIdentity(identityId, o); err != nil { + panic(err) + } + } + skip += len(children) + if len(children) == 0 { + done = true + } + } + + if skip == 0 { + _, err := fmt.Fprintln(o.Out, "No identities found") + return err + } + + return nil +} + +func runPolicyAdvisorForServices(o *policyAdvisorOptions) error { + skip := 0 + done := false + for !done { + filter := fmt.Sprintf(`true skip %v limit 2`, skip) + children, err := filterEntitiesOfType("services", filter, false, o.Out) + if err != nil { + panic(err) + } + + for _, child := range children { + serviceId, _ := child.S("id").Data().(string) + if err := runPolicyAdvisorForService(serviceId, o); err != nil { + panic(err) + } + } + skip += len(children) + if len(children) == 0 { + done = true + } + } + + if skip == 0 { + _, err := fmt.Fprintln(o.Out, "No services found") + return err + } + + return nil +} + +func runPolicyAdvisorForIdentity(identityId string, o *policyAdvisorOptions) error { + skip := 0 + done := false + for !done { + filter := "true limit 2" + filter = fmt.Sprintf(`true skip %v limit 2`, skip) + children, err := filterSubEntitiesOfType("identities", "services", identityId, filter, &o.commonOptions) + if err != nil { + panic(err) + } + + for _, child := range children { + serviceId, _ := child.S("id").Data().(string) + if err := runPolicyAdvisorForIdentityAndService(identityId, serviceId, o); err != nil { + panic(err) + } + } + skip += len(children) + if len(children) == 0 { + done = true + } + } + + if skip == 0 { + identityName, err := mapIdToName("identities", identityId) + if err != nil { + return err + } + _, err = fmt.Fprintf(o.Out, "ERROR: %v %v\n\n", identityName, "\n - Identity does not have access to any services. Adjust service policies.") + return err + } + + return nil +} + +func runPolicyAdvisorForService(serviceId string, o *policyAdvisorOptions) error { + skip := 0 + done := false + for !done { + filter := "true limit 2" + filter = fmt.Sprintf(`true skip %v limit 2`, skip) + children, err := filterSubEntitiesOfType("services", "identities", serviceId, filter, &o.commonOptions) + if err != nil { + panic(err) + } + + for _, child := range children { + identityId, _ := child.S("id").Data().(string) + if err := runPolicyAdvisorForIdentityAndService(identityId, serviceId, o); err != nil { + panic(err) + } + } + skip += len(children) + if len(children) == 0 { + done = true + } + } + + if skip == 0 { + serviceName, err := mapIdToName("services", serviceId) + if err != nil { + return err + } + _, err = fmt.Fprintf(o.Out, "ERROR: %v %v\n\n", serviceName, "\n - Service is not accessible by any identities. Adjust service policies.") + return err + } + + return nil +} + +func runPolicyAdvisorForIdentityAndService(identityId, serviceId string, o *policyAdvisorOptions) error { + result, err := util.EdgeControllerList("identities/"+identityId+"/policy-advice/"+serviceId, nil, o.OutputJSONResponse, o.Out) + if err != nil || o.OutputJSONResponse { + return err + } + + identityName, _ := result.S("data", "identity", "name").Data().(string) + serviceName, _ := result.S("data", "service", "name").Data().(string) + isBindAllowed, _ := result.S("data", "isBindAllowed").Data().(bool) + isDialAllowed, _ := result.S("data", "isDialAllowed").Data().(bool) + identityRouterCount, _ := result.S("data", "identityRouterCount").Data().(float64) + serviceRouterCount, _ := result.S("data", "serviceRouterCount").Data().(float64) + + commonRouters, err := result.S("data", "commonRouters").Children() + if err != nil && err != gabs.ErrNotObjOrArray { + return err + } + + commonCount := len(commonRouters) + onlineCount := 0 + for _, commonRouter := range commonRouters { + isOnline := commonRouter.S("isOnline").Data().(bool) + if isOnline { + onlineCount++ + } + } + + status := "OKAY " + detail := "" + if !isBindAllowed && !isDialAllowed { + status = "ERROR" + detail += "\n - No access to service. Adjust service policies." + } + + if identityRouterCount < 1 { + status = "ERROR" + detail += "\n - Identity has no edge routers assigned. Adjust edge router policies." + } + + if serviceRouterCount < 1 { + status = "ERROR" + detail += "\n - Service has no edge routers assigned. Adjust service edge router policies." + } + + if identityRouterCount > 0 && serviceRouterCount > 0 { + if commonCount < 1 { + status = "ERROR" + detail += "\n - Identity and services have no edge routers in common. Adjust edge router policies and/or service edge router policies." + } else if onlineCount < 1 { + status = "ERROR" + detail += "\n - Common edge routers are all off-line. Bring routers back on-line or adjust edge router policies and/or service edge router policies to increase common router pool." + } + } + + dialStatus := "Y" + if !isDialAllowed { + dialStatus = "N" + } + + bindStatus := "Y" + if !isBindAllowed { + bindStatus = "N" + } + + _, err = fmt.Fprintf(o.Out, "%v: %v (%v) -> %v (%v) Common Routers: (%v/%v) Dial: %v Bind: %v %v\n\n", + status, identityName, identityRouterCount, serviceName, serviceRouterCount, onlineCount, commonCount, dialStatus, bindStatus, detail) + + return err +} diff --git a/ziti/cmd/ziti/cmd/edge_controller/root.go b/ziti/cmd/ziti/cmd/edge_controller/root.go index bb63cd951..8ebb474f1 100644 --- a/ziti/cmd/ziti/cmd/edge_controller/root.go +++ b/ziti/cmd/ziti/cmd/edge_controller/root.go @@ -48,6 +48,7 @@ func newCmdEdgeController(f cmdutil.Factory, out io.Writer, errOut io.Writer) *c cmd.AddCommand(newListCmd(f, out, errOut)) cmd.AddCommand(newUpdateCmd(f, out, errOut)) cmd.AddCommand(newVersionCmd(f, out, errOut)) + cmd.AddCommand(newPolicyAdivsorCmd(f, out, errOut)) return cmd }