-
Notifications
You must be signed in to change notification settings - Fork 355
E2E test for TLS Parameters #1254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,259 @@ | ||
| /* | ||
| Copyright 2025. | ||
|
|
||
| Licensed under the Apache License, Version 2.0 (the "License"); | ||
| */ | ||
|
|
||
| package sequential | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "time" | ||
|
|
||
| appsv1 "k8s.io/api/apps/v1" | ||
| corev1 "k8s.io/api/core/v1" | ||
|
|
||
| argov1beta1api "github.com/argoproj-labs/argocd-operator/api/v1beta1" | ||
|
|
||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "k8s.io/apimachinery/pkg/types" | ||
|
|
||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
|
|
||
| . "github.com/onsi/ginkgo/v2" | ||
| . "github.com/onsi/gomega" | ||
|
|
||
| "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture" | ||
| osFixture "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/os" | ||
| "github.com/argoproj-labs/argocd-operator/tests/ginkgo/fixture/utils" | ||
| ) | ||
|
|
||
| var _ = Describe("Validate Deployment Env Args For TLS Configuration", func() { | ||
| const ( | ||
| argocdNamespace = "test-tls-argocd" | ||
| argocdInstanceName = "example-argocd" | ||
| ) | ||
| var ( | ||
| c client.Client | ||
| ctx context.Context | ||
| ) | ||
| BeforeEach(func() { | ||
| fixture.EnsureSequentialCleanSlate() | ||
| c, _ = utils.GetE2ETestKubeClient() | ||
| ctx = context.Background() | ||
| }) | ||
| BeforeEach(func() { | ||
| if fixture.EnvLocalRun() { | ||
| Skip("This test is known not to work when running gitops operator locally") | ||
| } | ||
| }) | ||
| // --- Helper: Extract TLS values from args --- | ||
| getTLSValues := func(args []string) (min string, hasMin bool, hasCiphers bool, ciphers string) { | ||
| for i := 0; i < len(args); i++ { | ||
| arg := args[i] | ||
| // handle --tlsminversion <value> | ||
| if arg == "--tlsminversion" { | ||
| hasMin = true | ||
| if i+1 < len(args) { | ||
| min = args[i+1] | ||
| } | ||
| } | ||
| if arg == "--tlsciphers" { | ||
| hasCiphers = true | ||
| if i+1 < len(args) { | ||
| ciphers = args[i+1] | ||
| } | ||
| } | ||
| // handle --tlsminversion=value | ||
| if len(arg) > len("--tlsminversion=") && arg[:len("--tlsminversion=")] == "--tlsminversion=" { | ||
| hasMin = true | ||
| min = arg[len("--tlsminversion="):] | ||
| } | ||
| if len(arg) > len("--tlsciphers=") && arg[:len("--tlsciphers=")] == "--tlsciphers=" { | ||
| hasCiphers = true | ||
| ciphers = arg[len("--tlsciphers="):] | ||
| } | ||
| } | ||
| return | ||
| } | ||
|
|
||
| Context("When the ArgoCD instance is created with default TLS settings", func() { | ||
| It("should validate default TLS values and updates on RepoServer, Server and Redis Deployments", func() { | ||
| ocVersion := getOCPVersion() | ||
| Expect(ocVersion).ToNot(BeEmpty()) | ||
|
|
||
| var major, minor int | ||
| _, err := fmt.Sscanf(ocVersion, "%d.%d", &major, &minor) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
|
|
||
| if major < 4 || (major == 4 && minor < 22) { | ||
| Skip(fmt.Sprintf("skipping this test as OCP version is %s, requires OCP >= 4.22", ocVersion)) | ||
| return | ||
| } | ||
| By("creating namespace") | ||
| ns := &corev1.Namespace{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: argocdNamespace, | ||
| }, | ||
| } | ||
| Expect(c.Create(ctx, ns)).To(Succeed()) | ||
|
|
||
| By("generating a test certificate to use with redis, using openssl") | ||
| redis_crt_File, err := os.CreateTemp("", "redis.crt") | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| redis_key_File, err := os.CreateTemp("", "redis.key") | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| openssl_test_File, err := os.CreateTemp("", "openssl_test.cnf") | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| opensslTestCNFContents := "\n[SAN]\nsubjectAltName=DNS:argocd-redis." + argocdNamespace + ".svc.cluster.local\n[req]\ndistinguished_name=req" | ||
|
|
||
| err = os.WriteFile(openssl_test_File.Name(), ([]byte)(opensslTestCNFContents), 0666) | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| _, err = osFixture.ExecCommandWithOutputParam(false, true, "openssl", "req", "-new", "-x509", "-sha256", | ||
| "-subj", "/C=XX/ST=XX/O=Testing/CN=redis", | ||
| "-reqexts", "SAN", | ||
| "-extensions", "SAN", | ||
| "-config", openssl_test_File.Name(), | ||
| "-keyout", redis_key_File.Name(), | ||
| "-out", redis_crt_File.Name(), | ||
| "-newkey", "rsa:4096", | ||
| "-nodes", | ||
| "-days", "10", | ||
| ) | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| By("creating argocd-operator-redis-tls secret from that cert") | ||
| _, err = osFixture.ExecCommand("kubectl", "create", "secret", "tls", "argocd-operator-redis-tls", "--key="+redis_key_File.Name(), "--cert="+redis_crt_File.Name(), "-n", argocdNamespace) | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
|
||
| By("adding argo cd label to argocd-operator-redis-tls secret") | ||
| _, err = osFixture.ExecCommand("kubectl", "annotate", "secret", "argocd-operator-redis-tls", "argocds.argoproj.io/name=argocd", "-n", argocdNamespace) | ||
| Expect(err).ToNot(HaveOccurred()) | ||
|
Comment on lines
+135
to
+137
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find how the redis TLS secret annotation/label is consumed in existing e2e tests and operator code.
rg -n -C5 'argocds\.argoproj\.io/name'
rg -n -C5 'argocd-operator-redis-tls'Repository: redhat-developer/gitops-operator Length of output: 170 🏁 Script executed: #!/bin/bash
set -eu
file="$(fd -t f '1-143_validate_deployment_Env_Args_For_Tls_Configuration_test.go' . | head -n1)"
printf '%s\n' "FILE=$file"
cat -n "$file" | sed -n '120,180p'
printf '%s\n' '--- related instance and secret references ---'
rg -n -C4 'example-argocd|argocd-operator-redis-tls|argocds\.argoproj\.io/name|argoproj\.io/name|redis.*tls' test controllers config deploy 2>/dev/null || true
printf '%s\n' '--- all references to the test helpers and resource names ---'
rg -n -C3 'Create\(.*Argo|New.*Argo|image-updater|image_updater|redis-tls' test/openshift/e2e/ginkgo/sequential 2>/dev/null || trueRepository: redhat-developer/gitops-operator Length of output: 50389 🏁 Script executed on selected repositories: Repositories: redhat-developer/gitops-operator, argoproj-labs/argocd-operator Length of output: 62056 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- annotation mapping implementation ---'
rg -n -C12 'func .*Map|AnnotationName|argocds\.argoproj\.io/namespace|Map with owner annotation|redis-operator-redis-tls|Secret.*Watch|Owns\(.*Secret' \
controllers/argocd api config 2>/dev/null | head -n 400
printf '%s\n' '--- secret reconciliation and watch registration ---'
rg -n -C10 'reconcileRedisTLSSecret|reconcileSecrets|RedisServerTLSSecretName|Watches|Owns' \
controllers/argocd 2>/dev/null | head -n 400Repository: argoproj-labs/argocd-operator Length of output: 50385 🏁 Script executed: #!/bin/bash
set -eu
cat -n controllers/argocd/custommapper.go | sed -n '1,180p'
printf '%s\n' '--- Redis TLS decision logic ---'
cat -n controllers/argocd/util.go | sed -n '475,530p'
printf '%s\n' '--- controller watch registration ---'
rg -n -C8 'setResourceWatches|tlsSecretMapper|tlsSecret' controllers/argocd/argocd_controller.go controllers/argocd/*.go | head -n 220Repository: argoproj-labs/argocd-operator Length of output: 26401 Use The TLS secret mapper uses 🤖 Prompt for AI Agents |
||
|
|
||
| By("creating ArgoCD instance") | ||
| argo := &argov1beta1api.ArgoCD{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: argocdInstanceName, | ||
| Namespace: argocdNamespace, | ||
| }, | ||
| Spec: argov1beta1api.ArgoCDSpec{}, | ||
| } | ||
| argo.Spec.ImageUpdater.Enabled = true | ||
| Expect(c.Create(ctx, argo)).To(Succeed()) | ||
| By("waiting for ArgoCD to be available") | ||
| Eventually(func() error { | ||
| return c.Get(ctx, types.NamespacedName{Name: argocdInstanceName, Namespace: argocdNamespace}, &argov1beta1api.ArgoCD{}) | ||
| }, 2*time.Minute, 5*time.Second).Should(Succeed()) | ||
| defer func() { | ||
| By("cleaning up resources") | ||
| _ = c.Delete(ctx, argo) | ||
| _ = c.Delete(ctx, ns) | ||
| os.Remove(redis_crt_File.Name()) | ||
| os.Remove(redis_key_File.Name()) | ||
| os.Remove(openssl_test_File.Name()) | ||
| }() | ||
|
Comment on lines
+95
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Register cleanup before creating cluster resources. The Move the cleanup registration to just after each resource is created. ♻️ Proposed restructuring Expect(c.Create(ctx, ns)).To(Succeed())
+ defer func() {
+ By("cleaning up namespace")
+ _ = c.Delete(ctx, ns)
+ }()
By("generating a test certificate to use with redis, using openssl")
redis_crt_File, err := os.CreateTemp("", "redis.crt")
Expect(err).ToNot(HaveOccurred())
+ defer os.Remove(redis_crt_File.Name())and reduce the later defer to the ArgoCD instance only: defer func() {
By("cleaning up resources")
_ = c.Delete(ctx, argo)
- _ = c.Delete(ctx, ns)
- os.Remove(redis_crt_File.Name())
- os.Remove(redis_key_File.Name())
- os.Remove(openssl_test_File.Name())
}()🧰 Tools🪛 ast-grep (0.45.1)[warning] 114-114: File mode grants world-writable permission; restrict the mode so other users cannot modify the file (e.g. 0o644 / 0o600). (world-writable-chmod-go) 🤖 Prompt for AI Agents |
||
| coreDeployments := []string{ | ||
| "example-argocd-server", | ||
| "example-argocd-repo-server", | ||
| "example-argocd-argocd-image-updater-controller", | ||
| } | ||
| time.Sleep(5 * time.Second) | ||
| // --- Validate updated TLS values --- | ||
| By("validating updated TLS args For RepoServer and Server") | ||
| Eventually(func() bool { | ||
| for _, deploymentName := range coreDeployments { | ||
| deployment := &appsv1.Deployment{} | ||
| if err := c.Get(ctx, types.NamespacedName{Name: deploymentName, Namespace: argocdNamespace}, deployment); err != nil { | ||
| return false | ||
| } | ||
| valid := false | ||
| for _, container := range deployment.Spec.Template.Spec.Containers { | ||
| min, hasMin, hasCiphers, ciphers := getTLSValues(container.Args) | ||
| if !hasMin { | ||
| continue | ||
| } | ||
| if min != "1.2" { | ||
| GinkgoWriter.Printf("%s: expected tlsminversion=1.2, got %s\n", deploymentName, min) | ||
| return false | ||
| } | ||
| if !hasCiphers || ciphers == "" { | ||
| GinkgoWriter.Printf("%s: expected --tlsciphers to be present and non-empty, got %q\n", deploymentName, ciphers) | ||
| return false | ||
| } | ||
| GinkgoWriter.Printf("%s updated TLS OK: min=%s\n", deploymentName, min) | ||
| valid = true | ||
| } | ||
| if !valid { | ||
| return false | ||
| } | ||
| } | ||
| return true | ||
| }, 60*time.Second, 2*time.Second).Should(BeTrue(), "all deployments should have updated TLS configuration") | ||
| By("Validating Updated TLS args in Redis deployment") | ||
| Eventually(func() bool { | ||
| deployment := &appsv1.Deployment{} | ||
| if err := c.Get(ctx, types.NamespacedName{Name: "example-argocd-redis", Namespace: argocdNamespace}, deployment); err != nil { | ||
| return false | ||
| } | ||
| if len(deployment.Spec.Template.Spec.Containers) == 0 { | ||
| return false | ||
| } | ||
| args := deployment.Spec.Template.Spec.Containers[0].Args | ||
| var tlsProtocols string | ||
| var tlsCiphersTLS12 string | ||
| var tlsCiphersTLS13 string | ||
| hasProtocols := false | ||
| hasCiphersTLS12 := false | ||
| hasCiphersTLS13 := false | ||
| for i := 0; i < len(args); i++ { | ||
| arg := args[i] | ||
| // --- Handle "--tls-protocols <value>" | ||
| if arg == "--tls-protocols" { | ||
| hasProtocols = true | ||
| if i+1 < len(args) { | ||
| tlsProtocols = args[i+1] | ||
| } | ||
| } | ||
| if arg == "--tls-ciphersuites" { | ||
| hasCiphersTLS13 = true | ||
| if i+1 < len(args) { | ||
| tlsCiphersTLS13 = args[i+1] | ||
| } | ||
| } | ||
|
|
||
| if arg == "--tls-ciphers" { | ||
| hasCiphersTLS12 = true | ||
| if i+1 < len(args) { | ||
| tlsCiphersTLS12 = args[i+1] | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // --- Print results (always helpful in debugging) | ||
| if !hasCiphersTLS13 || tlsCiphersTLS13 == "" { | ||
| GinkgoWriter.Printf(" --tls-ciphersuites should not be empty, got %q\n", tlsCiphersTLS13) | ||
| return false | ||
| } | ||
| if !hasCiphersTLS12 || tlsCiphersTLS12 == "" { | ||
| GinkgoWriter.Printf(" --tls-ciphers should not be empty, got %q\n", tlsCiphersTLS12) | ||
| return false | ||
| } | ||
|
|
||
| if !hasProtocols || tlsProtocols != "TLSv1.2" { | ||
| GinkgoWriter.Printf("%s: expected --tls-protocols=TLSv1.2, got %s\n", deployment.Name, tlsProtocols) | ||
| return false | ||
| } | ||
| GinkgoWriter.Printf("%s TLS args protocol value: %s\n", deployment.Name, tlsProtocols) | ||
| GinkgoWriter.Printf("%s TLS args ciphersuites value: %s\n", deployment.Name, tlsCiphersTLS13) | ||
| GinkgoWriter.Printf("%s TLS args ciphers value: %s\n", deployment.Name, tlsCiphersTLS12) | ||
| return true | ||
| }, 60*time.Second, 2*time.Second).Should(BeTrue()) | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
Close the temp files and restrict the config file mode.
os.CreateTempreturns open file handles that are never closed, and mode0666makes the OpenSSL config world-writable. The private key file is also written byopensslwith the default umask.🔒 Proposed fix
redis_crt_File, err := os.CreateTemp("", "redis.crt") Expect(err).ToNot(HaveOccurred()) + Expect(redis_crt_File.Close()).To(Succeed()) redis_key_File, err := os.CreateTemp("", "redis.key") Expect(err).ToNot(HaveOccurred()) + Expect(redis_key_File.Close()).To(Succeed()) openssl_test_File, err := os.CreateTemp("", "openssl_test.cnf") Expect(err).ToNot(HaveOccurred()) + Expect(openssl_test_File.Close()).To(Succeed()) opensslTestCNFContents := "\n[SAN]\nsubjectAltName=DNS:argocd-redis." + argocdNamespace + ".svc.cluster.local\n[req]\ndistinguished_name=req" - err = os.WriteFile(openssl_test_File.Name(), ([]byte)(opensslTestCNFContents), 0666) + err = os.WriteFile(openssl_test_File.Name(), ([]byte)(opensslTestCNFContents), 0600)📝 Committable suggestion
🧰 Tools
🪛 ast-grep (0.45.1)
[warning] 114-114: File mode grants world-writable permission; restrict the mode so other users cannot modify the file (e.g. 0o644 / 0o600).
Context: os.WriteFile(openssl_test_File.Name(), ([]byte)(opensslTestCNFContents), 0666)
Note: [CWE-276] Incorrect Default Permissions.
(world-writable-chmod-go)
🤖 Prompt for AI Agents
Source: Linters/SAST tools