Skip to content

Commit 45beca5

Browse files
committed
Added Mtls patch
(cherry picked from commit de2de96fc88022df783b637ccb145d1d73ba66ff)
1 parent 2fe0a52 commit 45beca5

File tree

5 files changed

+254
-7
lines changed

5 files changed

+254
-7
lines changed

config/rbac/role.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ rules:
4444
- subjectaccessreviews
4545
verbs:
4646
- create
47+
- apiGroups:
48+
- config.openshift.io
49+
resources:
50+
- ingresses
51+
verbs:
52+
- get
4753
- apiGroups:
4854
- ""
4955
resources:

main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252
"sigs.k8s.io/yaml"
5353

5454
routev1 "github.com/openshift/api/route/v1"
55+
clientset "github.com/openshift/client-go/config/clientset/versioned"
5556

5657
"github.com/project-codeflare/codeflare-operator/pkg/config"
5758
"github.com/project-codeflare/codeflare-operator/pkg/controllers"
@@ -75,6 +76,8 @@ func init() {
7576
utilruntime.Must(routev1.Install(scheme))
7677
}
7778

79+
// +kubebuilder:rbac:groups=config.openshift.io,resources=ingresses,verbs=get;
80+
7881
func main() {
7982
var configMapName string
8083
flag.StringVar(&configMapName, "config", "codeflare-operator-config",
@@ -117,6 +120,7 @@ func main() {
117120
KubeRay: &config.KubeRayConfiguration{
118121
RayDashboardOAuthEnabled: ptr.To(true),
119122
IngressDomain: "",
123+
MTLSEnabled: ptr.To(true),
120124
},
121125
}
122126

@@ -155,6 +159,13 @@ func main() {
155159
certsReady := make(chan struct{})
156160
exitOnError(setupCertManagement(mgr, namespace, certsReady), "unable to setup cert-controller")
157161

162+
if cfg.KubeRay.IngressDomain == "" {
163+
configClient, err := clientset.NewForConfig(kubeConfig)
164+
exitOnError(err, "unable to create Route Client Set")
165+
cfg.KubeRay.IngressDomain, err = getClusterDomain(ctx, configClient)
166+
exitOnError(err, cfg.KubeRay.IngressDomain)
167+
}
168+
158169
go setupControllers(mgr, kubeClient, cfg, isOpenShift(ctx, kubeClient.DiscoveryClient), certsReady)
159170

160171
setupLog.Info("setting up health endpoints")
@@ -332,3 +343,17 @@ func isOpenShift(ctx context.Context, dc discovery.DiscoveryInterface) bool {
332343
logger.Info("We detected being on Vanilla Kubernetes!")
333344
return false
334345
}
346+
347+
func getClusterDomain(ctx context.Context, configClient *clientset.Clientset) (string, error) {
348+
ingress, err := configClient.ConfigV1().Ingresses().Get(ctx, "cluster", metav1.GetOptions{})
349+
if err != nil {
350+
return "", fmt.Errorf("failed to get Ingress object: %v", err)
351+
}
352+
353+
domain := ingress.Spec.Domain
354+
if domain == "" {
355+
return "", fmt.Errorf("domain is not set in the Ingress object")
356+
}
357+
358+
return domain, nil
359+
}

pkg/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type KubeRayConfiguration struct {
3535
RayDashboardOAuthEnabled *bool `json:"rayDashboardOAuthEnabled,omitempty"`
3636

3737
IngressDomain string `json:"ingressDomain"`
38+
39+
MTLSEnabled *bool `json:"mTLSEnabled,omitempty"`
3840
}
3941

4042
type ControllerManager struct {

pkg/controllers/raycluster_webhook.go

Lines changed: 210 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controllers
1818

1919
import (
2020
"context"
21+
"strconv"
2122

2223
rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1"
2324

@@ -36,6 +37,7 @@ import (
3637
const (
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{}
6668
func (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+
}

pkg/controllers/support.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,14 @@ func withVolumeName(name string) compare[corev1.Volume] {
140140
return v1.Name == name
141141
}
142142
}
143+
144+
var byEnvVarName = compare[corev1.EnvVar](
145+
func(e1, e2 corev1.EnvVar) bool {
146+
return e1.Name == e2.Name
147+
})
148+
149+
func withEnvVarName(name string) compare[corev1.EnvVar] {
150+
return func(e1, e2 corev1.EnvVar) bool {
151+
return e1.Name == name
152+
}
153+
}

0 commit comments

Comments
 (0)