Skip to content

Commit 10ccc8e

Browse files
alicefrcheesesashimi
authored andcommitted
e2e: Test pull secrets from other namespaces
deploy-bink now creates an extra namespace and adds it to the release's pullSecretNamespaces, and passes its name to the tests. Configuring it at deploy time, rather than from the test, avoids restarting the controller during the run, so the new test can run in parallel. In TestPullSecretNamespaces, a pool referencing a secret in a namespace the test creates must be Degraded with SecretError and claim no node; switched to a secret in the allowed namespace, its node must update through the authenticated registry. The test is skipped when no namespace is specified, but fails if the operator does not allow a specified namespace. Assisted-by: AI Signed-off-by: Alice Frosi <afrosi@redhat.com>
1 parent 6c1b6e5 commit 10ccc8e

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

‎Makefile‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ BINK_NODE_DISK_IMAGE ?= ghcr.io/bootc-dev/bink/node:v$(DEFAULT_KUBE_MINOR)-fedor
1717
BINK_LOCAL_REGISTRY_NODE_IMAGE ?= registry.cluster.local:5000/node
1818
E2E_REGISTRY_USER ?= e2e-user
1919
E2E_REGISTRY_PASSWORD ?= e2e-password
20+
# Extra namespace deploy-bink allows pull secrets from (TestPullSecretNamespaces).
21+
E2E_PULL_SECRET_NAMESPACE ?= bootc-e2e-pull-secrets
2022
# Released operator for upgrade testing (TestOperatorUpgrade): the published
2123
# chart and the release tag, whose version without the leading "v" is the
2224
# chart version. The released image is the one the chart deploys. Override
@@ -129,6 +131,7 @@ e2e: ## Run e2e tests (requires: make deploy-bink). V=1 for verbose. RUN=<regex>
129131
E2E_NODE_IMAGE_UPDATE_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update | jq -r '.Digest') \
130132
E2E_NODE_IMAGE_UPDATE2_DIGEST=$$(skopeo inspect --tls-verify=false docker://localhost:5000/node:update2 | jq -r '.Digest') \
131133
E2E_REGISTRY_USER=$(E2E_REGISTRY_USER) E2E_REGISTRY_PASSWORD=$(E2E_REGISTRY_PASSWORD) \
134+
E2E_PULL_SECRET_NAMESPACE=$(E2E_PULL_SECRET_NAMESPACE) \
132135
$(if $(RELEASED_OPERATOR_TAG),E2E_OPERATOR_RELEASE_TAG=$(RELEASED_OPERATOR_TAG)) \
133136
$(if $(RELEASED_OPERATOR_TAG),E2E_OPERATOR_RELEASED_IMG=$(IMG_BINK_RELEASED)) \
134137
$(if $(RELEASED_OPERATOR_CHART),E2E_OPERATOR_RELEASED_CHART=$(RELEASED_OPERATOR_CHART)) \
@@ -273,13 +276,16 @@ start-bink: seed-node-image ## Start a bink cluster (idempotent).
273276
deploy-bink: start-bink build-update-image $(if $(RELEASED_OPERATOR_TAG),push-released-operator-image) helm ## Deploy to a bink cluster (requires: buildimg).
274277
podman push --tls-verify=false $(IMG) localhost:5000/bootc-operator-e2e:latest
275278
helm dependency build ./chart/bootc-operator
279+
kubectl --kubeconfig $(KUBECONFIG_BINK) create namespace $(E2E_PULL_SECRET_NAMESPACE) \
280+
--dry-run=client -o yaml | kubectl --kubeconfig $(KUBECONFIG_BINK) apply -f -
276281
img="$(IMG_BINK)" && \
277282
helm upgrade --install bootc-operator ./chart/bootc-operator \
278283
--kubeconfig $(abspath $(KUBECONFIG_BINK)) \
279284
--create-namespace --namespace bootc-operator \
280285
--set-string image.repository="$${img%:*}" \
281286
--set-string image.tag="$${img##*:}" \
282287
--set-string image.pullPolicy=Always \
288+
--set-json 'pullSecretNamespaces=["$(E2E_PULL_SECRET_NAMESPACE)"]' \
283289
--set-json 'controller.extraArgs=["--allow-insecure-registry","--tag-resolution-interval=10s"]' \
284290
--set-string 'daemon.extraArgs[0]=--bootc-poll-interval=$(E2E_BOOTC_POLL_INTERVAL)'
285291
kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout restart deployment/bootc-operator-controller
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package e2e
4+
5+
import (
6+
"context"
7+
"encoding/base64"
8+
"fmt"
9+
"os"
10+
"slices"
11+
"testing"
12+
"time"
13+
14+
. "github.com/onsi/gomega"
15+
corev1 "k8s.io/api/core/v1"
16+
apierrors "k8s.io/apimachinery/pkg/api/errors"
17+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
20+
bootcv1alpha1 "github.com/bootc-dev/bootc-operator/api/v1alpha1"
21+
"github.com/bootc-dev/bootc-operator/test/e2e/e2eutil"
22+
testutil "github.com/bootc-dev/bootc-operator/test/util"
23+
)
24+
25+
// TestPullSecretNamespaces verifies that pools can only use pull secrets
26+
// from the namespaces the operator is configured to read from. The
27+
// operator must already allow E2E_PULL_SECRET_NAMESPACE (make deploy-bink
28+
// does). A pool referencing a secret in a namespace the test creates must
29+
// be Degraded without claiming its node; switched to a secret in the
30+
// allowed namespace, its node must update through the authenticated
31+
// registry.
32+
func TestPullSecretNamespaces(t *testing.T) {
33+
e2eutil.Providers(t, "bink", "eks")
34+
t.Parallel()
35+
g := NewWithT(t)
36+
g.SetDefaultEventuallyTimeout(pollTimeout)
37+
g.SetDefaultEventuallyPollingInterval(pollInterval)
38+
39+
env := e2eutil.New(t)
40+
if env.RegistryUser() == "" || env.RegistryPassword() == "" {
41+
t.Skip("E2E_REGISTRY_USER / E2E_REGISTRY_PASSWORD not set")
42+
}
43+
allowedNS := os.Getenv("E2E_PULL_SECRET_NAMESPACE")
44+
if allowedNS == "" {
45+
t.Skip("E2E_PULL_SECRET_NAMESPACE not set")
46+
}
47+
48+
ctx := context.Background()
49+
configuredNamespaces := controllerPullSecretNamespaces(t, g, ctx, env.Client)
50+
if !slices.Contains(configuredNamespaces, allowedNS) {
51+
t.Fatalf(
52+
"operator must allow pull secrets from %q; configured namespaces: %v",
53+
allowedNS,
54+
configuredNamespaces,
55+
)
56+
}
57+
58+
deniedNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: env.TestID() + "-denied"}}
59+
g.Expect(env.Client.Create(ctx, deniedNS)).To(Succeed())
60+
t.Cleanup(func() { _ = env.Client.Delete(context.Background(), deniedNS) })
61+
62+
nodeName := env.AddNode(t)
63+
64+
deniedSecret := registrySecret(env, env.TestID()+"-pull-secret", deniedNS.Name)
65+
g.Expect(env.Client.Create(ctx, deniedSecret)).To(Succeed())
66+
allowedSecret := registrySecret(env, env.TestID()+"-pull-secret", allowedNS)
67+
g.Expect(env.Client.Create(ctx, allowedSecret)).To(Succeed())
68+
t.Cleanup(func() { _ = env.Client.Delete(context.Background(), allowedSecret) })
69+
70+
authImageRef := env.AuthImageRef()
71+
pool := env.NewPool("psns", authImageRef,
72+
testutil.WithPullSecret(deniedSecret.Name, deniedSecret.Namespace),
73+
)
74+
g.Expect(env.Client.Create(ctx, pool)).To(Succeed())
75+
76+
// Phase 1: the secret exists, but its namespace is not allowed.
77+
g.Eventually(func() ([]metav1.Condition, error) {
78+
var p bootcv1alpha1.BootcNodePool
79+
err := env.Client.Get(ctx, client.ObjectKeyFromObject(pool), &p)
80+
return p.Status.Conditions, err
81+
}).Should(ContainElement(And(
82+
HaveField("Type", bootcv1alpha1.PoolDegraded),
83+
HaveField("Status", metav1.ConditionTrue),
84+
HaveField("Reason", bootcv1alpha1.PoolSecretError),
85+
HaveField("Message", ContainSubstring(fmt.Sprintf("%q", deniedNS.Name))),
86+
)))
87+
g.Consistently(func() bool {
88+
var bn bootcv1alpha1.BootcNode
89+
err := env.Client.Get(ctx, client.ObjectKey{Name: nodeName}, &bn)
90+
return apierrors.IsNotFound(err)
91+
}).WithTimeout(10*time.Second).Should(BeTrue(),
92+
"no BootcNode should be created for a pool with a denied secret")
93+
94+
t.Logf("Pool %q is Degraded for secret in namespace %q", pool.Name, deniedNS.Name)
95+
96+
// Phase 2: a secret in the allowed namespace is used to pull the image.
97+
modified := pool.DeepCopy()
98+
modified.Spec.PullSecretRef = &bootcv1alpha1.PullSecretRef{
99+
Name: allowedSecret.Name, Namespace: allowedSecret.Namespace,
100+
}
101+
g.Expect(env.Client.Patch(ctx, modified, client.MergeFrom(pool))).To(Succeed())
102+
*pool = *modified
103+
104+
testutil.WaitForNodeIdle(t, g, ctx, env.Client, nodeName, 8*time.Minute,
105+
HaveField("Booted", HaveField("Image", Equal(authImageRef))),
106+
)
107+
g.Eventually(fetchPoolStatus(ctx, env.Client, pool)).
108+
Should(poolAllUpdated(1, env.NodeImageUpdateDigest()))
109+
110+
t.Logf("Node %q updated with a pull secret from namespace %q", nodeName, allowedNS)
111+
}
112+
113+
// registrySecret returns a dockerconfigjson Secret with the credentials of
114+
// the authenticated e2e registry.
115+
func registrySecret(env *e2eutil.Env, name, namespace string) *corev1.Secret {
116+
auth := base64.StdEncoding.EncodeToString(
117+
[]byte(env.RegistryUser() + ":" + env.RegistryPassword()),
118+
)
119+
return &corev1.Secret{
120+
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
121+
Type: corev1.SecretTypeDockerConfigJson,
122+
Data: map[string][]byte{
123+
corev1.DockerConfigJsonKey: fmt.Appendf(nil,
124+
`{"auths":{%q:{"auth":%q}}}`, env.RegistryHost(), auth),
125+
},
126+
}
127+
}

0 commit comments

Comments
 (0)