From df442f6eb8fcea7d3a682c44ef7de4dffdfdbbc2 Mon Sep 17 00:00:00 2001 From: Pablo Acevedo Montserrat Date: Mon, 8 Sep 2025 10:21:05 +0200 Subject: [PATCH 1/4] OCPEDGE-2037: Add etcd CA secret --- pkg/components/etcd.go | 131 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 pkg/components/etcd.go diff --git a/pkg/components/etcd.go b/pkg/components/etcd.go new file mode 100644 index 0000000000..7c7b778e05 --- /dev/null +++ b/pkg/components/etcd.go @@ -0,0 +1,131 @@ +package components + +import ( + "context" + "fmt" + "os" + + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + + "github.com/openshift/microshift/pkg/config" + "github.com/openshift/microshift/pkg/util/cryptomaterial" +) + +const ( + // Secret name for etcd CA certificate + etcdCASecretName = "microshift-etcd-ca" + // Secret namespace + etcdCASecretNamespace = "kube-system" +) + +func startEtcdController(ctx context.Context, cfg *config.Config, kubeconfigPath string) error { + client, err := getKubernetesClient(kubeconfigPath) + if err != nil { + return fmt.Errorf("failed to get Kubernetes client: %w", err) + } + err = exposeEtcdCA(ctx, client) + if err != nil { + return fmt.Errorf("failed to expose etcd CA: %w", err) + } + err = createClusterRole(ctx, client) + if err != nil { + return fmt.Errorf("failed to create etcd CA admin Role: %w", err) + } + return nil +} + +func getKubernetesClient(kubeconfigPath string) (kubernetes.Interface, error) { + restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) + if err != nil { + return nil, err + } + return kubernetes.NewForConfig(restConfig) +} + +func createClusterRole(ctx context.Context, client kubernetes.Interface) error { + role := &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: "microshift-etcd-ca-admin", + Namespace: etcdCASecretNamespace, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"secrets"}, + ResourceNames: []string{etcdCASecretName}, + Verbs: []string{"*"}, + }, + }, + } + + _, err := client.RbacV1().Roles(etcdCASecretNamespace).Create(ctx, role, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create etcd CA admin Role: %w", err) + } + + roleBinding := &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: "microshift-etcd-ca-admin-binding", + Namespace: etcdCASecretNamespace, + }, + Subjects: []rbacv1.Subject{ + { + Kind: "Group", + Name: "system:masters", + APIGroup: rbacv1.GroupName, + }, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: "microshift-etcd-ca-admin", + }, + } + + _, err = client.RbacV1().RoleBindings(etcdCASecretNamespace).Create(ctx, roleBinding, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create etcd CA admin RoleBinding: %w", err) + } + + return nil +} + +func exposeEtcdCA(ctx context.Context, client kubernetes.Interface) error { + certsDir := cryptomaterial.CertsDirectory(config.DataDir) + etcdSignerDir := cryptomaterial.EtcdSignerDir(certsDir) + etcdCACertPath := cryptomaterial.CACertPath(etcdSignerDir) + etcdCAKeyPath := cryptomaterial.CAKeyPath(etcdSignerDir) + + caCert, err := os.ReadFile(etcdCACertPath) + if err != nil { + return fmt.Errorf("failed to read etcd CA certificate from %s: %w", etcdCACertPath, err) + } + + caKey, err := os.ReadFile(etcdCAKeyPath) + if err != nil { + return fmt.Errorf("failed to read etcd CA key from %s: %w", etcdCAKeyPath, err) + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: etcdCASecretName, + Namespace: etcdCASecretNamespace, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "ca.crt": caCert, + "ca.key": caKey, + }, + } + + _, err = client.CoreV1().Secrets(etcdCASecretNamespace).Create(ctx, secret, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("failed to create etcd CA secret: %w", err) + } + return nil +} From a1a1147eb61671fc2aa087f17a7aabbbbf735c29 Mon Sep 17 00:00:00 2001 From: Pablo Acevedo Montserrat Date: Mon, 8 Sep 2025 10:21:39 +0200 Subject: [PATCH 2/4] OCPEDGE-2037: Add etcd controller --- pkg/components/components.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/components/components.go b/pkg/components/components.go index ed1ca8cb09..930a55348f 100755 --- a/pkg/components/components.go +++ b/pkg/components/components.go @@ -44,5 +44,10 @@ func StartComponents(cfg *config.Config, ctx context.Context) error { return err } + if err := startEtcdController(ctx, cfg, kubeAdminConfig); err != nil { + klog.Warningf("Failed to start etcd controller: %v", err) + return err + } + return nil } From f4b7837513ea59e97512cee777a8b8e775aa0042 Mon Sep 17 00:00:00 2001 From: Pablo Acevedo Montserrat Date: Mon, 8 Sep 2025 10:30:35 +0200 Subject: [PATCH 3/4] OCPEDGE-2037: Add serial --- pkg/components/etcd.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/components/etcd.go b/pkg/components/etcd.go index 7c7b778e05..65dd84e75d 100644 --- a/pkg/components/etcd.go +++ b/pkg/components/etcd.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -111,6 +112,11 @@ func exposeEtcdCA(ctx context.Context, client kubernetes.Interface) error { return fmt.Errorf("failed to read etcd CA key from %s: %w", etcdCAKeyPath, err) } + serial, err := os.ReadFile(filepath.Join(etcdSignerDir, "serial.txt")) + if err != nil { + return fmt.Errorf("failed to read CA serial: %w", err) + } + secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: etcdCASecretName, @@ -118,8 +124,9 @@ func exposeEtcdCA(ctx context.Context, client kubernetes.Interface) error { }, Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ - "ca.crt": caCert, - "ca.key": caKey, + "ca.crt": caCert, + "ca.key": caKey, + "serial.txt": serial, }, } From 8f7711d6730a7c0a7770c48e97da45be038b51a7 Mon Sep 17 00:00:00 2001 From: Pablo Acevedo Montserrat Date: Mon, 8 Sep 2025 12:33:23 +0200 Subject: [PATCH 4/4] OCPEDGE-2037: Nits --- pkg/components/components.go | 2 +- pkg/components/etcd.go | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/components/components.go b/pkg/components/components.go index 930a55348f..49815ddd69 100755 --- a/pkg/components/components.go +++ b/pkg/components/components.go @@ -44,7 +44,7 @@ func StartComponents(cfg *config.Config, ctx context.Context) error { return err } - if err := startEtcdController(ctx, cfg, kubeAdminConfig); err != nil { + if err := startEtcdController(ctx, kubeAdminConfig); err != nil { klog.Warningf("Failed to start etcd controller: %v", err) return err } diff --git a/pkg/components/etcd.go b/pkg/components/etcd.go index 65dd84e75d..efb9338256 100644 --- a/pkg/components/etcd.go +++ b/pkg/components/etcd.go @@ -18,13 +18,13 @@ import ( ) const ( - // Secret name for etcd CA certificate - etcdCASecretName = "microshift-etcd-ca" - // Secret namespace - etcdCASecretNamespace = "kube-system" + // Resource name for etcd CA certificate + etcdCAResourceName = "microshift-etcd-ca" + // Resource namespace + etcdCAResourceNamespace = "kube-system" ) -func startEtcdController(ctx context.Context, cfg *config.Config, kubeconfigPath string) error { +func startEtcdController(ctx context.Context, kubeconfigPath string) error { client, err := getKubernetesClient(kubeconfigPath) if err != nil { return fmt.Errorf("failed to get Kubernetes client: %w", err) @@ -40,7 +40,7 @@ func startEtcdController(ctx context.Context, cfg *config.Config, kubeconfigPath return nil } -func getKubernetesClient(kubeconfigPath string) (kubernetes.Interface, error) { +func getKubernetesClient(kubeconfigPath string) (*kubernetes.Clientset, error) { restConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath) if err != nil { return nil, err @@ -52,19 +52,19 @@ func createClusterRole(ctx context.Context, client kubernetes.Interface) error { role := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: "microshift-etcd-ca-admin", - Namespace: etcdCASecretNamespace, + Namespace: etcdCAResourceNamespace, }, Rules: []rbacv1.PolicyRule{ { APIGroups: []string{""}, Resources: []string{"secrets"}, - ResourceNames: []string{etcdCASecretName}, + ResourceNames: []string{etcdCAResourceName}, Verbs: []string{"*"}, }, }, } - _, err := client.RbacV1().Roles(etcdCASecretNamespace).Create(ctx, role, metav1.CreateOptions{}) + _, err := client.RbacV1().Roles(etcdCAResourceNamespace).Create(ctx, role, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { return fmt.Errorf("failed to create etcd CA admin Role: %w", err) } @@ -72,7 +72,7 @@ func createClusterRole(ctx context.Context, client kubernetes.Interface) error { roleBinding := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "microshift-etcd-ca-admin-binding", - Namespace: etcdCASecretNamespace, + Namespace: etcdCAResourceNamespace, }, Subjects: []rbacv1.Subject{ { @@ -88,7 +88,7 @@ func createClusterRole(ctx context.Context, client kubernetes.Interface) error { }, } - _, err = client.RbacV1().RoleBindings(etcdCASecretNamespace).Create(ctx, roleBinding, metav1.CreateOptions{}) + _, err = client.RbacV1().RoleBindings(etcdCAResourceNamespace).Create(ctx, roleBinding, metav1.CreateOptions{}) if err != nil && !apierrors.IsAlreadyExists(err) { return fmt.Errorf("failed to create etcd CA admin RoleBinding: %w", err) } @@ -119,8 +119,8 @@ func exposeEtcdCA(ctx context.Context, client kubernetes.Interface) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: etcdCASecretName, - Namespace: etcdCASecretNamespace, + Name: etcdCAResourceName, + Namespace: etcdCAResourceNamespace, }, Type: corev1.SecretTypeOpaque, Data: map[string][]byte{ @@ -130,7 +130,7 @@ func exposeEtcdCA(ctx context.Context, client kubernetes.Interface) error { }, } - _, err = client.CoreV1().Secrets(etcdCASecretNamespace).Create(ctx, secret, metav1.CreateOptions{}) + _, err = client.CoreV1().Secrets(etcdCAResourceNamespace).Create(ctx, secret, metav1.CreateOptions{}) if err != nil { return fmt.Errorf("failed to create etcd CA secret: %w", err) }