// Package admit is the protocol between the weave CNI plugin and weave-npc's egress agent: at CNI ADD the // plugin tells the agent which pod got which IP, and the agent answers once the pod's egress rules are in // place, so an EgressService member's first packet already leaves from the Service's address (or not at all). // // It depends on the standard library only: the plugin binary must not grow a Kubernetes client. package admit import ( "bytes" "context" "encoding/json" "fmt" "io" "net" "net/http" "strings" "time" ) // SocketPath is where the agent listens (a hostPath directory shared with the host, where the plugin runs). const SocketPath = "/run/weave-npc/egress.sock" // Request is one pod's admission: the IP the plugin just allocated to it. type Request struct { Namespace string `json:"namespace"` Name string `json:"name"` IP string `json:"ip"` } // Response says whether the pod is an EgressService member (its rules are installed either way when 200). type Response struct { Member bool `json:"member"` } // Timeout bounds one admission (the agent may have to fetch the pod from the API server). const Timeout = 20 * time.Second // PodFromArgs reads the pod's namespace and name from CNI_ARGS ("K8S_POD_NAMESPACE=..;K8S_POD_NAME=.."). func PodFromArgs(args string) (namespace, name string) { for _, kv := range strings.Split(args, ";") { k, v, ok := strings.Cut(kv, "=") if !ok { continue } switch k { case "K8S_POD_NAMESPACE": namespace = v case "K8S_POD_NAME": name = v } } return namespace, name } // Admit asks the agent at socket to admit req, and returns only once the agent has installed the pod's rules. func Admit(ctx context.Context, socket string, req Request) (Response, error) { var resp Response body, err := json.Marshal(req) if err != nil { return resp, err } client := &http.Client{Transport: &http.Transport{ DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { var d net.Dialer return d.DialContext(ctx, "unix", socket) }, }} ctx, cancel := context.WithTimeout(ctx, Timeout) defer cancel() hreq, err := http.NewRequestWithContext(ctx, http.MethodPost, "http://weave-npc/admit", bytes.NewReader(body)) if err != nil { return resp, err } hreq.Header.Set("Content-Type", "application/json") hresp, err := client.Do(hreq) if err != nil { return resp, fmt.Errorf("egress admission (weave-npc at %s): %v", socket, err) } defer hresp.Body.Close() out, _ := io.ReadAll(io.LimitReader(hresp.Body, 1<<16)) if hresp.StatusCode != http.StatusOK { return resp, fmt.Errorf("egress admission refused (%s): %s", hresp.Status, strings.TrimSpace(string(out))) } if err := json.Unmarshal(out, &resp); err != nil { return resp, fmt.Errorf("egress admission: bad answer: %v", err) } return resp, nil }