Skip to content

Commit

Permalink
istioctl ztunnel: add connections command (#50541)
Browse files Browse the repository at this point in the history
* istioctl ztunnel: add connections command

* fix lint

* Include remote service
  • Loading branch information
howardjohn committed Apr 22, 2024
1 parent c9bd035 commit 291bf43
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 4 deletions.
46 changes: 42 additions & 4 deletions istioctl/pkg/writer/ztunnel/configdump/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ type ZtunnelPolicy struct {
}

type ZtunnelDump struct {
Workloads map[string]*ZtunnelWorkload `json:"workloads"`
Services map[string]*ZtunnelService `json:"services"`
Policies map[string]*ZtunnelPolicy `json:"policies"`
Certificates []*CertsDump `json:"certificates"`
Workloads map[string]*ZtunnelWorkload `json:"workloads"`
Services map[string]*ZtunnelService `json:"services"`
Policies map[string]*ZtunnelPolicy `json:"policies"`
Certificates []*CertsDump `json:"certificates"`
WorkloadState map[string]WorkloadState `json:"workloadState"`
}

type CertsDump struct {
Expand All @@ -105,3 +106,40 @@ type Cert struct {
ValidFrom string `json:"validFrom"`
ExpirationTime string `json:"expirationTime"`
}

type WorkloadState struct {
State string `json:"state,omitempty"`
Connections WorkloadConnections `json:"connections,omitempty"`
Info WorkloadInfo `json:"info"`
}

type WorkloadConnections struct {
Inbound []InboundConnection `json:"inbound"`
Outbound []OutboundConnection `json:"outbound"`
}

type WorkloadInfo struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
TrustDomain string `json:"trustDomain"`
ServiceAccount string `json:"serviceAccount"`
}

type InboundConnection struct {
Src string `json:"src"`
OriginalDst string `json:"originalDst"`
ActualDst string `json:"actualDst"`
}

type OutboundConnection struct {
Src string `json:"src"`
OriginalDst string `json:"originalDst"`
ActualDst string `json:"actualDst"`
}

type WorkloadConnection struct {
Src string `json:"src"`
Dst string `json:"dst"`
SrcIdentity string `json:"src_identity"`
DstNetwork string `json:"dst_network"`
}
115 changes: 115 additions & 0 deletions istioctl/pkg/writer/ztunnel/configdump/connections.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright Istio Authors
//
// 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
//
// http://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 configdump

import (
"cmp"
"encoding/json"
"fmt"
"net"
"strings"

"sigs.k8s.io/yaml"

"istio.io/istio/pkg/maps"
"istio.io/istio/pkg/slices"
)

type ConnectionsFilter struct {
Namespace string
Direction string
Raw bool
}

func (c *ConfigWriter) PrintConnectionsDump(filter ConnectionsFilter, outputFormat string) error {
d := c.ztunnelDump
workloads := maps.Values(d.WorkloadState)
workloads = slices.SortFunc(workloads, func(a, b WorkloadState) int {
if r := cmp.Compare(a.Info.Namespace, b.Info.Namespace); r != 0 {
return r
}
return cmp.Compare(a.Info.Namespace, b.Info.Namespace)
})
workloads = slices.FilterInPlace(workloads, func(state WorkloadState) bool {
if filter.Namespace != "" && filter.Namespace != state.Info.Namespace {
return false
}
return true
})
out, err := json.MarshalIndent(workloads, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal workloads: %v", err)
}
if outputFormat == "yaml" {
if out, err = yaml.JSONToYAML(out); err != nil {
return err
}
}
fmt.Fprintln(c.Stdout, string(out))
return nil
}

func (c *ConfigWriter) PrintConnectionsSummary(filter ConnectionsFilter) error {
w := c.tabwriter()
d := c.ztunnelDump
serviceNames := map[string]string{}
workloadNames := map[string]string{}
for netIP, s := range d.Services {
_, ip, _ := strings.Cut(netIP, "/")
serviceNames[ip] = s.Hostname
}
for netIP, s := range d.Workloads {
_, ip, _ := strings.Cut(netIP, "/")
workloadNames[ip] = s.Name + "." + s.Namespace
}
lookupIP := func(addr string) string {
if filter.Raw {
return addr
}
ip, port, _ := net.SplitHostPort(addr)
if s, f := serviceNames[ip]; f {
return net.JoinHostPort(s, port)
}
if w, f := workloadNames[ip]; f {
return net.JoinHostPort(w, port)
}
return addr
}
fmt.Fprintln(w, "WORKLOAD\tDIRECTION\tLOCAL\tREMOTE\tREMOTE TARGET")
workloads := maps.Values(d.WorkloadState)
workloads = slices.SortFunc(workloads, func(a, b WorkloadState) int {
if r := cmp.Compare(a.Info.Namespace, b.Info.Namespace); r != 0 {
return r
}
return cmp.Compare(a.Info.Namespace, b.Info.Namespace)
})
for _, wl := range workloads {
if filter.Namespace != "" && filter.Namespace != wl.Info.Namespace {
continue
}
name := fmt.Sprintf("%s.%s", wl.Info.Name, wl.Info.Namespace)
if filter.Direction != "outbound" {
for _, c := range wl.Connections.Inbound {
fmt.Fprintf(w, "%v\tInbound\t%v\t%v\t%v\n", name, lookupIP(c.ActualDst), lookupIP(c.Src), c.OriginalDst)
}
}
if filter.Direction != "inbound" {
for _, c := range wl.Connections.Outbound {
fmt.Fprintf(w, "%v\tOutbound\t%v\t%v\t%v\n", name, lookupIP(c.Src), lookupIP(c.ActualDst), lookupIP(c.OriginalDst))
}
}
}
return w.Flush()
}
48 changes: 48 additions & 0 deletions istioctl/pkg/ztunnelconfig/ztunnelconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func ZtunnelConfig(ctx cli.Context) *cobra.Command {
configCmd.AddCommand(servicesCmd(ctx))
configCmd.AddCommand(policiesCmd(ctx))
configCmd.AddCommand(allCmd(ctx))
configCmd.AddCommand(connectionsCmd(ctx))

return configCmd
}
Expand Down Expand Up @@ -275,6 +276,53 @@ func workloadConfigCmd(ctx cli.Context) *cobra.Command {
return cmd
}

func connectionsCmd(ctx cli.Context) *cobra.Command {
var workloadsNamespace string
var direction string
var raw bool

common := new(commonFlags)
cmd := &cobra.Command{
Use: "connections [<type>/]<name>[.<namespace>]",
Hidden: true,
Short: "Retrieves connections for the specified Ztunnel pod.",
Long: `Retrieve information about connections for the Ztunnel instance.`,
Example: ` # Retrieve summary about connections for the ztunnel on a specific node.
istioctl ztunnel-config connections --node ambient-worker
# Retrieve summary of connections for a given Ztunnel instance.
istioctl ztunnel-config workload <ztunnel-name[.namespace]>
`,
Aliases: []string{"cons"},
Args: common.validateArgs,
RunE: runConfigDump(ctx, common, func(cw *ztunnelDump.ConfigWriter) error {
filter := ztunnelDump.ConnectionsFilter{
Namespace: workloadsNamespace,
Direction: direction,
Raw: raw,
}

switch common.outputFormat {
case summaryOutput:
return cw.PrintConnectionsSummary(filter)
case jsonOutput, yamlOutput:
return cw.PrintConnectionsDump(filter, common.outputFormat)
default:
return fmt.Errorf("output format %q not supported", common.outputFormat)
}
}),
ValidArgsFunction: completion.ValidPodsNameArgs(ctx),
}

common.attach(cmd)
cmd.PersistentFlags().StringVar(&direction, "direction", "", "Filter workloads by direction (inbound or outbound)")
cmd.PersistentFlags().BoolVar(&raw, "raw", false, "If set, show IP addresses instead of names")
cmd.PersistentFlags().StringVar(&workloadsNamespace, "workload-namespace", "",
"Filter workloads by namespace field")

return cmd
}

// Level is an enumeration of all supported log levels.
type Level int

Expand Down

0 comments on commit 291bf43

Please sign in to comment.