forked from openservicemesh/osm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpatch.go
163 lines (139 loc) · 7.31 KB
/
patch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package injector
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
mapset "github.com/deckarep/golang-set"
"github.com/google/uuid"
"github.com/pkg/errors"
"gomodules.xyz/jsonpatch/v2"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/openservicemesh/osm/pkg/constants"
"github.com/openservicemesh/osm/pkg/envoy"
"github.com/openservicemesh/osm/pkg/errcode"
"github.com/openservicemesh/osm/pkg/metricsstore"
)
func (wh *mutatingWebhook) createPatch(pod *corev1.Pod, req *admissionv1.AdmissionRequest, proxyUUID uuid.UUID) ([]byte, error) {
namespace := req.Namespace
// Issue a certificate for the proxy sidecar - used for Envoy to connect to XDS (not Envoy-to-Envoy connections)
cn := envoy.NewXDSCertCommonName(proxyUUID, envoy.KindSidecar, pod.Spec.ServiceAccountName, namespace)
log.Debug().Msgf("Patching POD spec: service-account=%s, namespace=%s with certificate CN=%s", pod.Spec.ServiceAccountName, namespace, cn)
startTime := time.Now()
bootstrapCertificate, err := wh.certManager.IssueCertificate(cn, constants.XDSCertificateValidityPeriod)
if err != nil {
log.Error().Err(err).Msgf("Error issuing bootstrap certificate for Envoy with CN=%s", cn)
return nil, err
}
elapsed := time.Since(startTime)
metricsstore.DefaultMetricsStore.CertIssuedCount.Inc()
metricsstore.DefaultMetricsStore.CertIssuedTime.
WithLabelValues().Observe(elapsed.Seconds())
originalHealthProbes := rewriteHealthProbes(pod)
// Create the bootstrap configuration for the Envoy proxy for the given pod
envoyBootstrapConfigName := fmt.Sprintf("envoy-bootstrap-config-%s", proxyUUID)
// The webhook has a side effect (making out-of-band changes) of creating k8s secret
// corresponding to the Envoy bootstrap config. Such a side effect needs to be skipped
// when the request is a DryRun.
// Ref: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#side-effects
if req.DryRun != nil && *req.DryRun {
log.Debug().Msgf("Skipping envoy bootstrap config creation for dry-run request: service-account=%s, namespace=%s", pod.Spec.ServiceAccountName, namespace)
} else if _, err = wh.createEnvoyBootstrapConfig(envoyBootstrapConfigName, namespace, wh.osmNamespace, bootstrapCertificate, originalHealthProbes); err != nil {
log.Error().Err(err).Msgf("Failed to create Envoy bootstrap config for pod: service-account=%s, namespace=%s, certificate CN=%s", pod.Spec.ServiceAccountName, namespace, cn)
return nil, err
}
// Create volume for envoy TLS secret
pod.Spec.Volumes = append(pod.Spec.Volumes, getVolumeSpec(envoyBootstrapConfigName)...)
// On Windows we cannot use init containers to program HNS because it requires elevated privileges
// As a result we assume that the HNS redirection policies are already programmed via a CNI plugin.
// Skip adding the init container and only patch the pod spec with sidecar container.
podOS := pod.Spec.NodeSelector["kubernetes.io/os"]
if err := wh.verifyPrerequisites(podOS); err != nil {
return nil, err
}
if !strings.EqualFold(podOS, constants.OSWindows) {
// Build outbound port exclusion list
podOutboundPortExclusionList, _ := wh.getPortExclusionListForPod(pod, namespace, outboundPortExclusionListAnnotation)
globalOutboundPortExclusionList := wh.configurator.GetOutboundPortExclusionList()
outboundPortExclusionList := mergePortExclusionLists(podOutboundPortExclusionList, globalOutboundPortExclusionList)
// Build inbound port exclusion list
podInboundPortExclusionList, _ := wh.getPortExclusionListForPod(pod, namespace, inboundPortExclusionListAnnotation)
globalInboundPortExclusionList := wh.configurator.GetInboundPortExclusionList()
inboundPortExclusionList := mergePortExclusionLists(podInboundPortExclusionList, globalInboundPortExclusionList)
// Add the Init Container
initContainer := getInitContainerSpec(constants.InitContainerName, wh.configurator, wh.configurator.GetOutboundIPRangeExclusionList(), outboundPortExclusionList, inboundPortExclusionList, wh.configurator.IsPrivilegedInitContainer())
pod.Spec.InitContainers = append(pod.Spec.InitContainers, initContainer)
}
// Add the Envoy sidecar
sidecar := getEnvoySidecarContainerSpec(pod, wh.configurator, originalHealthProbes, podOS)
pod.Spec.Containers = append(pod.Spec.Containers, sidecar)
enableMetrics, err := wh.isMetricsEnabled(namespace)
if err != nil {
log.Error().Err(err).Msgf("Error checking if namespace %s is enabled for metrics", namespace)
return nil, err
}
if enableMetrics {
if pod.Annotations == nil {
pod.Annotations = make(map[string]string)
}
pod.Annotations[constants.PrometheusScrapeAnnotation] = strconv.FormatBool(true)
pod.Annotations[constants.PrometheusPortAnnotation] = strconv.Itoa(constants.EnvoyPrometheusInboundListenerPort)
pod.Annotations[constants.PrometheusPathAnnotation] = constants.PrometheusScrapePath
}
// This will append a label to the pod, which points to the unique Envoy ID used in the
// xDS certificate for that Envoy. This label will help xDS match the actual pod to the Envoy that
// connects to xDS (with the certificate's CN matching this label).
if pod.Labels == nil {
pod.Labels = make(map[string]string)
}
pod.Labels[constants.EnvoyUniqueIDLabelName] = proxyUUID.String()
return json.Marshal(makePatches(req, pod))
}
// verifyPrerequisites verifies if the prerequisites to patch the request are met by returning an error if unmet
func (wh *mutatingWebhook) verifyPrerequisites(podOS string) error {
isWindows := strings.EqualFold(podOS, constants.OSWindows)
// Verify that the required images are configured
if image := wh.configurator.GetEnvoyImage(); !isWindows && image == "" {
// Linux pods require Envoy Linux image
return errors.New("MeshConfig sidecar.envoyImage not set")
}
if image := wh.configurator.GetEnvoyWindowsImage(); isWindows && image == "" {
// Windows pods require Envoy Windows image
return errors.New("MeshConfig sidecar.envoyWindowsImage not set")
}
if image := wh.configurator.GetInitContainerImage(); !isWindows && image == "" {
// Linux pods require init container image
return errors.New("MeshConfig sidecar.initContainerImage not set")
}
return nil
}
func makePatches(req *admissionv1.AdmissionRequest, pod *corev1.Pod) []jsonpatch.JsonPatchOperation {
original := req.Object.Raw
current, err := json.Marshal(pod)
if err != nil {
log.Error().Err(err).Str(errcode.Kind, errcode.GetErrCodeWithMetric(errcode.ErrMarshallingKubernetesResource)).
Msgf("Error marshaling Pod with UID=%s", pod.ObjectMeta.UID)
}
admissionResponse := admission.PatchResponseFromRaw(original, current)
return admissionResponse.Patches
}
func mergePortExclusionLists(podSpecificPortExclusionList, globalPortExclusionList []int) []int {
portExclusionListMap := mapset.NewSet()
var portExclusionListMerged []int
// iterate over the global outbound ports to be excluded
for _, port := range globalPortExclusionList {
if addedToSet := portExclusionListMap.Add(port); addedToSet {
portExclusionListMerged = append(portExclusionListMerged, port)
}
}
// iterate over the pod specific ports to be excluded
for _, port := range podSpecificPortExclusionList {
if addedToSet := portExclusionListMap.Add(port); addedToSet {
portExclusionListMerged = append(portExclusionListMerged, port)
}
}
return portExclusionListMerged
}