Skip to content
588 changes: 284 additions & 304 deletions controllers/evalhub/build_test.go

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion controllers/evalhub/configmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
type ServiceConfig struct {
Port int `json:"port"`
Host string `json:"host,omitempty"`
DisableAuth bool `json:"disable_auth,omitempty"`
ReadyFile string `json:"ready_file"`
TerminationFile string `json:"termination_file"`
EvalInitImage string `json:"eval_init_image,omitempty"`
Expand Down Expand Up @@ -153,7 +154,8 @@ func (r *EvalHubReconciler) generateConfigData(ctx context.Context, instance *ev

config := EvalHubConfig{
Service: ServiceConfig{
Port: containerPort,
Port: evalHubAppPort,
DisableAuth: true,
ReadyFile: "/tmp/repo-ready",
TerminationFile: "/tmp/termination-log",
EvalInitImage: evalHubImage,
Expand All @@ -162,6 +164,7 @@ func (r *EvalHubReconciler) generateConfigData(ctx context.Context, instance *ev
EnvMappings: EnvMappings{
"PORT": "service.port",
"API_HOST": "service.host",
"DISABLE_AUTH": "service.disable_auth",
"TLS_CERT_FILE": "service.tls_cert_file",
"TLS_KEY_FILE": "service.tls_key_file",
"DB_URL": "database.url",
Expand Down
20 changes: 16 additions & 4 deletions controllers/evalhub/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,27 @@ const (

// Container configuration
containerName = "evalhub"
containerPort = 8443
// evalHubAppPort is where eval-hub binds TLS (loopback only); kube-rbac-proxy listens on servicePort.
evalHubAppPort = 8444
// evalHubHealthPath is forwarded by kube-rbac-proxy; use --ignore-paths so kubelet probes skip authn/z.
evalHubHealthPath = "/api/v1/health"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

// Service configuration
// Service configuration (public HTTPS targets kube-rbac-proxy on this port)
serviceName = "evalhub"
servicePort = 8443

// kube-rbac-proxy sidecar
kubeRBACProxyContainerName = "kube-rbac-proxy"
kubeRBACProxyConfigMountPath = "/etc/kube-rbac-proxy/auth.yaml"
evalHubAuthConfigMapKey = "auth.yaml"
kubeRBACProxyUpstreamCAMountPath = "/etc/kube-rbac-proxy/upstream-ca"
kubeRBACProxyHealthPort = 9443

// Configuration constants
configMapName = "trustyai-service-operator-config"
configMapEvalHubImageKey = "evalHubImage"
configMapName = "trustyai-service-operator-config"
configMapEvalHubImageKey = "evalHubImage"
configMapKubeRBACProxyImageKey = "kube-rbac-proxy"
defaultKubeRBACProxyImage = "quay.io/openshift/origin-kube-rbac-proxy:4.19"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@nbs-rh hard-coded version?

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.

This is the fallback if a version is not provided through the operator configmap. Since we do not own kube-rbac-proxy repo, it is better to stick to a default image that we know works well rather than use ":latest"? Btw, this version 4.19 does not have our changes with endpoint-specific SARs - it needs to be added to the configmap and here in code as default once we have an image.


// TLS configuration (OpenShift service serving certificates)
tlsSecretMountPath = "/etc/tls/private"
Expand Down
120 changes: 99 additions & 21 deletions controllers/evalhub/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -74,17 +75,22 @@ func (r *EvalHubReconciler) buildDeploymentSpec(ctx context.Context, instance *e
evalHubImage = defaultEvalHubImage
}

kubeRBACProxyImage, err := r.getKubeRBACProxyImage(ctx)
if err != nil {
log.FromContext(ctx).Error(err, "Error getting kube-rbac-proxy image from ConfigMap. Using the default image value of "+defaultKubeRBACProxyImage)
kubeRBACProxyImage = defaultKubeRBACProxyImage
}

// Build default environment variables
// EvalHub serves TLS directly using OpenShift service serving certificates.
// Auth is handled internally via SAR checks.
// EvalHub listens on loopback only; kube-rbac-proxy terminates TLS on servicePort and enforces SAR (auth.yaml).
defaultEnvVars := []corev1.EnvVar{
{
Name: "API_HOST",
Value: "0.0.0.0",
Value: "127.0.0.1",
},
{
Name: "PORT",
Value: fmt.Sprintf("%d", containerPort),
Value: fmt.Sprintf("%d", evalHubAppPort),
},
{
Name: "TLS_CERT_FILE",
Expand Down Expand Up @@ -116,7 +122,7 @@ func (r *EvalHubReconciler) buildDeploymentSpec(ctx context.Context, instance *e
},
{
Name: "SERVICE_URL",
Value: fmt.Sprintf("https://%s.%s.svc.cluster.local:8443", instance.Name, instance.Namespace),
Value: fmt.Sprintf("https://%s.%s.svc.cluster.local:%d", instance.Name, instance.Namespace, servicePort),
},
{
Name: "EVALHUB_INSTANCE_NAME",
Expand Down Expand Up @@ -191,40 +197,103 @@ func (r *EvalHubReconciler) buildDeploymentSpec(ctx context.Context, instance *e
ImagePullPolicy: corev1.PullAlways,
Ports: []corev1.ContainerPort{
{
Name: "https",
ContainerPort: containerPort,
Name: "evalhub",
ContainerPort: evalHubAppPort,
Protocol: corev1.ProtocolTCP,
},
},
Env: env,
Resources: defaultResourceRequirements,
SecurityContext: defaultSecurityContext,
VolumeMounts: volumeMounts,
// HTTPGet probes with HTTPS scheme — kubelet skips TLS verification for probe requests.
LivenessProbe: &corev1.Probe{
// Liveness/readiness run on kube-rbac-proxy (same URL path as clients) with --ignore-paths on evalHubHealthPath.
}

upstreamURL := fmt.Sprintf("http://127.0.0.1:%d/", evalHubAppPort)
upstreamCAPath := kubeRBACProxyUpstreamCAMountPath + "/" + serviceCACertFile

kubeRBACProxyContainer := corev1.Container{
Name: kubeRBACProxyContainerName,
Image: kubeRBACProxyImage,
ImagePullPolicy: corev1.PullIfNotPresent,
Args: []string{
"--secure-listen-address=0.0.0.0:" + fmt.Sprintf("%d", servicePort),
"--upstream=" + upstreamURL,

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.

@nbs-rh do we need these (--upstream-ca-file) if the upstream is HTTP only?

"--upstream-ca-file=" + upstreamCAPath,
"--config-file=" + kubeRBACProxyConfigMountPath,
"--tls-cert-file=" + tlsSecretMountPath + "/" + tlsCertFile,
"--tls-private-key-file=" + tlsSecretMountPath + "/" + tlsKeyFile,
"--proxy-endpoints-port=" + fmt.Sprintf("%d", kubeRBACProxyHealthPort),
"--ignore-paths=" + evalHubHealthPath,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"--auth-header-fields-enabled",
"--auth-header-user-field-name=X-User",
"--v=0",
},
Ports: []corev1.ContainerPort{
{
Name: "https",
ContainerPort: servicePort,
Protocol: corev1.ProtocolTCP,
},
{
Name: "proxy-healthz",
ContainerPort: kubeRBACProxyHealthPort,
Protocol: corev1.ProtocolTCP,
},
},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("50m"),
corev1.ResourceMemory: resource.MustParse("32Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@nbs-rh is it to be expected that these are hard-coded here?

corev1.ResourceMemory: resource.MustParse("64Mi"),
},
},
SecurityContext: defaultSecurityContext,
VolumeMounts: []corev1.VolumeMount{
{
Name: "evalhub-config",
MountPath: kubeRBACProxyConfigMountPath,
SubPath: evalHubAuthConfigMapKey,
ReadOnly: true,
},
{
Name: instance.Name + "-tls",
MountPath: tlsSecretMountPath,
ReadOnly: true,
},
{
Name: serviceCAVolumeName,
MountPath: kubeRBACProxyUpstreamCAMountPath,
ReadOnly: true,
},
},
ReadinessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/v1/health",
Port: intstr.FromInt(containerPort),
Path: evalHubHealthPath,
Port: intstr.FromInt(servicePort),
Scheme: corev1.URISchemeHTTPS,
},
},
InitialDelaySeconds: 30,
PeriodSeconds: 10,
InitialDelaySeconds: 10,
PeriodSeconds: 5,
TimeoutSeconds: 5,
FailureThreshold: 3,
},
ReadinessProbe: &corev1.Probe{
LivenessProbe: &corev1.Probe{
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Path: "/api/v1/health",
Port: intstr.FromInt(containerPort),
Path: evalHubHealthPath,
Port: intstr.FromInt(servicePort),
Scheme: corev1.URISchemeHTTPS,
},
},
InitialDelaySeconds: 10,
PeriodSeconds: 5,
TimeoutSeconds: 3,
InitialDelaySeconds: 30,
PeriodSeconds: 10,
TimeoutSeconds: 5,
FailureThreshold: 3,
},
}
Expand Down Expand Up @@ -309,14 +378,14 @@ func (r *EvalHubReconciler) buildDeploymentSpec(ctx context.Context, instance *e
},
})
}
// Pod template with EvalHub container and required volumes
// Pod template with EvalHub + kube-rbac-proxy and required volumes
podTemplate := corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
ServiceAccountName: generateServiceAccountName(instance),
Containers: []corev1.Container{container},
Containers: []corev1.Container{container, kubeRBACProxyContainer},
SecurityContext: defaultPodSecurityContext,
RestartPolicy: corev1.RestartPolicyAlways,
Volumes: volumes,
Expand Down Expand Up @@ -347,6 +416,15 @@ func (r *EvalHubReconciler) buildDeploymentSpec(ctx context.Context, instance *e
}, nil
}

// getKubeRBACProxyImage retrieves the kube-rbac-proxy image from the operator ConfigMap.
func (r *EvalHubReconciler) getKubeRBACProxyImage(ctx context.Context) (string, error) {
namespace := r.Namespace
if namespace == "" {
namespace = "trustyai-service-operator-system"
}
return utils.GetImageFromConfigMapWithFallback(ctx, r.Client, configMapKubeRBACProxyImageKey, configMapName, namespace, defaultKubeRBACProxyImage)
}

// getEvalHubImage retrieves the EvalHub image from ConfigMap with fallback to default
func (r *EvalHubReconciler) getEvalHubImage(ctx context.Context) (string, error) {
// Get the namespace where the operator is deployed (where the ConfigMap should be)
Expand Down
Loading
Loading