1 package injector
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/linkerd/linkerd2/controller/k8s"
10 "github.com/linkerd/linkerd2/controller/webhook"
11 "github.com/linkerd/linkerd2/pkg/config"
12 "github.com/linkerd/linkerd2/pkg/inject"
13 pkgK8s "github.com/linkerd/linkerd2/pkg/k8s"
14 "github.com/linkerd/linkerd2/pkg/version"
15 "github.com/sirupsen/logrus"
16 admissionv1beta1 "k8s.io/api/admission/v1beta1"
17 v1 "k8s.io/api/core/v1"
18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19 "k8s.io/apimachinery/pkg/labels"
20 "k8s.io/client-go/tools/record"
21 )
22
23 const (
24 eventTypeSkipped = "InjectionSkipped"
25 eventTypeInjected = "Injected"
26 )
27
28 var log = logrus.New()
29
30
31
32
33 func Inject(linkerdNamespace string) webhook.Handler {
34 return func(
35 ctx context.Context,
36 api *k8s.MetadataAPI,
37 request *admissionv1beta1.AdmissionRequest,
38 recorder record.EventRecorder,
39 ) (*admissionv1beta1.AdmissionResponse, error) {
40 log.Debugf("request object bytes: %s", request.Object.Raw)
41
42
43
44
45 valuesConfig, err := config.Values(pkgK8s.MountPathValuesConfig)
46 if err != nil {
47 return nil, err
48 }
49
50 caPEM, err := os.ReadFile(pkgK8s.MountPathTrustRootsPEM)
51 if err != nil {
52 return nil, err
53 }
54 valuesConfig.IdentityTrustAnchorsPEM = string(caPEM)
55
56 ns, err := api.Get(k8s.NS, request.Namespace)
57 if err != nil {
58 return nil, err
59 }
60 resourceConfig := inject.NewResourceConfig(valuesConfig, inject.OriginWebhook, linkerdNamespace).
61 WithOwnerRetriever(ownerRetriever(ctx, api, request.Namespace)).
62 WithNsAnnotations(ns.GetAnnotations()).
63 WithKind(request.Kind.Kind)
64
65
66 report, err := resourceConfig.ParseMetaAndYAML(request.Object.Raw)
67 if err != nil {
68 return nil, err
69 }
70 log.Infof("received %s", report.ResName())
71
72
73
74 var parent *metav1.PartialObjectMetadata
75 var ownerKind string
76 if ownerRef := resourceConfig.GetOwnerRef(); ownerRef != nil {
77 res, err := k8s.GetAPIResource(ownerRef.Kind)
78 if err != nil {
79 log.Tracef("skipping event for parent %s: %s", ownerRef.Kind, err)
80 } else {
81 objs, err := api.GetByNamespaceFiltered(res, request.Namespace, ownerRef.Name, labels.Everything())
82 if err != nil {
83 log.Warnf("couldn't retrieve parent object %s-%s-%s; error: %s", request.Namespace, ownerRef.Kind, ownerRef.Name, err)
84 } else if len(objs) == 0 {
85 log.Warnf("couldn't retrieve parent object %s-%s-%s", request.Namespace, ownerRef.Kind, ownerRef.Name)
86 } else {
87 parent = objs[0]
88 }
89 ownerKind = strings.ToLower(ownerRef.Kind)
90 }
91 }
92
93 configLabels := configToPrometheusLabels(resourceConfig)
94 proxyInjectionAdmissionRequests.With(admissionRequestLabels(ownerKind, request.Namespace, report.InjectAnnotationAt, configLabels)).Inc()
95
96
97
98 injectable, reasons := report.Injectable()
99 if injectable {
100 resourceConfig.AppendPodAnnotation(pkgK8s.CreatedByAnnotation, fmt.Sprintf("linkerd/proxy-injector %s", version.Version))
101
102
103
104 inject.AppendNamespaceAnnotations(resourceConfig.GetOverrideAnnotations(), resourceConfig.GetNsAnnotations(), resourceConfig.GetWorkloadAnnotations())
105
106
107
108
109
110 if !resourceConfig.HasWorkloadAnnotation(pkgK8s.ProxyOpaquePortsAnnotation) {
111 defaultPorts := strings.Split(resourceConfig.GetValues().Proxy.OpaquePorts, ",")
112 filteredPorts := resourceConfig.FilterPodOpaquePorts(defaultPorts)
113
114
115 if len(filteredPorts) != 0 {
116 ports := strings.Join(filteredPorts, ",")
117 resourceConfig.AppendPodAnnotation(pkgK8s.ProxyOpaquePortsAnnotation, ports)
118 }
119 }
120
121 patchJSON, err := resourceConfig.GetPodPatch(true)
122 if err != nil {
123 return nil, err
124 }
125 if parent != nil {
126 recorder.Event(parent, v1.EventTypeNormal, eventTypeInjected, "Linkerd sidecar proxy injected")
127 }
128 log.Infof("injection patch generated for: %s", report.ResName())
129 log.Debugf("injection patch: %s", patchJSON)
130 proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
131 patchType := admissionv1beta1.PatchTypeJSONPatch
132 return &admissionv1beta1.AdmissionResponse{
133 UID: request.UID,
134 Allowed: true,
135 PatchType: &patchType,
136 Patch: patchJSON,
137 }, nil
138 }
139
140
141
142 readableReasons := make([]string, 0, len(reasons))
143 for _, reason := range reasons {
144 readableReasons = append(readableReasons, inject.Reasons[reason])
145 }
146 readableMsg := strings.Join(readableReasons, ", ")
147
148 if parent != nil {
149 recorder.Eventf(parent, v1.EventTypeNormal, eventTypeSkipped, "Linkerd sidecar proxy injection skipped: %s", readableMsg)
150 }
151
152
153
154 patchJSON, err := resourceConfig.CreateOpaquePortsPatch()
155 if err != nil {
156 return nil, err
157 }
158
159
160
161 if len(patchJSON) != 0 {
162 log.Infof("annotation patch generated for: %s", report.ResName())
163 log.Debugf("annotation patch: %s", patchJSON)
164 proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "false", "", report.InjectAnnotationAt, configLabels)).Inc()
165 patchType := admissionv1beta1.PatchTypeJSONPatch
166 return &admissionv1beta1.AdmissionResponse{
167 UID: request.UID,
168 Allowed: true,
169 PatchType: &patchType,
170 Patch: patchJSON,
171 }, nil
172 }
173
174
175
176
177 if resourceConfig.IsPod() {
178 log.Infof("skipped %s: %s", report.ResName(), readableMsg)
179 proxyInjectionAdmissionResponses.With(admissionResponseLabels(ownerKind, request.Namespace, "true", strings.Join(reasons, ","), report.InjectAnnotationAt, configLabels)).Inc()
180 return &admissionv1beta1.AdmissionResponse{
181 UID: request.UID,
182 Allowed: true,
183 }, nil
184 }
185
186 return &admissionv1beta1.AdmissionResponse{
187 UID: request.UID,
188 Allowed: true,
189 }, nil
190 }
191 }
192
193 func ownerRetriever(ctx context.Context, api *k8s.MetadataAPI, ns string) inject.OwnerRetrieverFunc {
194 return func(p *v1.Pod) (string, string, error) {
195 p.SetNamespace(ns)
196 return api.GetOwnerKindAndName(ctx, p, true)
197 }
198 }
199
View as plain text