@@ -16,6 +16,7 @@ import (
1616
1717 appsv1 "k8s.io/api/apps/v1"
1818 corev1 "k8s.io/api/core/v1"
19+ rbacv1 "k8s.io/api/rbac/v1"
1920 apierrors "k8s.io/apimachinery/pkg/api/errors"
2021 "k8s.io/apimachinery/pkg/api/resource"
2122 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -52,13 +53,14 @@ func EnsureProviderServe(
5253
5354 name := cr .Name
5455 providerSecret := name + "-provider-kubeconfig"
55- runtimeSecret := name + "-runtime-kubeconfig"
5656 if err := upsertOpaqueSecret (ctx , cs , ServeNamespace , providerSecret , "kubeconfig" , providerKubeconfig ); err != nil {
5757 return fmt .Errorf ("replicate provider kubeconfig: %w" , err )
5858 }
59- if err := upsertOpaqueSecret (ctx , cs , ServeNamespace , runtimeSecret , "kubeconfig" , runtimeKubeconfig ); err != nil {
60- return fmt .Errorf ("replicate runtime kubeconfig: %w" , err )
61- }
59+
60+ // inCluster: the runtime is the operator's own cluster (no runtime
61+ // kubeconfig). The serve pod then runs the kro backend with its pod
62+ // ServiceAccount (in-cluster) instead of a mounted runtime kubeconfig.
63+ inCluster := len (runtimeKubeconfig ) == 0
6264
6365 port := cr .Spec .Provider .Port
6466 if port == 0 {
@@ -73,7 +75,6 @@ func EnsureProviderServe(
7375 {Name : "PORT" , Value : fmt .Sprintf ("%d" , port )},
7476 {Name : "KEDGE_PROVIDER_NAME" , Value : "infrastructure" },
7577 {Name : "INFRASTRUCTURE_KUBECONFIG" , Value : providerKubeconfigMount },
76- {Name : "KRO_KUBECONFIG" , Value : runtimeKubeconfigMount },
7778 }
7879 if cr .Spec .Hub .URL != "" {
7980 env = append (env , corev1.EnvVar {Name : "KEDGE_HUB_URL" , Value : cr .Spec .Hub .URL })
@@ -83,11 +84,30 @@ func EnsureProviderServe(
8384 }
8485 volMounts := []corev1.VolumeMount {
8586 {Name : "provider-kubeconfig" , MountPath : "/var/run/secrets/kedge/provider" , ReadOnly : true },
86- {Name : "runtime-kubeconfig" , MountPath : "/var/run/secrets/kedge/runtime" , ReadOnly : true },
8787 }
8888 volumes := []corev1.Volume {
8989 {Name : "provider-kubeconfig" , VolumeSource : corev1.VolumeSource {Secret : & corev1.SecretVolumeSource {SecretName : providerSecret }}},
90- {Name : "runtime-kubeconfig" , VolumeSource : corev1.VolumeSource {Secret : & corev1.SecretVolumeSource {SecretName : runtimeSecret }}},
90+ }
91+ // Serve's kro backend reaches the runtime cluster either via a mounted
92+ // runtime kubeconfig (explicit runtime) or its in-cluster SA (in-cluster
93+ // runtime — KRO_KUBECONFIG left unset; controller_manager falls back to
94+ // in-cluster).
95+ serveSA := ""
96+ if ! inCluster {
97+ runtimeSecret := name + "-runtime-kubeconfig"
98+ if err := upsertOpaqueSecret (ctx , cs , ServeNamespace , runtimeSecret , "kubeconfig" , runtimeKubeconfig ); err != nil {
99+ return fmt .Errorf ("replicate runtime kubeconfig: %w" , err )
100+ }
101+ env = append (env , corev1.EnvVar {Name : "KRO_KUBECONFIG" , Value : runtimeKubeconfigMount })
102+ volMounts = append (volMounts , corev1.VolumeMount {Name : "runtime-kubeconfig" , MountPath : "/var/run/secrets/kedge/runtime" , ReadOnly : true })
103+ volumes = append (volumes , corev1.Volume {Name : "runtime-kubeconfig" , VolumeSource : corev1.VolumeSource {Secret : & corev1.SecretVolumeSource {SecretName : runtimeSecret }}})
104+ } else {
105+ // Give the serve pod an SA bound to the access its kro backend needs on
106+ // the (operator's own) runtime cluster.
107+ serveSA = name
108+ if err := ensureServeRBAC (ctx , cs , serveSA ); err != nil {
109+ return fmt .Errorf ("serve RBAC: %w" , err )
110+ }
91111 }
92112 if cr .Spec .Hub .TokenSecret != nil && len (hubToken ) > 0 {
93113 hubSecret := name + "-hub-token"
@@ -117,6 +137,7 @@ func EnsureProviderServe(
117137 Template : corev1.PodTemplateSpec {
118138 ObjectMeta : metav1.ObjectMeta {Labels : labels },
119139 Spec : corev1.PodSpec {
140+ ServiceAccountName : serveSA ,
120141 Containers : []corev1.Container {{
121142 Name : "provider" ,
122143 Image : image ,
@@ -166,6 +187,37 @@ func EnsureProviderServe(
166187 return ensureServeService (ctx , cs , name , labels , port )
167188}
168189
190+ // ensureServeRBAC creates the serve pod's ServiceAccount (in ServeNamespace)
191+ // and binds it to cluster-admin so its in-cluster kro backend can author
192+ // RGD-defined instances, namespaces, and secrets on the runtime cluster. Used
193+ // only for the in-cluster runtime (no runtime kubeconfig). Scope down for
194+ // least privilege in hardened environments.
195+ func ensureServeRBAC (ctx context.Context , cs kubernetes.Interface , saName string ) error {
196+ sa := & corev1.ServiceAccount {ObjectMeta : metav1.ObjectMeta {Name : saName , Namespace : ServeNamespace }}
197+ if _ , err := cs .CoreV1 ().ServiceAccounts (ServeNamespace ).Get (ctx , saName , metav1.GetOptions {}); apierrors .IsNotFound (err ) {
198+ if _ , cerr := cs .CoreV1 ().ServiceAccounts (ServeNamespace ).Create (ctx , sa , metav1.CreateOptions {}); cerr != nil && ! apierrors .IsAlreadyExists (cerr ) {
199+ return fmt .Errorf ("create serve ServiceAccount: %w" , cerr )
200+ }
201+ } else if err != nil {
202+ return fmt .Errorf ("get serve ServiceAccount: %w" , err )
203+ }
204+
205+ crbName := "kedge-infrastructure-serve-" + saName
206+ crb := & rbacv1.ClusterRoleBinding {
207+ ObjectMeta : metav1.ObjectMeta {Name : crbName },
208+ RoleRef : rbacv1.RoleRef {APIGroup : "rbac.authorization.k8s.io" , Kind : "ClusterRole" , Name : "cluster-admin" },
209+ Subjects : []rbacv1.Subject {{Kind : "ServiceAccount" , Name : saName , Namespace : ServeNamespace }},
210+ }
211+ if _ , err := cs .RbacV1 ().ClusterRoleBindings ().Get (ctx , crbName , metav1.GetOptions {}); apierrors .IsNotFound (err ) {
212+ if _ , cerr := cs .RbacV1 ().ClusterRoleBindings ().Create (ctx , crb , metav1.CreateOptions {}); cerr != nil && ! apierrors .IsAlreadyExists (cerr ) {
213+ return fmt .Errorf ("create serve ClusterRoleBinding: %w" , cerr )
214+ }
215+ } else if err != nil {
216+ return fmt .Errorf ("get serve ClusterRoleBinding: %w" , err )
217+ }
218+ return nil
219+ }
220+
169221func ensureServeService (ctx context.Context , cs kubernetes.Interface , name string , labels map [string ]string , port int32 ) error {
170222 want := & corev1.Service {
171223 ObjectMeta : metav1.ObjectMeta {Name : name , Namespace : ServeNamespace , Labels : labels },
0 commit comments