linkerd2/cni-plugin/main.go

237 lines
7.5 KiB
Go

// Copyright 2017 CNI authors
// Modifications copyright (c) Linkerd 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.
// This file was inspired by:
// 1) https://github.com/istio/cni/blob/c63a509539b5ed165a6617548c31b686f13c2133/cmd/istio-cni/main.go
package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/proxy-init/cmd"
"github.com/linkerd/linkerd2/proxy-init/iptables"
"github.com/projectcalico/libcalico-go/lib/logutils"
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ProxyInit is the configuration for the proxy-init binary
type ProxyInit struct {
IncomingProxyPort int `json:"incoming-proxy-port"`
OutgoingProxyPort int `json:"outgoing-proxy-port"`
ProxyUID int `json:"proxy-uid"`
PortsToRedirect []int `json:"ports-to-redirect"`
InboundPortsToIgnore []int `json:"inbound-ports-to-ignore"`
OutboundPortsToIgnore []int `json:"outbound-ports-to-ignore"`
Simulate bool `json:"simulate"`
}
// Kubernetes a K8s specific struct to hold config
type Kubernetes struct {
K8sAPIRoot string `json:"k8s_api_root"`
Kubeconfig string `json:"kubeconfig"`
}
// K8sArgs is the valid CNI_ARGS used for Kubernetes
// The field names need to match exact keys in kubelet args for unmarshalling
type K8sArgs struct {
types.CommonArgs
K8sPodName types.UnmarshallableString
K8sPodNamespace types.UnmarshallableString
}
// PluginConf is whatever JSON is passed via stdin.
type PluginConf struct {
types.NetConf
// This is the previous result, when called in the context of a chained
// plugin. We will just pass any prevResult through.
RawPrevResult *map[string]interface{} `json:"prevResult"`
PrevResult *current.Result `json:"-"`
LogLevel string `json:"log_level"`
ProxyInit ProxyInit `json:"linkerd"`
Kubernetes Kubernetes `json:"kubernetes"`
}
func main() {
// Set up logging formatting.
logrus.SetFormatter(&logutils.Formatter{})
// Install a hook that adds file/line no information.
logrus.AddHook(&logutils.ContextHook{})
skel.PluginMain(cmdAdd, cmdDel, version.All)
}
func configureLogging(logLevel string) {
if strings.EqualFold(logLevel, "debug") {
logrus.SetLevel(logrus.DebugLevel)
} else if strings.EqualFold(logLevel, "info") {
logrus.SetLevel(logrus.InfoLevel)
} else {
// Default level
logrus.SetLevel(logrus.WarnLevel)
}
// Must log to Stderr because the CNI runtime uses Stdout as its state
logrus.SetOutput(os.Stderr)
}
// parseConfig parses the supplied configuration (and prevResult) from stdin.
func parseConfig(stdin []byte) (*PluginConf, error) {
conf := PluginConf{}
logrus.Debugf("linkerd-cni: stdin to plugin: %v", string(stdin))
if err := json.Unmarshal(stdin, &conf); err != nil {
return nil, fmt.Errorf("linkerd-cni: failed to parse network configuration: %v", err)
}
if conf.RawPrevResult != nil {
resultBytes, err := json.Marshal(conf.RawPrevResult)
if err != nil {
return nil, fmt.Errorf("linkerd-cni: could not serialize prevResult: %v", err)
}
res, err := version.NewResult(conf.CNIVersion, resultBytes)
if err != nil {
return nil, fmt.Errorf("linkerd-cni: could not parse prevResult: %v", err)
}
conf.RawPrevResult = nil
conf.PrevResult, err = current.NewResultFromResult(res)
if err != nil {
return nil, fmt.Errorf("linkerd-cni: could not convert result to current version: %v", err)
}
logrus.Debugf("linkerd-cni: prevResult: %v", conf.PrevResult)
}
return &conf, nil
}
// cmdAdd is called by the CNI runtime for ADD requests
func cmdAdd(args *skel.CmdArgs) error {
logrus.Debug("linkerd-cni: cmdAdd, parsing config")
conf, err := parseConfig(args.StdinData)
if err != nil {
return err
}
configureLogging(conf.LogLevel)
if conf.PrevResult != nil {
logrus.WithFields(logrus.Fields{
"version": conf.CNIVersion,
"prevResult": conf.PrevResult,
}).Debug("linkerd-cni: cmdAdd, config parsed")
} else {
logrus.WithFields(logrus.Fields{
"version": conf.CNIVersion,
}).Debug("linkerd-cni: cmdAdd, config parsed")
}
// Determine if running under k8s by checking the CNI args
k8sArgs := K8sArgs{}
args.Args = strings.Replace(args.Args, "K8S_POD_NAMESPACE", "K8sPodNamespace", 1)
args.Args = strings.Replace(args.Args, "K8S_POD_NAME", "K8sPodName", 1)
if err := types.LoadArgs(args.Args, &k8sArgs); err != nil {
return err
}
namespace := string(k8sArgs.K8sPodNamespace)
podName := string(k8sArgs.K8sPodName)
logEntry := logrus.WithFields(logrus.Fields{
"ContainerID": args.ContainerID,
"Pod": podName,
"Namespace": namespace,
})
if namespace != "" && podName != "" {
client, err := k8s.NewAPI(conf.Kubernetes.Kubeconfig, "linkerd-cni-context", 0)
if err != nil {
return err
}
pod, err := client.CoreV1().Pods(namespace).Get(podName, metav1.GetOptions{})
if err != nil {
return err
}
containsLinkerdProxy := false
for _, container := range pod.Spec.Containers {
if container.Name == k8s.ProxyContainerName {
containsLinkerdProxy = true
break
}
}
containsInitContainer := false
for _, container := range pod.Spec.InitContainers {
if container.Name == k8s.InitContainerName {
containsInitContainer = true
break
}
}
if containsLinkerdProxy && !containsInitContainer {
logEntry.Debug("linkerd-cni: setting up iptables firewall")
options := cmd.RootOptions{
IncomingProxyPort: conf.ProxyInit.IncomingProxyPort,
OutgoingProxyPort: conf.ProxyInit.OutgoingProxyPort,
ProxyUserID: conf.ProxyInit.ProxyUID,
PortsToRedirect: conf.ProxyInit.PortsToRedirect,
InboundPortsToIgnore: conf.ProxyInit.InboundPortsToIgnore,
OutboundPortsToIgnore: conf.ProxyInit.OutboundPortsToIgnore,
SimulateOnly: conf.ProxyInit.Simulate,
NetNs: args.Netns,
}
firewallConfiguration, err := cmd.BuildFirewallConfiguration(&options)
if err != nil {
logEntry.Errorf("linkerd-cni: could not create a Firewall Configuration from the options: %v", options)
return err
}
iptables.ConfigureFirewall(*firewallConfiguration)
} else {
if containsInitContainer {
logEntry.Debug("linkerd-cni: linkerd-init initContainer is present, skipping.")
} else {
logEntry.Debug("linkerd-cni: linkerd-proxy is not present, skipping.")
}
}
} else {
logEntry.Debug("linkerd-cni: no Kubernetes namespace or pod name found, skipping.")
}
logrus.Debug("linkerd-cni: plugin is finished")
if conf.PrevResult != nil {
// Pass through the prevResult for the next plugin
return types.PrintResult(conf.PrevResult, conf.CNIVersion)
}
logrus.Debug("linkerd-cni: no previous result to pass through, emptying stdout")
return nil
}
// cmdDel is called for DELETE requests
func cmdDel(args *skel.CmdArgs) error {
logrus.Debug("linkerd-cni: cmdDel not implemented")
return nil
}