Skip to content

Commit

Permalink
Merge branch 'main' into test/detect-distro
Browse files Browse the repository at this point in the history
  • Loading branch information
Noxsios authored and phillebaba committed May 22, 2024
2 parents 2261d2e + f2d3602 commit 5420860
Show file tree
Hide file tree
Showing 15 changed files with 337 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/zarf-agent/manifests/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ spec:
imagePullSecrets:
- name: private-registry
priorityClassName: system-node-critical
serviceAccountName: zarf
containers:
- name: server
image: "###ZARF_REGISTRY###/###ZARF_CONST_AGENT_IMAGE###:###ZARF_CONST_AGENT_IMAGE_TAG###"
Expand Down
12 changes: 12 additions & 0 deletions packages/zarf-agent/manifests/role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: zarf-agent
namespace: zarf
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
13 changes: 13 additions & 0 deletions packages/zarf-agent/manifests/rolebinding.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: zarf-agent-binding
namespace: zarf
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: zarf-agent
subjects:
- kind: ServiceAccount
name: zarf
namespace: zarf
5 changes: 5 additions & 0 deletions packages/zarf-agent/manifests/serviceaccount.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: zarf
namespace: zarf
3 changes: 3 additions & 0 deletions packages/zarf-agent/zarf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ components:
- manifests/secret.yaml
- manifests/deployment.yaml
- manifests/webhook.yaml
- manifests/role.yaml
- manifests/rolebinding.yaml
- manifests/serviceaccount.yaml
actions:
onCreate:
before:
Expand Down
13 changes: 13 additions & 0 deletions site/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ export default defineConfig({
integrations: [
starlight({
title: "Zarf",
head: [
{
tag: "script",
content: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','G-N1XZ8ZXCWL');`,
},
],
components: {
SkipLink: "./src/components/SkipLink.astro",
},
social: {
github: "https://github.com/defenseunicorns/zarf",
slack: "https://kubernetes.slack.com/archives/C03B6BJAUJ3",
Expand Down
18 changes: 18 additions & 0 deletions site/src/components/SkipLink.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
import type { Props } from '@astrojs/starlight/props'
import Default from '@astrojs/starlight/components/SkipLink.astro'
---

<!-- Add the noscript tag for Google Tag Manager. -->
<noscript>
<iframe
src="https://www.googletagmanager.com/gtag/js?id=G-N1XZ8ZXCWL"
height="0"
width="0"
style="display:none;visibility:hidden"
>
</iframe>
</noscript>

<!-- Render the default <SkipLink/> component. -->
<Default {...Astro.props}><slot /></Default>
2 changes: 1 addition & 1 deletion src/config/lang/english.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ const (
AgentErrBadRequest = "could not read request body: %s"
AgentErrBindHandler = "Unable to bind the webhook handler"
AgentErrCouldNotDeserializeReq = "could not deserialize request: %s"
AgentErrGetState = "failed to load zarf state from file: %w"
AgentErrGetState = "failed to load zarf state: %w"
AgentErrHostnameMatch = "failed to complete hostname matching: %w"
AgentErrImageSwap = "Unable to swap the host for (%s)"
AgentErrInvalidMethod = "invalid method only POST requests are allowed"
Expand Down
39 changes: 22 additions & 17 deletions src/internal/agent/hooks/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
package hooks

import (
"context"
"encoding/json"
"fmt"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/config/lang"
"github.com/defenseunicorns/zarf/src/internal/agent/operations"
"github.com/defenseunicorns/zarf/src/internal/agent/state"
"github.com/defenseunicorns/zarf/src/pkg/cluster"
"github.com/defenseunicorns/zarf/src/pkg/message"
"github.com/defenseunicorns/zarf/src/pkg/transform"
v1 "k8s.io/api/admission/v1"
Expand All @@ -20,11 +21,15 @@ import (
)

// NewPodMutationHook creates a new instance of pods mutation hook.
func NewPodMutationHook() operations.Hook {
func NewPodMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook {
message.Debug("hooks.NewMutationHook()")
return operations.Hook{
Create: mutatePod,
Update: mutatePod,
Create: func(r *v1.AdmissionRequest) (*operations.Result, error) {
return mutatePod(ctx, r, cluster)
},
Update: func(r *v1.AdmissionRequest) (*operations.Result, error) {
return mutatePod(ctx, r, cluster)
},
}
}

Expand All @@ -34,14 +39,12 @@ func parsePod(object []byte) (*corev1.Pod, error) {
if err := json.Unmarshal(object, &pod); err != nil {
return nil, err
}

return &pod, nil
}

func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) {
func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) {
message.Debugf("hooks.mutatePod()(*v1.AdmissionRequest) - %#v , %s/%s: %#v", r.Kind, r.Namespace, r.Name, r.Operation)

var patchOperations []operations.PatchOperation
pod, err := parsePod(r.Object.Raw)
if err != nil {
return &operations.Result{Msg: err.Error()}, nil
Expand All @@ -51,24 +54,26 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) {
// We've already played with this pod, just keep swimming 🐟
return &operations.Result{
Allowed: true,
PatchOps: patchOperations,
PatchOps: []operations.PatchOperation{},
}, nil
}

// Add the zarf secret to the podspec
zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}
patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret))

zarfState, err := state.GetZarfStateFromAgentPod()
state, err := cluster.LoadZarfState(ctx)
if err != nil {
return nil, fmt.Errorf(lang.AgentErrGetState, err)
}
containerRegistryURL := zarfState.RegistryInfo.Address
registryURL := state.RegistryInfo.Address

var patchOperations []operations.PatchOperation

// Add the zarf secret to the podspec
zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}
patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret))

// update the image host for each init container
for idx, container := range pod.Spec.InitContainers {
path := fmt.Sprintf("/spec/initContainers/%d/image", idx)
replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image)
replacement, err := transform.ImageTransformHost(registryURL, container.Image)
if err != nil {
message.Warnf(lang.AgentErrImageSwap, container.Image)
continue // Continue, because we might as well attempt to mutate the other containers for this pod
Expand All @@ -79,7 +84,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) {
// update the image host for each ephemeral container
for idx, container := range pod.Spec.EphemeralContainers {
path := fmt.Sprintf("/spec/ephemeralContainers/%d/image", idx)
replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image)
replacement, err := transform.ImageTransformHost(registryURL, container.Image)
if err != nil {
message.Warnf(lang.AgentErrImageSwap, container.Image)
continue // Continue, because we might as well attempt to mutate the other containers for this pod
Expand All @@ -90,7 +95,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) {
// update the image host for each normal container
for idx, container := range pod.Spec.Containers {
path := fmt.Sprintf("/spec/containers/%d/image", idx)
replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image)
replacement, err := transform.ImageTransformHost(registryURL, container.Image)
if err != nil {
message.Warnf(lang.AgentErrImageSwap, container.Image)
continue // Continue, because we might as well attempt to mutate the other containers for this pod
Expand Down
149 changes: 149 additions & 0 deletions src/internal/agent/hooks/pods_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

package hooks

import (
"context"
"encoding/json"
"net/http"
"testing"

"github.com/defenseunicorns/zarf/src/config"
"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
"github.com/defenseunicorns/zarf/src/internal/agent/operations"
"github.com/defenseunicorns/zarf/src/types"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) *v1.AdmissionRequest {
t.Helper()
raw, err := json.Marshal(pod)
require.NoError(t, err)
return &v1.AdmissionRequest{
Operation: op,
Object: runtime.RawExtension{
Raw: raw,
},
}
}

func TestPodMutationWebhook(t *testing.T) {
t.Parallel()

ctx := context.Background()

state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}}
c := createTestClientWithZarfState(ctx, t, state)
handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c))

tests := []struct {
name string
admissionReq *v1.AdmissionRequest
expectedPatch []operations.PatchOperation
code int
}{
{
name: "pod with label should be mutated",
admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"should-be": "mutated"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
InitContainers: []corev1.Container{{Image: "busybox"}},
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Image: "alpine",
},
},
},
},
}),
expectedPatch: []operations.PatchOperation{
operations.ReplacePatchOperation(
"/spec/imagePullSecrets",
[]corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}},
),
operations.ReplacePatchOperation(
"/spec/initContainers/0/image",
"127.0.0.1:31999/library/busybox:latest-zarf-2140033595",
),
operations.ReplacePatchOperation(
"/spec/ephemeralContainers/0/image",
"127.0.0.1:31999/library/alpine:latest-zarf-1117969859",
),
operations.ReplacePatchOperation(
"/spec/containers/0/image",
"127.0.0.1:31999/library/nginx:latest-zarf-3793515731",
),
operations.ReplacePatchOperation(
"/metadata/labels/zarf-agent",
"patched",
),
},
code: http.StatusOK,
},
{
name: "pod with zarf-agent patched label should not be mutated",
admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{"zarf-agent": "patched"},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
},
}),
expectedPatch: nil,
code: http.StatusOK,
},
{
name: "pod with no labels should not error",
admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: nil,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{Image: "nginx"}},
},
}),
expectedPatch: []operations.PatchOperation{
operations.ReplacePatchOperation(
"/spec/imagePullSecrets",
[]corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}},
),
operations.ReplacePatchOperation(
"/spec/containers/0/image",
"127.0.0.1:31999/library/nginx:latest-zarf-3793515731",
),
operations.AddPatchOperation(
"/metadata/labels",
map[string]string{"zarf-agent": "patched"},
),
},
code: http.StatusOK,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code)
if tt.expectedPatch == nil {
require.Empty(t, string(resp.Patch))
} else {
expectedPatchJSON, err := json.Marshal(tt.expectedPatch)
require.NoError(t, err)
require.NotNil(t, resp)
require.True(t, resp.Allowed)
require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch))
}
})
}
}
Loading

0 comments on commit 5420860

Please sign in to comment.