Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complete --from and --to fields #20

Merged
merged 2 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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