@@ -18,6 +18,7 @@ package controllers
1818
1919import (
2020 "context"
21+ "strconv"
2122
2223 rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
2324
@@ -36,6 +37,7 @@ import (
3637const (
3738 oauthProxyContainerName = "oauth-proxy"
3839 oauthProxyVolumeName = "proxy-tls-secret"
40+ initContainerName = "create-cert"
3941)
4042
4143// log is for logging in this package.
@@ -66,17 +68,44 @@ var _ webhook.CustomValidator = &rayClusterWebhook{}
6668func (w * rayClusterWebhook ) Default (ctx context.Context , obj runtime.Object ) error {
6769 rayCluster := obj .(* rayv1.RayCluster )
6870
69- if ! ptr .Deref (w .Config .RayDashboardOAuthEnabled , true ) {
70- return nil
71- }
71+ if ptr .Deref (w .Config .RayDashboardOAuthEnabled , true ) {
72+ rayclusterlog . V ( 2 ). Info ( "Adding OAuth sidecar container" )
73+ rayCluster . Spec . HeadGroupSpec . Template . Spec . Containers = upsert ( rayCluster . Spec . HeadGroupSpec . Template . Spec . Containers , oauthProxyContainer ( rayCluster ), withContainerName ( oauthProxyContainerName ))
7274
73- rayclusterlog . V ( 2 ). Info ( "Adding OAuth sidecar container" )
75+ rayCluster . Spec . HeadGroupSpec . Template . Spec . Volumes = upsert ( rayCluster . Spec . HeadGroupSpec . Template . Spec . Volumes , oauthProxyTLSSecretVolume ( rayCluster ), withVolumeName ( oauthProxyVolumeName ) )
7476
75- rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers , oauthProxyContainer (rayCluster ), withContainerName (oauthProxyContainerName ))
77+ rayCluster .Spec .HeadGroupSpec .Template .Spec .ServiceAccountName = rayCluster .Name + "-oauth-proxy"
78+ }
7679
77- rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , oauthProxyTLSSecretVolume (rayCluster ), withVolumeName (oauthProxyVolumeName ))
80+ if ptr .Deref (w .Config .MTLSEnabled , true ) {
81+ rayclusterlog .V (2 ).Info ("Adding create-cert Init Containers" )
82+ // HeadGroupSpec //
83+ // Append the list of environment variables for the ray-head container
84+ for _ , envVar := range envVarList () {
85+ rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env , envVar , withEnvVarName (envVar .Name ))
86+ }
87+
88+ // Append the create-cert Init Container
89+ rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers , rayHeadInitContainer (rayCluster , w .Config .IngressDomain ), withContainerName (initContainerName ))
90+
91+ // Append the CA volumes
92+ for _ , caVol := range caVolumes (rayCluster ) {
93+ rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes = upsert (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , caVol , withVolumeName (caVol .Name ))
94+ }
95+ // WorkerGroupSpec //
96+ // Append the list of environment variables for the worker container
97+ for _ , envVar := range envVarList () {
98+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env , envVar , withEnvVarName (envVar .Name ))
99+ }
100+
101+ // Append the CA volumes
102+ for _ , caVol := range caVolumes (rayCluster ) {
103+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes , caVol , withVolumeName (caVol .Name ))
104+ }
105+ // Append the create-cert Init Container
106+ rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers = upsert (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers , rayWorkerInitContainer (), withContainerName (initContainerName ))
78107
79- rayCluster . Spec . HeadGroupSpec . Template . Spec . ServiceAccountName = rayCluster . Name + "-oauth-proxy"
108+ }
80109
81110 return nil
82111}
@@ -117,6 +146,14 @@ func (w *rayClusterWebhook) ValidateUpdate(ctx context.Context, oldObj, newObj r
117146 allErrors = append (allErrors , validateHeadGroupServiceAccountName (rayCluster )... )
118147 }
119148
149+ // Init Container related errors
150+ if ptr .Deref (w .Config .MTLSEnabled , true ) {
151+ allErrors = append (allErrors , validateHeadInitContainer (rayCluster , w )... )
152+ allErrors = append (allErrors , validateWorkerInitContainer (rayCluster )... )
153+ allErrors = append (allErrors , validateHeadEnvVars (rayCluster )... )
154+ allErrors = append (allErrors , validateWorkerEnvVars (rayCluster )... )
155+ allErrors = append (allErrors , validateCaVolumes (rayCluster )... )
156+ }
120157 return warnings , allErrors .ToAggregate ()
121158}
122159
@@ -225,3 +262,169 @@ func oauthProxyTLSSecretVolume(rayCluster *rayv1.RayCluster) corev1.Volume {
225262 },
226263 }
227264}
265+
266+ func initCaVolumeMounts () []corev1.VolumeMount {
267+ return []corev1.VolumeMount {
268+ {
269+ Name : "ca-vol" ,
270+ MountPath : "/home/ray/workspace/ca" ,
271+ ReadOnly : true ,
272+ },
273+ {
274+ Name : "server-cert" ,
275+ MountPath : "/home/ray/workspace/tls" ,
276+ ReadOnly : false ,
277+ },
278+ }
279+ }
280+
281+ func envVarList () []corev1.EnvVar {
282+ return []corev1.EnvVar {
283+ {
284+ Name : "MY_POD_IP" ,
285+ ValueFrom : & corev1.EnvVarSource {
286+ FieldRef : & corev1.ObjectFieldSelector {
287+ FieldPath : "status.podIP" ,
288+ },
289+ },
290+ },
291+ {
292+ Name : "RAY_USE_TLS" ,
293+ Value : "1" ,
294+ },
295+ {
296+ Name : "RAY_TLS_SERVER_CERT" ,
297+ Value : "/home/ray/workspace/tls/server.crt" ,
298+ },
299+ {
300+ Name : "RAY_TLS_SERVER_KEY" ,
301+ Value : "/home/ray/workspace/tls/server.key" ,
302+ },
303+ {
304+ Name : "RAY_TLS_CA_CERT" ,
305+ Value : "/home/ray/workspace/tls/ca.crt" ,
306+ },
307+ }
308+ }
309+
310+ func caVolumes (rayCluster * rayv1.RayCluster ) []corev1.Volume {
311+ return []corev1.Volume {
312+ {
313+ Name : "ca-vol" ,
314+ VolumeSource : corev1.VolumeSource {
315+ Secret : & corev1.SecretVolumeSource {
316+ SecretName : `ca-secret-` + rayCluster .Name ,
317+ },
318+ },
319+ },
320+ {
321+ Name : "server-cert" ,
322+ VolumeSource : corev1.VolumeSource {
323+ EmptyDir : & corev1.EmptyDirVolumeSource {},
324+ },
325+ },
326+ }
327+ }
328+
329+ func rayHeadInitContainer (rayCluster * rayv1.RayCluster , domain string ) corev1.Container {
330+ rayClientRoute := "rayclient-" + rayCluster .Name + "-" + rayCluster .Namespace + "." + domain
331+ // Service name for basic interactive
332+ svcDomain := rayCluster .Name + "-head-svc." + rayCluster .Namespace + ".svc"
333+
334+ initContainerHead := corev1.Container {
335+ Name : "create-cert" ,
336+ Image : "quay.io/project-codeflare/ray:latest-py39-cu118" ,
337+ Command : []string {
338+ "sh" ,
339+ "-c" ,
340+ `cd /home/ray/workspace/tls && openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj '/CN=ray-head' && printf "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = 127.0.0.1\nDNS.2 = localhost\nDNS.3 = ${FQ_RAY_IP}\nDNS.4 = $(awk 'END{print $1}' /etc/hosts)\nDNS.5 = ` + rayClientRoute + `\nDNS.6 = ` + svcDomain + `">./domain.ext && cp /home/ray/workspace/ca/* . && openssl x509 -req -CA ca.crt -CAkey ca.key -in server.csr -out server.crt -days 365 -CAcreateserial -extfile domain.ext` ,
341+ },
342+ VolumeMounts : initCaVolumeMounts (),
343+ }
344+ return initContainerHead
345+ }
346+
347+ func rayWorkerInitContainer () corev1.Container {
348+ initContainerWorker := corev1.Container {
349+ Name : "create-cert" ,
350+ Image : "quay.io/project-codeflare/ray:latest-py39-cu118" ,
351+ Command : []string {
352+ "sh" ,
353+ "-c" ,
354+ `cd /home/ray/workspace/tls && openssl req -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj '/CN=ray-head' && printf "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nsubjectAltName = @alt_names\n[alt_names]\nDNS.1 = 127.0.0.1\nDNS.2 = localhost\nDNS.3 = ${FQ_RAY_IP}\nDNS.4 = $(awk 'END{print $1}' /etc/hosts)">./domain.ext && cp /home/ray/workspace/ca/* . && openssl x509 -req -CA ca.crt -CAkey ca.key -in server.csr -out server.crt -days 365 -CAcreateserial -extfile domain.ext` ,
355+ },
356+ VolumeMounts : initCaVolumeMounts (),
357+ }
358+ return initContainerWorker
359+ }
360+
361+ func validateHeadInitContainer (rayCluster * rayv1.RayCluster , w * rayClusterWebhook ) field.ErrorList {
362+ var allErrors field.ErrorList
363+
364+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .InitContainers , rayHeadInitContainer (rayCluster , w .Config .IngressDomain ), byContainerName ,
365+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "initContainers" ),
366+ "create-cert Init Container is immutable" ); err != nil {
367+ allErrors = append (allErrors , err )
368+ }
369+
370+ return allErrors
371+ }
372+
373+ func validateWorkerInitContainer (rayCluster * rayv1.RayCluster ) field.ErrorList {
374+ var allErrors field.ErrorList
375+
376+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .InitContainers , rayWorkerInitContainer (), byContainerName ,
377+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "initContainers" ),
378+ "create-cert Init Container is immutable" ); err != nil {
379+ allErrors = append (allErrors , err )
380+ }
381+
382+ return allErrors
383+ }
384+
385+ func validateCaVolumes (rayCluster * rayv1.RayCluster ) field.ErrorList {
386+ var allErrors field.ErrorList
387+
388+ for _ , caVol := range caVolumes (rayCluster ) {
389+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .Volumes , caVol , byVolumeName ,
390+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "volumes" ),
391+ "ca-vol and server-cert Secret volumes are immutable" ); err != nil {
392+ allErrors = append (allErrors , err )
393+ }
394+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Volumes , caVol , byVolumeName ,
395+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "volumes" ),
396+ "ca-vol and server-cert Secret volumes are immutable" ); err != nil {
397+ allErrors = append (allErrors , err )
398+ }
399+ }
400+
401+ return allErrors
402+ }
403+
404+ func validateHeadEnvVars (rayCluster * rayv1.RayCluster ) field.ErrorList {
405+ var allErrors field.ErrorList
406+
407+ for _ , envVar := range envVarList () {
408+ if err := contains (rayCluster .Spec .HeadGroupSpec .Template .Spec .Containers [0 ].Env , envVar , byEnvVarName ,
409+ field .NewPath ("spec" , "headGroupSpec" , "template" , "spec" , "containers" , strconv .Itoa (0 ), "env" ),
410+ "RAY_TLS related environment variables are immutable" ); err != nil {
411+ allErrors = append (allErrors , err )
412+ }
413+ }
414+
415+ return allErrors
416+ }
417+
418+ func validateWorkerEnvVars (rayCluster * rayv1.RayCluster ) field.ErrorList {
419+ var allErrors field.ErrorList
420+
421+ for _ , envVar := range envVarList () {
422+ if err := contains (rayCluster .Spec .WorkerGroupSpecs [0 ].Template .Spec .Containers [0 ].Env , envVar , byEnvVarName ,
423+ field .NewPath ("spec" , "workerGroupSpecs" , "0" , "template" , "spec" , "containers" , strconv .Itoa (0 ), "env" ),
424+ "RAY_TLS related environment variables are immutable" ); err != nil {
425+ allErrors = append (allErrors , err )
426+ }
427+ }
428+
429+ return allErrors
430+ }
0 commit comments