Skip to content

Commit

Permalink
Complete --from and --to fields (#20)
Browse files Browse the repository at this point in the history
* Complete --from and --to fields
* Move proxy-related helper functions to a dedicated file

Signed-off-by: Daichi Sakaue <[email protected]>
  • Loading branch information
yokaze authored Dec 10, 2024
1 parent 995d2e4 commit c25a5f8
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 110 deletions.
42 changes: 0 additions & 42 deletions cmd/npv/app/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,48 +45,6 @@ func createK8sClients() (*kubernetes.Clientset, *dynamic.DynamicClient, error) {
return clientset, dynamicClient, nil
}

func createCiliumClient(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string) (*client.Client, error) {
endpoint, err := getProxyEndpoint(ctx, clientset, namespace, name)
if err != nil {
return nil, err
}

if cached, ok := cachedCiliumClients[endpoint]; ok {
return cached, nil
}

ciliumClient, err := client.NewClient(endpoint)
if err != nil {
return nil, err
}
cachedCiliumClients[endpoint] = ciliumClient

return ciliumClient, err
}

func getProxyEndpoint(ctx context.Context, c *kubernetes.Clientset, namespace, name string) (string, error) {
targetPod, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", err
}
targetNode := targetPod.Spec.NodeName

pods, err := c.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{
FieldSelector: "spec.nodeName=" + targetNode,
LabelSelector: rootOptions.proxySelector,
})
if err != nil {
return "", err
}
if num := len(pods.Items); num != 1 {
err := fmt.Errorf("failed to find cilium-agent-proxy. found %d pods", num)
return "", err
}

podIP := pods.Items[0].Status.PodIP
return fmt.Sprintf("http://%s:%d", podIP, rootOptions.proxyPort), nil
}

func getPodEndpointID(ctx context.Context, d *dynamic.DynamicClient, namespace, name string) (int64, error) {
ep, err := d.Resource(gvrEndpoint).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
Expand Down
36 changes: 36 additions & 0 deletions cmd/npv/app/helper_completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"context"
"strings"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -46,3 +47,38 @@ func completePods(cmd *cobra.Command, args []string, toComplete string) (ret []s
}
return
}

func completeNamespacePods(cmd *cobra.Command, args []string, toComplete string) (ret []string, directive cobra.ShellCompDirective) {
ret = make([]string, 0)
directive = cobra.ShellCompDirectiveNoFileComp

clientset, _, err := createK8sClients()
if err != nil {
return
}

li := strings.Split(toComplete, "/")
switch len(li) {
case 2: // namespace already filled
pods, err := listRelevantPods(context.Background(), clientset, li[0])
if err != nil {
return
}
for _, p := range pods {
ret = append(ret, li[0]+"/"+p.Name)
}
return

default:
nss, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
return
}

for _, ns := range nss.Items {
ret = append(ret, ns.Name+"/")
}
directive = cobra.ShellCompDirectiveNoSpace
return
}
}
123 changes: 123 additions & 0 deletions cmd/npv/app/helper_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package app

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/cilium/cilium/pkg/client"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)

func getProxyEndpoint(ctx context.Context, c *kubernetes.Clientset, namespace, name string) (string, error) {
targetPod, err := c.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", err
}
targetNode := targetPod.Spec.NodeName

pods, err := c.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{
FieldSelector: "spec.nodeName=" + targetNode,
LabelSelector: rootOptions.proxySelector,
})
if err != nil {
return "", err
}
if num := len(pods.Items); num != 1 {
err := fmt.Errorf("failed to find cilium-agent-proxy. found %d pods", num)
return "", err
}

podIP := pods.Items[0].Status.PodIP
return fmt.Sprintf("http://%s:%d", podIP, rootOptions.proxyPort), nil
}

func createCiliumClient(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string) (*client.Client, error) {
endpoint, err := getProxyEndpoint(ctx, clientset, namespace, name)
if err != nil {
return nil, err
}

if cached, ok := cachedCiliumClients[endpoint]; ok {
return cached, nil
}

ciliumClient, err := client.NewClient(endpoint)
if err != nil {
return nil, err
}
cachedCiliumClients[endpoint] = ciliumClient

return ciliumClient, err
}

type policyEntryKey struct {
Identity int `json:"Identity"`
Direction int `json:"TrafficDirection"`
Protocol int `json:"Nexthdr"`
BigPort int `json:"DestPortNetwork"` // big endian
}

func (p policyEntryKey) Port() int {
return ((p.BigPort & 0xFF) << 8) + ((p.BigPort & 0xFF00) >> 8)
}

// For the meanings of the flags, see:
// https://github.com/cilium/cilium/blob/v1.16.3/bpf/lib/common.h#L394
type policyEntry struct {
Flags int `json:"Flags"`
Packets int `json:"Packets"`
Bytes int `json:"Bytes"`
Key policyEntryKey `json:"Key"`
}

func (p policyEntry) IsDenyRule() bool {
return (p.Flags & 1) > 0
}

func (p policyEntry) IsEgressRule() bool {
return p.Key.Direction > 0
}

func (p policyEntry) IsWildcardProtocol() bool {
return (p.Flags & 2) > 0
}

func (p policyEntry) IsWildcardPort() bool {
return (p.Flags & 4) > 0
}

