Skip to content

Commit 0572170

Browse files
devjoesaramase
authored andcommitted
feat: allow proxies to be injected using a native sidecar
Signed-off-by: Anish Ramasekar <anish.ramasekar@gmail.com>
1 parent 6618387 commit 0572170

File tree

3 files changed

+157
-6
lines changed

3 files changed

+157
-6
lines changed

cmd/webhook/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func setupWebhook(mgr manager.Manager, setupFinished chan struct{}) {
168168

169169
// setup webhooks
170170
entryLog.Info("registering webhook to the webhook server")
171-
podMutator, err := wh.NewPodMutator(mgr.GetClient(), mgr.GetAPIReader(), audience, mgr.GetScheme())
171+
podMutator, err := wh.NewPodMutator(mgr.GetClient(), mgr.GetAPIReader(), audience, mgr.GetScheme(), mgr.GetConfig())
172172
if err != nil {
173173
panic(fmt.Errorf("unable to set up pod mutator: %w", err))
174174
}

pkg/webhook/webhook.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
apierrors "k8s.io/apimachinery/pkg/api/errors"
2121
"k8s.io/apimachinery/pkg/runtime"
2222
"k8s.io/apimachinery/pkg/types"
23+
"k8s.io/client-go/discovery"
24+
"k8s.io/client-go/rest"
2325
"k8s.io/utils/ptr"
2426

2527
"github.com/Azure/azure-workload-identity/pkg/config"
@@ -53,17 +55,31 @@ type podMutator struct {
5355
azureAuthorityHost string
5456
proxyImage string
5557
proxyInitImage string
58+
useNativeSidecar bool
5659
}
5760

5861
// NewPodMutator returns a pod mutation handler
59-
func NewPodMutator(client client.Client, reader client.Reader, audience string, scheme *runtime.Scheme) (admission.Handler, error) {
62+
func NewPodMutator(client client.Client, reader client.Reader, audience string, scheme *runtime.Scheme, restConfig *rest.Config) (admission.Handler, error) {
6063
c, err := config.ParseConfig()
6164
if err != nil {
6265
return nil, err
6366
}
6467
if audience == "" {
6568
audience = DefaultAudience
6669
}
70+
71+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(restConfig)
72+
if err != nil {
73+
return nil, errors.Wrap(err, "failed to create discovery client")
74+
}
75+
// "SidecarContainers" went beta in 1.29. With the 3 version skew policy,
76+
// between API server and kubelet, 1.32 is the earliest version this can be
77+
// safely used.
78+
useNativeSidecar, err := isSupportedKubernetesVersion(discoveryClient, 1, 32)
79+
if err != nil {
80+
return nil, errors.Wrap(err, "failed to check kubernetes version")
81+
}
82+
6783
// this is used to configure the AZURE_AUTHORITY_HOST env var that's
6884
// used by the azure sdk
6985
azureAuthorityHost, err := getAzureAuthorityHost(c)
@@ -92,6 +108,7 @@ func NewPodMutator(client client.Client, reader client.Reader, audience string,
92108
azureAuthorityHost: azureAuthorityHost,
93109
proxyImage: proxyImage,
94110
proxyInitImage: proxyInitImage,
111+
useNativeSidecar: useNativeSidecar,
95112
}, nil
96113
}
97114

@@ -155,7 +172,11 @@ func (m *podMutator) Handle(ctx context.Context, req admission.Request) (respons
155172
}
156173

157174
pod.Spec.InitContainers = m.injectProxyInitContainer(pod.Spec.InitContainers, proxyPort)
158-
pod.Spec.Containers = m.injectProxySidecarContainer(pod.Spec.Containers, proxyPort)
175+
if m.useNativeSidecar {
176+
pod.Spec.InitContainers = m.injectProxySidecarContainer(pod.Spec.InitContainers, proxyPort, ptr.To(corev1.ContainerRestartPolicyAlways))
177+
} else {
178+
pod.Spec.Containers = m.injectProxySidecarContainer(pod.Spec.Containers, proxyPort, nil)
179+
}
159180
}
160181

161182
// get service account token expiration
@@ -228,13 +249,14 @@ func (m *podMutator) injectProxyInitContainer(containers []corev1.Container, pro
228249
return containers
229250
}
230251

231-
func (m *podMutator) injectProxySidecarContainer(containers []corev1.Container, proxyPort int32) []corev1.Container {
252+
func (m *podMutator) injectProxySidecarContainer(containers []corev1.Container, proxyPort int32, restartPolicy *corev1.ContainerRestartPolicy) []corev1.Container {
232253
for _, container := range containers {
233254
if container.Name == ProxySidecarContainerName {
234255
return containers
235256
}
236257
}
237258
logLevel := currentLogLevel() // run the proxy at the same log level as the webhook
259+
238260
containers = append([]corev1.Container{{
239261
Name: ProxySidecarContainerName,
240262
Image: m.proxyImage,
@@ -267,6 +289,7 @@ func (m *podMutator) injectProxySidecarContainer(containers []corev1.Container,
267289
ReadOnlyRootFilesystem: ptr.To(true),
268290
RunAsNonRoot: ptr.To(true),
269291
},
292+
RestartPolicy: restartPolicy,
270293
}}, containers...)
271294

272295
return containers
@@ -467,3 +490,26 @@ func currentLogLevel() string {
467490
}
468491
return "" // this is unreachable
469492
}
493+
494+
// isSupportedKubernetesVersion checks if the Kubernetes version is supported
495+
// by comparing the major and minor version numbers.
496+
// It returns true if the version is greater than or equal to the specified
497+
// minimum version, and false otherwise.
498+
func isSupportedKubernetesVersion(discoveryClient discovery.DiscoveryInterface, minMajor, minMinor int) (bool, error) {
499+
// check if the kubernetes version is supported
500+
version, err := discoveryClient.ServerVersion()
501+
if err != nil {
502+
return false, err
503+
}
504+
505+
major, err := strconv.Atoi(version.Major)
506+
if err != nil {
507+
return false, err
508+
}
509+
minor, err := strconv.Atoi(version.Minor)
510+
if err != nil {
511+
return false, err
512+
}
513+
514+
return major > minMajor || (major == minMajor && minor >= minMinor), nil
515+
}

pkg/webhook/webhook_test.go

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"testing"
1212

13+
"k8s.io/utils/ptr"
1314
"monis.app/mlog"
1415
"sigs.k8s.io/controller-runtime/pkg/client"
1516
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -19,7 +20,9 @@ import (
1920
corev1 "k8s.io/api/core/v1"
2021
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2122
"k8s.io/apimachinery/pkg/runtime"
22-
"k8s.io/utils/ptr"
23+
"k8s.io/apimachinery/pkg/version"
24+
discoveryfake "k8s.io/client-go/discovery/fake"
25+
kubernetesfake "k8s.io/client-go/kubernetes/fake"
2326

2427
"github.com/Azure/azure-workload-identity/pkg/config"
2528
)
@@ -1115,20 +1118,26 @@ func TestInjectProxySidecarContainer(t *testing.T) {
11151118
},
11161119
}
11171120

1121+
proxyNativeSidecarContainer := proxySidecarContainer
1122+
proxyNativeSidecarContainer.RestartPolicy = ptr.To(corev1.ContainerRestartPolicyAlways)
1123+
11181124
tests := []struct {
11191125
name string
11201126
containers []corev1.Container
11211127
expectedContainers []corev1.Container
1128+
restartPolicy *corev1.ContainerRestartPolicy
11221129
}{
11231130
{
11241131
name: "no containers",
11251132
containers: []corev1.Container{},
11261133
expectedContainers: []corev1.Container{proxySidecarContainer},
1134+
restartPolicy: nil,
11271135
},
11281136
{
11291137
name: "proxy sidecar container manually injected",
11301138
containers: []corev1.Container{proxySidecarContainer},
11311139
expectedContainers: []corev1.Container{proxySidecarContainer},
1140+
restartPolicy: nil,
11321141
},
11331142
{
11341143
name: "inject proxy sidecar container to existing containers",
@@ -1145,13 +1154,31 @@ func TestInjectProxySidecarContainer(t *testing.T) {
11451154
Image: "my-image",
11461155
},
11471156
},
1157+
restartPolicy: nil,
1158+
},
1159+
{
1160+
name: "inject proxy native sidecar container to existing containers when restartPolicy is set",
1161+
containers: []corev1.Container{
1162+
{
1163+
Name: "my-container",
1164+
Image: "my-image",
1165+
},
1166+
},
1167+
expectedContainers: []corev1.Container{
1168+
proxyNativeSidecarContainer,
1169+
{
1170+
Name: "my-container",
1171+
Image: "my-image",
1172+
},
1173+
},
1174+
restartPolicy: ptr.To(corev1.ContainerRestartPolicyAlways),
11481175
},
11491176
}
11501177

