Skip to content
Merged
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
15 changes: 15 additions & 0 deletions controller/deploy/operator/api/v1alpha1/jumpstarter_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,21 @@ type TelemetryConfig struct {

// Logging configuration for the telemetry log ingestion path.
Logging TelemetryLoggingConfig `json:"logging,omitempty"`

// gRPC configuration for the telemetry service.
// Use this to configure TLS when not using cert-manager.
GRPC TelemetryGRPCConfig `json:"grpc,omitempty"`
}

// TelemetryGRPCConfig defines gRPC configuration for the telemetry service.
// This is a simplified version of GRPCConfig since telemetry is internal-only
// (ClusterIP) and doesn't need external endpoints or keepalive settings.
type TelemetryGRPCConfig struct {
// TLS configuration for secure gRPC communication with the telemetry service.
// When spec.certManager.enabled is true, this is ignored and certificates are
// automatically managed by cert-manager.
// When spec.certManager.enabled is false, you can provide your own TLS secret here.
TLS TLSConfig `json:"tls,omitempty"`
Comment thread
bkhizgiy marked this conversation as resolved.
}

// TelemetryLoggingConfig configures the log push path to the telemetry service.
Comment thread
mangelajo marked this conversation as resolved.
Expand Down
17 changes: 17 additions & 0 deletions controller/deploy/operator/api/v1alpha1/zz_generated.deepcopy.go

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 @@ -2104,6 +2104,28 @@ spec:
When enabled, the operator deploys a jumpstarter-telemetry pod and a ClusterIP
Service, and configures the controller to advertise the endpoint to exporters.
type: boolean
grpc:
description: |-
gRPC configuration for the telemetry service.
Use this to configure TLS when not using cert-manager.
properties:
tls:
description: |-
TLS configuration for secure gRPC communication with the telemetry service.
When spec.certManager.enabled is true, this is ignored and certificates are
automatically managed by cert-manager.
When spec.certManager.enabled is false, you can provide your own TLS secret here.
properties:
certSecret:
description: |-
Name of the Kubernetes secret containing the TLS certificate and private key.
The secret must contain 'tls.crt' and 'tls.key' keys.
If spec.certManager.enabled is true, this secret will be automatically managed and
configured by cert-manager.
pattern: ^[a-z0-9]([a-z0-9\-\.]*[a-z0-9])?$
type: string
type: object
type: object
image:
default: quay.io/jumpstarter-dev/jumpstarter-telemetry:latest
description: Container image for the telemetry pod in 'registry/repository/image:tag'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func (r *JumpstarterReconciler) reconcileRouterCertificate(ctx context.Context,

// reconcileTelemetryCertificate creates the TLS certificate for the telemetry service.
func (r *JumpstarterReconciler) reconcileTelemetryCertificate(ctx context.Context, js *operatorv1alpha1.Jumpstarter, issuerRef cmmeta.ObjectReference) error {
certName := getTelemetryCertSecretName(js)
certName := GetTelemetryCertSecretName(js)
includeInternalNames := !isExternalIssuer(js)
dnsNames := r.collectTelemetryDNSNames(js, includeInternalNames)
return r.reconcileServerCertificate(ctx, js, issuerRef, certName, "telemetry", dnsNames, nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,3 +429,166 @@ var _ = Describe("getRouterTLSSecretHash", func() {
Expect(hashMissing).To(BeEmpty())
})
})

var _ = Describe("getTelemetryTLSSecretHash", func() {
var r *JumpstarterReconciler

BeforeEach(func() {
r = &JumpstarterReconciler{Client: k8sClient}
})

It("should return empty hash when no TLS is configured", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "default",
},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: false},
Telemetry: &operatorv1alpha1.TelemetryConfig{
Enabled: true,
// No TLS.CertSecret configured
},
},
}
hash, err := r.getTelemetryTLSSecretHash(ctx, js)
Expect(err).NotTo(HaveOccurred())
Expect(hash).To(BeEmpty())
})

It("should return empty hash when cert-manager secret does not exist yet", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{
Name: "test-telemetry-hash",
Namespace: "default",
},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: true},
},
}
hash, err := r.getTelemetryTLSSecretHash(ctx, js)
Expect(err).NotTo(HaveOccurred())
Expect(hash).To(BeEmpty())
})

It("should return hash when cert-manager telemetry TLS secret exists", func() {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test-telemetry-tls-telemetry-tls",
Namespace: "default",
},
Data: map[string][]byte{
"tls.crt": []byte("telemetry-cert"),
"tls.key": []byte("telemetry-key"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
defer func() {
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
}()

js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{
Name: "test-telemetry-tls",
Namespace: "default",
},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: true},
},
}
hash, err := r.getTelemetryTLSSecretHash(ctx, js)
Expect(err).NotTo(HaveOccurred())
Expect(hash).To(HaveLen(64))
})

It("should return hash when manual telemetry TLS secret exists", func() {
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "my-manual-telemetry-tls",
Namespace: "default",
},
Data: map[string][]byte{
"tls.crt": []byte("manual-telemetry-cert"),
"tls.key": []byte("manual-telemetry-key"),
},
}
Expect(k8sClient.Create(ctx, secret)).To(Succeed())
defer func() {
Expect(k8sClient.Delete(ctx, secret)).To(Succeed())
}()

