Skip to content

Commit ffc494e

Browse files
committed
Add TLS for MCP sidecar service
Enable TLS on the MCP sidecar service using cert-manager with the internal CA issuer. When CaBundleSecretName is set (indicating TLS is active on the control plane), the controller provisions a TLS certificate for the MCP service DNS names via certmanager.EnsureCert(), mounts the cert secret into the MCP sidecar container at /etc/pki/tls/mcp/, and switches the service port from 8080 to 8443. The rhos-mcps config is updated to include TLS cert/key paths and use https for allowed origins. Changes: - internal/openstackclient/funcs.go: Add mcpTLSSecretName param to ClientPodSpec() for TLS secret volume mount; add tlsEnabled param to MCPConfigYAML() for TLS cert/key config and port selection - config/rbac/role.yaml, bindata/: Regenerated via make manifests and make bindata
1 parent 637694b commit ffc494e

2 files changed

Lines changed: 98 additions & 11 deletions

File tree

internal/controller/client/openstackclient_controller.go

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ import (
4141
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4242

4343
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
44+
"github.com/openstack-k8s-operators/lib-common/modules/certmanager"
4445
"github.com/openstack-k8s-operators/lib-common/modules/common"
46+
"github.com/openstack-k8s-operators/lib-common/modules/common/clusterdns"
4547
condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
4648
"github.com/openstack-k8s-operators/lib-common/modules/common/configmap"
4749
"github.com/openstack-k8s-operators/lib-common/modules/common/env"
@@ -83,6 +85,8 @@ func (r *OpenStackClientReconciler) GetLogger(ctx context.Context) logr.Logger {
8385
// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use
8486
// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch;patch
8587
// +kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch
88+
// +kubebuilder:rbac:groups=cert-manager.io,resources=issuers,verbs=get;list;watch
89+
// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create;update;patch;delete
8690

8791
// Reconcile -
8892
func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) {
@@ -308,7 +312,53 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
308312
instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage)
309313

310314
// Reconcile MCP sidecar resources when enabled
315+
mcpTLSSecretName := ""
311316
if instance.Spec.MCP != nil && instance.Spec.MCP.Enabled {
317+
mcpTLSEnabled := instance.Spec.CaBundleSecretName != ""
318+
319+
if mcpTLSEnabled {
320+
issuer, err := certmanager.GetIssuerByLabels(
321+
ctx, helper,
322+
instance.Namespace,
323+
map[string]string{certmanager.RootCAIssuerInternalLabel: ""},
324+
)
325+
if err != nil {
326+
instance.Status.Conditions.Set(condition.FalseCondition(
327+
clientv1.OpenStackClientReadyCondition,
328+
condition.ErrorReason,
329+
condition.SeverityWarning,
330+
clientv1.OpenStackClientReadyErrorMessage,
331+
err.Error()))
332+
return ctrl.Result{}, err
333+
}
334+
335+
clusterDomain := clusterdns.GetDNSClusterDomain()
336+
mcpSvcName := instance.Name + "-mcp"
337+
certRequest := certmanager.CertificateRequest{
338+
IssuerName: issuer.Name,
339+
CertName: mcpSvcName + "-tls",
340+
Hostnames: []string{
341+
fmt.Sprintf("%s.%s.svc", mcpSvcName, instance.Namespace),
342+
fmt.Sprintf("%s.%s.svc.%s", mcpSvcName, instance.Namespace, clusterDomain),
343+
},
344+
Labels: map[string]string{},
345+
}
346+
certSecret, ctrlResult, err := certmanager.EnsureCert(ctx, helper, certRequest, instance)
347+
if err != nil {
348+
instance.Status.Conditions.Set(condition.FalseCondition(
349+
clientv1.OpenStackClientReadyCondition,
350+
condition.ErrorReason,
351+
condition.SeverityWarning,
352+
clientv1.OpenStackClientReadyErrorMessage,
353+
err.Error()))
354+
return ctrlResult, err
355+
} else if (ctrlResult != ctrl.Result{}) {
356+
return ctrlResult, nil
357+
}
358+
mcpTLSSecretName = certSecret.Name
359+
configVars[mcpTLSSecretName] = env.SetValue(certSecret.ResourceVersion)
360+
}
361+
312362
mcpConfigCM := &corev1.ConfigMap{
313363
ObjectMeta: metav1.ObjectMeta{
314364
Name: instance.Name + "-mcp-config",
@@ -317,14 +367,14 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
317367
}
318368
_, err = controllerutil.CreateOrPatch(ctx, r.Client, mcpConfigCM, func() error {
319369
mcpConfigCM.Data = map[string]string{
320-
"config.yaml": openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName),
370+
"config.yaml": openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName, mcpTLSEnabled),
321371
}
322372
return controllerutil.SetControllerReference(instance, mcpConfigCM, r.Scheme)
323373
})
324374
if err != nil {
325375
return ctrl.Result{}, fmt.Errorf("error creating MCP config ConfigMap: %w", err)
326376
}
327-
configVars[instance.Name+"-mcp-config"] = env.SetValue(openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName))
377+
configVars[instance.Name+"-mcp-config"] = env.SetValue(openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName, mcpTLSEnabled))
328378

329379
}
330380

@@ -335,6 +385,12 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
335385

