Skip to content

Commit

Permalink
Refactor webhook to use the new injection lib
Browse files Browse the repository at this point in the history
For testing purposes the GlobalConfig and ProxyConfig are currently
hard-coded.

Ref #1748 and #2289

Signed-off-by: Alejandro Pedraza <[email protected]>
  • Loading branch information
alpeb committed Mar 4, 2019
1 parent cdc815e commit 04912d3
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 219 deletions.
2 changes: 1 addition & 1 deletion cli/cmd/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func uninjectAndInject(inputs []io.Reader, errWriter, outWriter io.Writer, globa
}

func (resourceTransformerInject) transform(bytes []byte, globalConfig *pb.GlobalConfig, proxyConfig *pb.ProxyConfig) ([]byte, []inject.Report, error) {
conf, err := inject.NewResourceConfig(bytes)
conf, err := inject.NewResourceConfig(bytes, nil)
if err != nil {
return bytes, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/uninject.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ sub-folders, or coming from stdin.`,
}

func (resourceTransformerUninject) transform(bytes []byte, globalConfig *pb.GlobalConfig, proxyConfig *pb.ProxyConfig) ([]byte, []inject.Report, error) {
conf, err := inject.NewResourceConfig(bytes)
conf, err := inject.NewResourceConfig(bytes, nil)
if err != nil {
return bytes, nil, err
}
Expand Down
236 changes: 27 additions & 209 deletions controller/proxy-injector/webhook.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
package injector

import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"

"github.com/linkerd/linkerd2/pkg/healthcheck"
k8sPkg "github.com/linkerd/linkerd2/pkg/k8s"
pb "github.com/linkerd/linkerd2/controller/gen/config"
"github.com/linkerd/linkerd2/pkg/inject"
log "github.com/sirupsen/logrus"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
Expand Down Expand Up @@ -94,108 +87,35 @@ func (w *Webhook) decode(data []byte) (*admissionv1beta1.AdmissionReview, error)
}

func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admissionv1beta1.AdmissionResponse, error) {
var deployment appsv1.Deployment
if err := yaml.Unmarshal(request.Object.Raw, &deployment); err != nil {
return nil, err
}
log.Infof("working on %s/%s %s..", request.Kind.Version, strings.ToLower(request.Kind.Kind), deployment.ObjectMeta.Name)

ns := request.Namespace
if ns == "" {
ns = corev1.NamespaceDefault
}
log.Infof("resource namespace: %s", ns)

inject, err := w.shouldInject(ns, &deployment)
log.Debugf("request object bytes: %v", string(request.Object.Raw))
conf, err := inject.NewResourceConfig(request.Object.Raw, request)
if err != nil {
return nil, err
}

if !inject {
log.Infof("skipping deployment %s", deployment.GetName())
return &admissionv1beta1.AdmissionResponse{
UID: request.UID,
Allowed: true,
}, nil
}

identity := &k8sPkg.TLSIdentity{
Name: deployment.ObjectMeta.Name,
Kind: strings.ToLower(request.Kind.Kind),
Namespace: ns,
ControllerNamespace: w.controllerNamespace,
}

proxy, proxyInit, err := w.containersSpec(identity)
if err != nil {
return nil, err
}
log.Infof("proxy image: %s", proxy.Image)
log.Infof("proxy-init image: %s", proxyInit.Image)
log.Debugf("proxy container: %+v", proxy)
log.Debugf("init container: %+v", proxyInit)

patch := NewPatch()
patch.addContainer(proxy)

if !w.noInitContainer {
if len(deployment.Spec.Template.Spec.InitContainers) == 0 {
patch.addInitContainerRoot()
}
patch.addInitContainer(proxyInit)
}

if deployment.Spec.Template.Annotations == nil {
deployment.Spec.Template.Annotations = map[string]string{}
}
if w.tlsEnabled {
caBundle, tlsSecrets, err := w.volumesSpec(identity)
if err != nil {
return nil, err
}
log.Debugf("ca bundle volume: %+v", caBundle)
log.Debugf("tls secrets volume: %+v", tlsSecrets)

if len(deployment.Spec.Template.Spec.Volumes) == 0 {
patch.addVolumeRoot()
}
patch.addVolume(caBundle)
patch.addVolume(tlsSecrets)
deployment.Spec.Template.Annotations[k8sPkg.IdentityModeAnnotation] = k8sPkg.IdentityModeOptional
} else {
deployment.Spec.Template.Annotations[k8sPkg.IdentityModeAnnotation] = k8sPkg.IdentityModeDisabled
}

if deployment.Spec.Template.Labels == nil {
deployment.Spec.Template.Labels = map[string]string{}
}

deployment.Spec.Template.Labels[k8sPkg.ControllerNSLabel] = w.controllerNamespace
deployment.Spec.Template.Labels[k8sPkg.ProxyDeploymentLabel] = deployment.ObjectMeta.Name
patch.addPodLabels(deployment.Spec.Template.Labels)

if deployment.Labels == nil {
deployment.Labels = map[string]string{}
}

deployment.Labels[k8sPkg.ControllerNSLabel] = w.controllerNamespace
deployment.Labels[k8sPkg.ProxyDeploymentLabel] = deployment.ObjectMeta.Name
patch.addDeploymentLabels(deployment.Labels)

image := strings.Split(proxy.Image, ":")
var imageTag string

if len(image) < 2 {
imageTag = "latest"
} else {
imageTag = image[1]
}

deployment.Spec.Template.Annotations[k8sPkg.CreatedByAnnotation] = fmt.Sprintf("linkerd/proxy-injector %s", imageTag)
deployment.Spec.Template.Annotations[k8sPkg.ProxyVersionAnnotation] = imageTag
patch.addPodAnnotations(deployment.Spec.Template.Annotations)

patchJSON, err := json.Marshal(patch.patchOps)
// TODO: Fetch GlobalConfig and ProxyConfig from the ConfigMap/API
globalConfig := &pb.GlobalConfig{
LinkerdNamespace: "linkerd",
CniEnabled: false,
IdentityContext: nil,
}
proxyConfig := &pb.ProxyConfig{
ProxyImage: &pb.Image{ImageName: "gcr.io/linkerd-io/proxy", PullPolicy: "IfNotPresent", Registry: "gcr.io/linkerd-io"},
ProxyInitImage: &pb.Image{ImageName: "gcr.io/linkerd-io/proxy-init", PullPolicy: "IfNotPresent", Registry: "gcr.io/linkerd-io"},
ApiPort: &pb.Port{Port: 8086},
ControlPort: &pb.Port{Port: 4190},
IgnoreInboundPorts: []*pb.Port{},
IgnoreOutboundPorts: []*pb.Port{},
InboundPort: &pb.Port{Port: 4143},
MetricsPort: &pb.Port{Port: 4191},
OutboundPort: &pb.Port{Port: 4140},
Resource: &pb.ResourceRequirements{RequestCpu: "100m", RequestMemory: "200Mi"},
ProxyUid: 2102,
LogLevel: &pb.LogLevel{Level: "warn,linkerd2_proxy=info"},
DisableExternalProfiles: false,
}

patchJSON, _, err := conf.Transform(globalConfig, proxyConfig)
if err != nil {
return nil, err
}
Expand All @@ -210,105 +130,3 @@ func (w *Webhook) inject(request *admissionv1beta1.AdmissionRequest) (*admission

return admissionResponse, nil
}

// shouldInject determines whether or not the given deployment should be
// injected. A deployment should be injected if it does not already contain
// any known sidecars, and:
// - the deployment's namespace has the linkerd.io/inject annotation set to
// "enabled", and the deployment's pod spec does not have the
// linkerd.io/inject annotation set to "disabled"; or
// - the deployment's pod spec has the linkerd.io/inject annotation set to
// "enabled"
func (w *Webhook) shouldInject(ns string, deployment *appsv1.Deployment) (bool, error) {
if healthcheck.HasExistingSidecars(&deployment.Spec.Template.Spec) {
return false, nil
}

namespace, err := w.client.CoreV1().Namespaces().Get(ns, metav1.GetOptions{})
if err != nil {
return false, err
}

nsAnnotation := namespace.GetAnnotations()[k8sPkg.ProxyInjectAnnotation]
podAnnotation := deployment.Spec.Template.GetAnnotations()[k8sPkg.ProxyInjectAnnotation]

if nsAnnotation == k8sPkg.ProxyInjectEnabled && podAnnotation != k8sPkg.ProxyInjectDisabled {
return true, nil
}

return podAnnotation == k8sPkg.ProxyInjectEnabled, nil
}

func (w *Webhook) containersSpec(identity *k8sPkg.TLSIdentity) (*corev1.Container, *corev1.Container, error) {
proxySpec, err := ioutil.ReadFile(w.resources.FileProxySpec)
if err != nil {
return nil, nil, err
}

var proxy corev1.Container
if err := yaml.Unmarshal(proxySpec, &proxy); err != nil {
return nil, nil, err
}

for index, env := range proxy.Env {
if env.Name == envVarKeyProxyTLSPodIdentity {
proxy.Env[index].Value = identity.ToDNSName()
} else if env.Name == envVarKeyProxyTLSControllerIdentity {
proxy.Env[index].Value = identity.ToControllerIdentity().ToDNSName()
} else if env.Name == envVarKeyProxyID {
proxy.Env[index].Value = identity.ToDNSName()
}
}

proxyInitSpec, err := ioutil.ReadFile(w.resources.FileProxyInitSpec)
if err != nil {
return nil, nil, err
}

var proxyInit corev1.Container
if err := yaml.Unmarshal(proxyInitSpec, &proxyInit); err != nil {
return nil, nil, err
}

return &proxy, &proxyInit, nil
}

func (w *Webhook) volumesSpec(identity *k8sPkg.TLSIdentity) (*corev1.Volume, *corev1.Volume, error) {
trustAnchorVolumeSpec, err := ioutil.ReadFile(w.resources.FileTLSTrustAnchorVolumeSpec)
if err != nil {
return nil, nil, err
}

var trustAnchors corev1.Volume
if err := yaml.Unmarshal(trustAnchorVolumeSpec, &trustAnchors); err != nil {
return nil, nil, err
}

tlsVolumeSpec, err := ioutil.ReadFile(w.resources.FileTLSIdentityVolumeSpec)
if err != nil {
return nil, nil, err
}

var linkerdSecrets corev1.Volume
if err := yaml.Unmarshal(tlsVolumeSpec, &linkerdSecrets); err != nil {
return nil, nil, err
}
linkerdSecrets.VolumeSource.Secret.SecretName = identity.ToSecretName()

return &trustAnchors, &linkerdSecrets, nil
}

// WebhookResources contain paths to all the needed file resources.
type WebhookResources struct {
// FileProxySpec is the path to the proxy spec.
FileProxySpec string

// FileProxyInitSpec is the path to the proxy-init spec.
FileProxyInitSpec string

// FileTLSTrustAnchorVolumeSpec is the path to the trust anchor volume spec.
FileTLSTrustAnchorVolumeSpec string

// FileTLSIdentityVolumeSpec is the path to the TLS identity volume spec.
FileTLSIdentityVolumeSpec string
}
22 changes: 14 additions & 8 deletions pkg/inject/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
appsV1 "k8s.io/api/apps/v1"
batchV1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -44,6 +45,7 @@ type objMeta struct {
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
}

// ResourceConfig contains both the raw (bytes) and parsed information for a given workload
type ResourceConfig struct {
bytes []byte
obj interface{}
Expand All @@ -56,19 +58,23 @@ type ResourceConfig struct {
}

// NewResourceConfig creates and initializes a ResourceConfig
func NewResourceConfig(bytes []byte) (*ResourceConfig, error) {
func NewResourceConfig(bytes []byte, request *admissionv1beta1.AdmissionRequest) (*ResourceConfig, error) {
conf := &ResourceConfig{
bytes: bytes,
k8sLabels: map[string]string{},
}
// Unmarshal the object enough to read the Kind field
if err := yaml.Unmarshal(bytes, &conf.meta); err != nil {
return nil, err
}
// retrieve the `metadata/name` field for reporting
if err := yaml.Unmarshal(bytes, &conf.om); err != nil {
return nil, err
if request != nil {
conf.meta = metaV1.TypeMeta{Kind: request.Kind.Kind}
conf.om = objMeta{metaV1.ObjectMeta{Name: request.Name, Namespace: request.Namespace}}
} else {
if err := yaml.Unmarshal(bytes, &conf.meta); err != nil {
return nil, err
}
if err := yaml.Unmarshal(bytes, &conf.om); err != nil {
return nil, err
}
}

return conf, nil
}

Expand Down

0 comments on commit 04912d3

Please sign in to comment.