Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func ReconcileEtcdPeerSecret(secret, ca *corev1.Secret, ownerRef config.OwnerRef
dnsNames := []string{
fmt.Sprintf("*.etcd-discovery.%s.svc", secret.Namespace),
fmt.Sprintf("*.etcd-discovery.%s.svc.cluster.local", secret.Namespace),
// etcd-client headless service shares pod IPs with etcd-discovery; reverse DNS may resolve
// to etcd-client, so peer certs must include both service SANs to pass TLS verification.
fmt.Sprintf("*.etcd-client.%s.svc", secret.Namespace),
fmt.Sprintf("*.etcd-client.%s.svc.cluster.local", secret.Namespace),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we please add a // comment on the reasoning why this is needed so it's clear for humans/agents landing here without needing to trace back the PR desc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you @enxebre , I have added the comment as per the suggestion

// TODO(OCPBUGS-86648): move IPs to the ips parameter of reconcileSignedCertWithKeysAndAddresses.
"127.0.0.1",
"::1",
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package pki

import (
"crypto/x509"
"crypto/x509/pkix"
"testing"

. "github.com/onsi/gomega"

"github.com/openshift/hypershift/support/certs"
"github.com/openshift/hypershift/support/config"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

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

caCfg := certs.CertCfg{
IsCA: true,
Subject: pkix.Name{CommonName: "etcd-signer", OrganizationalUnit: []string{"openshift"}},
}
caKey, caCert, err := certs.GenerateSelfSignedCertificate(&caCfg)
if err != nil {
t.Fatalf("failed to generate CA: %v", err)
}
caSecret := &corev1.Secret{
Data: map[string][]byte{
certs.CASignerCertMapKey: certs.CertToPem(caCert),
certs.CASignerKeyMapKey: certs.PrivateKeyToPem(caKey),
},
}

t.Run("When reconciling etcd peer secret it should include both etcd-discovery and etcd-client SANs", func(t *testing.T) {
g := NewWithT(t)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "clusters-test",
},
}

err := ReconcileEtcdPeerSecret(secret, caSecret, config.OwnerRef{})
g.Expect(err).ToNot(HaveOccurred())

certData := secret.Data[EtcdPeerCrtKey]
g.Expect(certData).ToNot(BeEmpty())

cert, err := certs.PemToCertificate(certData)
g.Expect(err).ToNot(HaveOccurred())

g.Expect(cert.DNSNames).To(ContainElements(
"*.etcd-discovery.clusters-test.svc",
"*.etcd-discovery.clusters-test.svc.cluster.local",
"*.etcd-client.clusters-test.svc",
"*.etcd-client.clusters-test.svc.cluster.local",
// TODO(OCPBUGS-86648): assert on cert.IPAddresses instead once IPs are moved out of dnsNames.
"127.0.0.1",
"::1",
Comment on lines +58 to +59

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check how ReconcileEtcdPeerSecret handles IP addresses in certificate generation

# Search for the implementation of ReconcileEtcdPeerSecret
ast-grep --pattern $'func ReconcileEtcdPeerSecret($$$) $$$'

# Search for certificate generation calls that might include IPs
rg -A 10 -B 5 "127\.0\.0\.1|::1" --type go --glob '**/pki/etcd.go'

# Look for DNSNames and IPAddresses field assignments
rg -A 5 "DNSNames|IPAddresses" --type go --glob '**/pki/etcd.go'

Repository: openshift/hypershift

Length of output: 2726


🏁 Script executed:

# Find the reconcileSignedCertWithKeysAndAddresses function implementation
ast-grep --pattern $'func reconcileSignedCertWithKeysAndAddresses($$$) $$$'

# Also search for where DNSNames is actually set in the certificate
rg -B 5 -A 15 "DNSNames\s*=" --type go --glob '**/pki/*.go' | head -100

# Look for IPAddresses assignment in certificate generation
rg -B 5 -A 5 "IPAddresses\s*=" --type go --glob '**/pki/*.go'

Repository: openshift/hypershift

Length of output: 1123


🏁 Script executed:

# Find where certs.ReconcileSignedCert is imported from
rg "import.*certs" --type go --glob '**/pki/cert.go' -A 20 | head -40

# Find the certs package and ReconcileSignedCert implementation
find . -name "*.go" -type f -exec grep -l "func ReconcileSignedCert" {} \;

# Search for how it populates certificate fields
rg -B 5 -A 20 "func ReconcileSignedCert" --type go

Repository: openshift/hypershift

Length of output: 1324


🏁 Script executed:

# Get the full ReconcileSignedCert implementation
sed -n '1,300p' support/certs/tls.go | tail -200

Repository: openshift/hypershift

Length of output: 6056


🏁 Script executed:

# Find where the CSR is generated with dnsNames and ips in ReconcileSignedCert
sed -n '/^func ReconcileSignedCert/,/^func [A-Z]/p' support/certs/tls.go | head -100

Repository: openshift/hypershift

Length of output: 2748


🏁 Script executed:

# Get the test file context around lines 57-58
sed -n '35,75p' control-plane-operator/controllers/hostedcontrolplane/pki/etcd_test.go

Repository: openshift/hypershift

Length of output: 1305


Fix IP address handling in etcd peer certificate generation.

Lines 50–51 in etcd.go incorrectly add IP addresses ("127.0.0.1", "::1") to the dnsNames list. Per X.509 standards, IP addresses must be in the IPAddresses field, not DNSNames. The function signature supports a separate ips parameter for this purpose.

Fix the implementation by passing IPs via the dedicated parameter:

return reconcileSignedCertWithKeysAndAddresses(secret, ca, ownerRef, "etcd-discovery", []string{"kubernetes"}, X509UsageClientServerAuth, EtcdPeerCrtKey, EtcdPeerKeyKey, "", dnsNames, []string{"127.0.0.1", "::1"}, "")

Then update the test assertion to check the correct certificate field:

g.Expect(cert.IPAddresses).To(ContainElements(
    net.ParseIP("127.0.0.1"),
    net.ParseIP("::1"),
))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@control-plane-operator/controllers/hostedcontrolplane/pki/etcd_test.go`
around lines 57 - 58, The etcd peer cert is currently placing "127.0.0.1" and
"::1" into DNSNames; move those into the IPAddresses parameter when calling
reconcileSignedCertWithKeysAndAddresses by removing them from dnsNames and
passing them as the ips argument (use []string{"127.0.0.1", "::1"}) in the
reconcileSignedCertWithKeysAndAddresses call for "etcd-discovery"; then adjust
the test in etcd_test.go to assert on cert.IPAddresses (using net.ParseIP for
each IP) instead of cert.DNSNames so the test verifies IPs are present in the
certificate's IPAddresses field.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is encoding the behavior that should be fixed. Mind after creating the JIRA card reference it here in a comment with a TODO

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sdminonne ,
I have added the TODO comment and raised the JIRA card(OCPBUGS-86648) as per the suggestion.

))
})

t.Run("When reconciling etcd peer secret it should have client and server auth usage", func(t *testing.T) {
g := NewWithT(t)
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: "clusters-test",
},
}

err := ReconcileEtcdPeerSecret(secret, caSecret, config.OwnerRef{})
g.Expect(err).ToNot(HaveOccurred())

cert, err := certs.PemToCertificate(secret.Data[EtcdPeerCrtKey])
g.Expect(err).ToNot(HaveOccurred())

g.Expect(cert.ExtKeyUsage).To(ContainElements(
x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth,
))
})
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ metadata:
app: etcd
name: etcd-client
spec:
clusterIP: None
ports:
- name: etcd-client
port: 2379
Expand Down