336386
// Reconcile MCP Service after configVarsHash so the hash annotation captures all config changes
337387
if instance.Spec.MCP != nil && instance.Spec.MCP.Enabled {
388+
mcpTLSEnabled := instance.Spec.CaBundleSecretName != ""
389+
mcpPort := int32(8080)
390+
if mcpTLSEnabled {
391+
mcpPort = 8443
392+
}
393+
338394
mcpService := &corev1.Service{
339395
ObjectMeta: metav1.ObjectMeta{
340396
Name: instance.Name + "-mcp",
@@ -344,7 +400,7 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
344400
mcpServiceHash, err := util.ObjectHash(map[string]interface{}{
345401
"containerImage": instance.Spec.ContainerImage,
346402
"mcpContainerImage": instance.Spec.MCP.ContainerImage,
347-
"mcpConfig": openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName),
403+
"mcpConfig": openstackclient.MCPConfigYAML(instance.Spec.CaBundleSecretName, mcpTLSEnabled),
348404
"configVarsHash": configVarsHash,
349405
})
350406
if err != nil {
@@ -359,7 +415,7 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
359415
mcpService.Spec.Ports = []corev1.ServicePort{
360416
{
361417
Name: "mcp",
362-
Port: 8080,
418+
Port: mcpPort,
363419
Protocol: corev1.ProtocolTCP,
364420
},
365421
}
@@ -368,6 +424,7 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
368424
if err != nil {
369425
return ctrl.Result{}, fmt.Errorf("error creating MCP Service: %w", err)
370426
}
427+
371428
}
372429

373430
osclient := &corev1.Pod{
@@ -377,7 +434,7 @@ func (r *OpenStackClientReconciler) Reconcile(ctx context.Context, req ctrl.Requ
377434
},
378435
}
379436

380-
spec := openstackclient.ClientPodSpec(ctx, instance, helper, configVarsHash)
437+
spec := openstackclient.ClientPodSpec(ctx, instance, helper, configVarsHash, mcpTLSSecretName)
381438

382439
podSpecHash, err := util.ObjectHash(spec)
383440
if err != nil {

internal/openstackclient/funcs.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ func ClientPodSpec(
3434
instance *clientv1.OpenStackClient,
3535
helper *helper.Helper,
3636
configHash string,
37+
mcpTLSSecretName string,
3738
) corev1.PodSpec {
3839
envVars := map[string]env.Setter{}
3940
envVars["OS_CLOUD"] = env.SetValue("default")
@@ -135,6 +136,24 @@ func ClientPodSpec(
135136
mcpVolumeMounts = append(mcpVolumeMounts, instance.Spec.CreateVolumeMounts(nil)...)
136137
}
137138

139+
mcpPort := int32(8080)
140+
if mcpTLSSecretName != "" {
141+
mcpPort = 8443
142+
mcpVolumeMounts = append(mcpVolumeMounts, corev1.VolumeMount{
143+
Name: "mcp-tls",
144+
MountPath: "/etc/pki/tls/mcp",
145+
ReadOnly: true,
146+
})
147+
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
148+
Name: "mcp-tls",
149+
VolumeSource: corev1.VolumeSource{
150+
Secret: &corev1.SecretVolumeSource{
151+
SecretName: mcpTLSSecretName,
152+
},
153+
},
154+
})
155+
}
156+
138157
podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{
139158
Name: "mcp-config",
140159
VolumeSource: corev1.VolumeSource{
@@ -156,7 +175,7 @@ func ClientPodSpec(
156175
{Name: "OS_CLIENT_CONFIG_FILE", Value: "/home/cloud-admin/.config/openstack/clouds.yaml"},
157176
},
158177
Ports: []corev1.ContainerPort{
159-
{Name: "mcp", ContainerPort: 8080, Protocol: corev1.ProtocolTCP},
178+
{Name: "mcp", ContainerPort: mcpPort, Protocol: corev1.ProtocolTCP},
160179
},
161180
SecurityContext: &corev1.SecurityContext{
162181
RunAsUser: ptr.To[int64](42401),
@@ -179,24 +198,35 @@ func ClientPodSpec(
179198
}
180199

181200
// MCPConfigYAML returns the rhos-mcps config.yaml content for the MCP sidecar
182-
func MCPConfigYAML(caBundleSecretName string) string {
201+
func MCPConfigYAML(caBundleSecretName string, tlsEnabled bool) string {
183202
caCert := ""
184203
if caBundleSecretName != "" {
185204
caCert = fmt.Sprintf("\n ca_cert: %s", tls.DownstreamTLSCABundlePath)
186205
}
187-
return fmt.Sprintf(`port: 8080
206+
port := "8080"
207+
tlsConfig := ""
208+
allowedOriginScheme := "http"
209+
if tlsEnabled {
210+
port = "8443"
211+
tlsConfig = `
212+
tls:
213+
cert_file: /etc/pki/tls/mcp/tls.crt
214+
key_file: /etc/pki/tls/mcp/tls.key`
215+
allowedOriginScheme = "https"
216+
}
217+
return fmt.Sprintf(`port: %s
188218
openstack:
189219
enabled: true
190220
allow_write: false%s
191221
openshift:
192-
enabled: false
222+
enabled: false%s
193223
mcp_transport_security:
194224
enable_dns_rebinding_protection: false
195225
allowed_hosts:
196226
- "*:*"
197227
allowed_origins:
198-
- "http://*:*"
199-
`, caCert)
228+
- "%s://*:*"
229+
`, port, caCert, tlsConfig, allowedOriginScheme)
200230
}
201231

202232
func clientPodVolumeMounts() []corev1.VolumeMount {

0 commit comments

Comments
 (0)