11511178
m := &podMutator{proxyImage: imageURL}
11521179
for _, test := range tests {
11531180
t.Run(test.name, func(t *testing.T) {
1154-
containers := m.injectProxySidecarContainer(test.containers, proxyPort)
1181+
containers := m.injectProxySidecarContainer(test.containers, proxyPort, test.restartPolicy)
11551182
if !reflect.DeepEqual(containers, test.expectedContainers) {
11561183
t.Errorf("expected: %v, got: %v", test.expectedContainers, containers)
11571184
}
@@ -1374,3 +1401,81 @@ func TestHandleError(t *testing.T) {
13741401
})
13751402
}
13761403
}
1404+
1405+
func TestIsSupportedKubernetesVersion(t *testing.T) {
1406+
tests := []struct {
1407+
name string
1408+
versionInfo *version.Info
1409+
minMajor int
1410+
minMinor int
1411+
want bool
1412+
wantErr bool
1413+
}{
1414+
{
1415+
name: "Exact match",
1416+
versionInfo: &version.Info{Major: "1", Minor: "20"},
1417+
minMajor: 1,
1418+
minMinor: 20,
1419+
want: true,
1420+
},
1421+
{
1422+
name: "Higher major version",
1423+
versionInfo: &version.Info{Major: "2", Minor: "0"},
1424+
minMajor: 1,
1425+
minMinor: 25,
1426+
want: true,
1427+
},
1428+
{
1429+
name: "Higher minor version",
1430+
versionInfo: &version.Info{Major: "1", Minor: "25"},
1431+
minMajor: 1,
1432+
minMinor: 20,
1433+
want: true,
1434+
},
1435+
{
1436+
name: "Lower minor version",
1437+
versionInfo: &version.Info{Major: "1", Minor: "18"},
1438+
minMajor: 1,
1439+
minMinor: 20,
1440+
want: false,
1441+
},
1442+
{
1443+
name: "Lower major version",
1444+
versionInfo: &version.Info{Major: "0", Minor: "25"},
1445+
minMajor: 1,
1446+
minMinor: 20,
1447+
want: false,
1448+
},
1449+
{
1450+
name: "Invalid major version",
1451+
versionInfo: &version.Info{Major: "invalid", Minor: "20"},
1452+
minMajor: 1,
1453+
minMinor: 20,
1454+
wantErr: true,
1455+
},
1456+
{
1457+
name: "Invalid minor version",
1458+
versionInfo: &version.Info{Major: "1", Minor: "invalid"},
1459+
minMajor: 1,
1460+
minMinor: 20,
1461+
wantErr: true,
1462+
},
1463+
}
1464+
1465+
for _, tt := range tests {
1466+
t.Run(tt.name, func(t *testing.T) {
1467+
t.Parallel()
1468+
discoveryClient := kubernetesfake.NewClientset().Discovery()
1469+
discoveryClient.(*discoveryfake.FakeDiscovery).FakedServerVersion = tt.versionInfo
1470+
1471+
got, err := isSupportedKubernetesVersion(discoveryClient, tt.minMajor, tt.minMinor)
1472+
if (err != nil) != tt.wantErr {
1473+
t.Errorf("isSupportedKubernetesVersion() error = %v, wantErr %v", err, tt.wantErr)
1474+
return
1475+
}
1476+
if got != tt.want {
1477+
t.Errorf("isSupportedKubernetesVersion() = %v, want %v", got, tt.want)
1478+
}
1479+
})
1480+
}
1481+
}

0 commit comments

Comments
 (0)