func queryPolicyMap(ctx context.Context, clientset *kubernetes.Clientset, dynamicClient *dynamic.DynamicClient, namespace, name string) ([]policyEntry, error) {
endpointID, err := getPodEndpointID(ctx, dynamicClient, namespace, name)
if err != nil {
return nil, fmt.Errorf("failed to get pod endpoint ID: %w", err)
}

url, err := getProxyEndpoint(ctx, clientset, namespace, name)
if err != nil {
return nil, fmt.Errorf("failed to get proxy endpoint: %w", err)
}

url = fmt.Sprintf("%s/policy/%d", url, endpointID)
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to request policy: %w", err)
}
defer resp.Body.Close()

data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}

policies := make([]policyEntry, 0)
if err = json.Unmarshal(data, &policies); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

return policies, nil
}
69 changes: 1 addition & 68 deletions cmd/npv/app/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package app

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"slices"
"strconv"
"strings"
Expand All @@ -16,8 +14,6 @@ import (
"github.com/spf13/cobra"
"golang.org/x/exp/rand"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
)

func init() {
Expand All @@ -36,38 +32,6 @@ var inspectCmd = &cobra.Command{
ValidArgsFunction: completePods,
}

type policyEntryKey struct {
Identity int `json:"Identity"`
Direction int `json:"TrafficDirection"`
Protocol int `json:"Nexthdr"`
BigPort int `json:"DestPortNetwork"` // big endian
}

// For the meanings of the flags, see:
// https://github.com/cilium/cilium/blob/v1.16.3/bpf/lib/common.h#L394
type policyEntry struct {
Flags int `json:"Flags"`
Packets int `json:"Packets"`
Bytes int `json:"Bytes"`
Key policyEntryKey `json:"Key"`
}

func (p policyEntry) IsDenyRule() bool {
return (p.Flags & 1) > 0
}

func (p policyEntry) IsEgressRule() bool {
return p.Key.Direction > 0
}

func (p policyEntry) IsWildcardProtocol() bool {
return (p.Flags & 2) > 0
}

func (p policyEntry) IsWildcardPort() bool {
return (p.Flags & 4) > 0
}

// This command aims to show the result of "cilium bpf policy get" from a remote pod.
// https://github.com/cilium/cilium/blob/v1.16.3/cilium-dbg/cmd/bpf_policy_get.go
type inspectEntry struct {
Expand All @@ -84,37 +48,6 @@ type inspectEntry struct {
Packets int `json:"packets"`
}

func queryPolicyMap(ctx context.Context, clientset *kubernetes.Clientset, dynamicClient *dynamic.DynamicClient, namespace, name string) ([]policyEntry, error) {
endpointID, err := getPodEndpointID(ctx, dynamicClient, namespace, name)
if err != nil {
return nil, fmt.Errorf("failed to get pod endpoint ID: %w", err)
}

url, err := getProxyEndpoint(ctx, clientset, namespace, name)
if err != nil {
return nil, fmt.Errorf("failed to get proxy endpoint: %w", err)
}

url = fmt.Sprintf("%s/policy/%d", url, endpointID)
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("failed to request policy: %w", err)
}
defer resp.Body.Close()

data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response: %w", err)
}

policies := make([]policyEntry, 0)
if err = json.Unmarshal(data, &policies); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}

return policies, nil
}

func runInspect(ctx context.Context, w io.Writer, name string) error {
clientset, dynamicClient, err := createK8sClients()
if err != nil {
Expand Down Expand Up @@ -204,7 +137,7 @@ func runInspect(ctx context.Context, w io.Writer, name string) error {
entry.WildcardProtocol = p.IsWildcardProtocol()
entry.WildcardPort = p.IsWildcardPort()
entry.Protocol = p.Key.Protocol
entry.Port = ((p.Key.BigPort & 0xFF) << 8) + ((p.Key.BigPort & 0xFF00) >> 8)
entry.Port = p.Key.Port()
entry.Bytes = p.Bytes
entry.Packets = p.Packets
arr[i] = entry
Expand Down
2 changes: 2 additions & 0 deletions cmd/npv/app/manifest_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func init() {
manifestGenerateCmd.Flags().BoolVar(&manifestGenerateOptions.deny, "deny", false, "generate deny rule")
manifestGenerateCmd.Flags().StringVar(&manifestGenerateOptions.from, "from", "", "egress pod")
manifestGenerateCmd.Flags().StringVar(&manifestGenerateOptions.to, "to", "", "ingress pod")
manifestGenerateCmd.RegisterFlagCompletionFunc("from", completeNamespacePods)
manifestGenerateCmd.RegisterFlagCompletionFunc("to", completeNamespacePods)
manifestCmd.AddCommand(manifestGenerateCmd)
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/npv/app/manifest_range.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var manifestRangeOptions struct {
func init() {
manifestRangeCmd.Flags().StringVar(&manifestRangeOptions.from, "from", "", "egress pod")
manifestRangeCmd.Flags().StringVar(&manifestRangeOptions.to, "to", "", "ingress pod")
manifestRangeCmd.RegisterFlagCompletionFunc("from", completeNamespacePods)
manifestRangeCmd.RegisterFlagCompletionFunc("to", completeNamespacePods)
manifestCmd.AddCommand(manifestRangeCmd)
}

Expand Down

0 comments on commit c25a5f8

Please sign in to comment.