js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{
Name: "test-manual-tls",
Namespace: "default",
},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: false},
Telemetry: &operatorv1alpha1.TelemetryConfig{
Enabled: true,
GRPC: operatorv1alpha1.TelemetryGRPCConfig{
TLS: operatorv1alpha1.TLSConfig{
CertSecret: "my-manual-telemetry-tls",
},
},
},
},
}
hash, err := r.getTelemetryTLSSecretHash(ctx, js)
Expect(err).NotTo(HaveOccurred())
Expect(hash).To(HaveLen(64))
})
})

var _ = Describe("telemetryTLSSecretName", func() {
It("returns cert-manager secret name when cert-manager is enabled", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{Name: "my-js"},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: true},
},
}
Expect(telemetryTLSSecretName(js)).To(Equal("my-js-telemetry-tls"))
})

It("returns manual CertSecret when cert-manager is disabled", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{Name: "my-js"},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: false},
Telemetry: &operatorv1alpha1.TelemetryConfig{
Enabled: true,
GRPC: operatorv1alpha1.TelemetryGRPCConfig{
TLS: operatorv1alpha1.TLSConfig{
CertSecret: "custom-tls-secret",
},
},
},
},
}
Expect(telemetryTLSSecretName(js)).To(Equal("custom-tls-secret"))
})

It("returns empty string when no TLS is configured", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{Name: "my-js"},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: false},
Telemetry: &operatorv1alpha1.TelemetryConfig{
Enabled: true,
},
},
}
Expect(telemetryTLSSecretName(js)).To(BeEmpty())
})

It("returns empty string when telemetry is nil", func() {
js := &operatorv1alpha1.Jumpstarter{
ObjectMeta: metav1.ObjectMeta{Name: "my-js"},
Spec: operatorv1alpha1.JumpstarterSpec{
CertManager: operatorv1alpha1.CertManagerConfig{Enabled: false},
},
}
Expect(telemetryTLSSecretName(js)).To(BeEmpty())
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,13 @@ func (r *JumpstarterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, err
}

// Requeue after 30 minutes to check for changes
return ctrl.Result{RequeueAfter: 30 * time.Minute}, nil
// Requeue periodically to pick up changes. Use a shorter interval while the
// telemetry CA secret is not yet ready so the controller ConfigMap converges quickly.
requeueAfter := 30 * time.Minute
if r.telemetryCANeedsRequeue(ctx, &jumpstarter) {
requeueAfter = telemetryCARequeueInterval
}
return ctrl.Result{RequeueAfter: requeueAfter}, nil
}

// emitEventf emits a Kubernetes event on the Jumpstarter object.
Expand Down Expand Up @@ -1305,7 +1310,6 @@ func (r *JumpstarterReconciler) buildConfig(ctx context.Context, jumpstarter *op
}

// Telemetry configuration.
// Certificate is intentionally omitted until the telemetry binary supports TLS serving.
if jumpstarter.Spec.Telemetry != nil && jumpstarter.Spec.Telemetry.Enabled {
t := jumpstarter.Spec.Telemetry
telemetryCfg := &config.Telemetry{
Expand All @@ -1315,6 +1319,19 @@ func (r *JumpstarterReconciler) buildConfig(ctx context.Context, jumpstarter *op
if t.Logging.Filter.MinSeverity != "" {
telemetryCfg.Logging.Filter.MinSeverity = t.Logging.Filter.MinSeverity
}
// Include CA certificate when cert-manager is enabled so exporters can verify TLS
if jumpstarter.Spec.CertManager.Enabled {
caCert, err := r.resolveTelemetryCA(ctx, jumpstarter)
if err != nil {
// Log at default verbosity so operators notice during initial cert-manager setup.
// Reconciliation continues without a certificate; telemetryCANeedsRequeue
// triggers a short requeue until the CA secret is ready.
logf.FromContext(ctx).Info("Could not resolve telemetry CA certificate; exporters cannot verify telemetry TLS until the CA is available",
"error", err)
} else if caCert != "" {
telemetryCfg.Certificate = caCert
}
Comment thread
mangelajo marked this conversation as resolved.
}
cfg.Telemetry = telemetryCfg
}

Expand Down Expand Up @@ -1728,6 +1745,15 @@ func (r *JumpstarterReconciler) SetupWithManager(mgr ctrl.Manager) error {
keys = append(keys, jumpstarter.Namespace+"/"+s)
}

// Telemetry TLS cert secret
if jumpstarter.Spec.Telemetry != nil && jumpstarter.Spec.Telemetry.Enabled {
if jumpstarter.Spec.CertManager.Enabled {
keys = append(keys, jumpstarter.Namespace+"/"+GetTelemetryCertSecretName(jumpstarter))
} else if s := jumpstarter.Spec.Telemetry.GRPC.TLS.CertSecret; s != "" {
keys = append(keys, jumpstarter.Namespace+"/"+s)
}
}

return keys
},
); err != nil {
Expand Down
Loading
Loading