Skip to content

Commit

Permalink
Inject certificate to http client from a configmap referenced in the …
Browse files Browse the repository at this point in the history
…config

Signed-off-by: ivinokur <[email protected]>
  • Loading branch information
vinokurig committed May 2, 2024
1 parent 04f5369 commit 4da785b
Show file tree
Hide file tree
Showing 7 changed files with 85 additions and 6 deletions.
8 changes: 8 additions & 0 deletions apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ type RoutingConfig struct {
// DevWorkspaces. However, changing the proxy configuration for the DevWorkspace Operator itself
// requires restarting the controller deployment.
ProxyConfig *Proxy `json:"proxyConfig,omitempty"`
// TLSCertificateConfigmapRef defines the name and namespace of the configmap with a certificate to inject to http
// client.
TLSCertificateConfigmapRef *ConfigmapReference `json:"tlsCertificateConfigmapRef,omitempty"`
}

type WorkspaceConfig struct {
Expand Down Expand Up @@ -240,6 +243,11 @@ type ProjectCloneConfig struct {
Env []corev1.EnvVar `json:"env,omitempty"`
}

type ConfigmapReference struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
}

// DevWorkspaceOperatorConfig is the Schema for the devworkspaceoperatorconfigs API
// +kubebuilder:object:root=true
// +kubebuilder:resource:path=devworkspaceoperatorconfigs,scope=Namespaced,shortName=dwoc
Expand Down
7 changes: 4 additions & 3 deletions controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,8 @@ func (r *DevWorkspaceReconciler) getWorkspaceId(ctx context.Context, workspace *
}
}

func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
setupHttpClients()

func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager, k8s client.Client) error {
setupHttpClients(k8s)
maxConcurrentReconciles, err := wkspConfig.GetMaxConcurrentReconciles()
if err != nil {
return err
Expand All @@ -681,6 +680,7 @@ func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {

configWatcher := builder.WithPredicates(wkspConfig.Predicates())
automountWatcher := builder.WithPredicates(automountPredicates)
certificateWatcher := builder.WithPredicates(certificatePredicates)

// TODO: Set up indexing https://book.kubebuilder.io/cronjob-tutorial/controller-implementation.html#setup
return ctrl.NewControllerManagedBy(mgr).
Expand All @@ -700,6 +700,7 @@ func (r *DevWorkspaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
Watches(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, handler.EnqueueRequestsFromMapFunc(r.dwPVCHandler)).
Watches(&source.Kind{Type: &corev1.Secret{}}, handler.EnqueueRequestsFromMapFunc(r.runningWorkspacesHandler), automountWatcher).
Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(r.runningWorkspacesHandler), automountWatcher).
Watches(&source.Kind{Type: &corev1.ConfigMap{}}, handler.EnqueueRequestsFromMapFunc(r.certificateHandler), certificateWatcher).
Watches(&source.Kind{Type: &corev1.PersistentVolumeClaim{}}, handler.EnqueueRequestsFromMapFunc(r.runningWorkspacesHandler), automountWatcher).
Watches(&source.Kind{Type: &controllerv1alpha1.DevWorkspaceOperatorConfig{}}, handler.EnqueueRequestsFromMapFunc(emptyMapper), configWatcher).
WithEventFilter(devworkspacePredicates).
Expand Down
10 changes: 10 additions & 0 deletions controllers/workspace/eventhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ package controllers

import (
"context"
corev1 "k8s.io/api/core/v1"
"net/http"

dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
wkspConfig "github.com/devfile/devworkspace-operator/pkg/config"
Expand Down Expand Up @@ -106,3 +108,11 @@ func (r *DevWorkspaceReconciler) runningWorkspacesHandler(obj client.Object) []r
}
return reconciles
}

func (r *DevWorkspaceReconciler) certificateHandler(obj client.Object) []reconcile.Request {
certsPem, ok := obj.(*corev1.ConfigMap).Data["custom-ca-certificates.pem"]
if ok {
injectCertificates([]byte(certsPem), httpClient.Transport.(*http.Transport))
}
return []reconcile.Request{}
}
39 changes: 38 additions & 1 deletion controllers/workspace/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@
package controllers

import (
"context"
"crypto/tls"
"crypto/x509"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"net/http"
"net/url"
"sigs.k8s.io/controller-runtime/pkg/client"
"time"

"github.com/devfile/devworkspace-operator/pkg/config"
Expand All @@ -28,8 +33,12 @@ var (
healthCheckHttpClient *http.Client
)

func setupHttpClients() {
func setupHttpClients(k8s client.Client) {
transport := http.DefaultTransport.(*http.Transport).Clone()
certs, ok := readCertificates(k8s)
if ok {
injectCertificates(certs, transport)
}
healthCheckTransport := http.DefaultTransport.(*http.Transport).Clone()
healthCheckTransport.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
Expand Down Expand Up @@ -64,3 +73,31 @@ func setupHttpClients() {
Timeout: 500 * time.Millisecond,
}
}

func readCertificates(k8s client.Client) ([]byte, bool) {
configmapRef := config.GetGlobalConfig().Routing.TLSCertificateConfigmapRef
if configmapRef == nil {
return nil, false
}
configMap := &corev1.ConfigMap{}
namespacedName := &types.NamespacedName{
Name: configmapRef.Name,
Namespace: configmapRef.Namespace,
}
err := k8s.Get(context.Background(), *namespacedName, configMap)
if err == nil {
certificates, ok := configMap.Data["custom-ca-certificates.pem"]
if ok {
return []byte(certificates), true
}
}
return []byte{}, false
}

func injectCertificates(certsPem []byte, transport *http.Transport) {
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(certsPem)
if ok {
transport.TLSClientConfig = &tls.Config{RootCAs: caCertPool}
}
}
23 changes: 23 additions & 0 deletions controllers/workspace/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package controllers

import (
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/devworkspace-operator/pkg/config"
"github.com/devfile/devworkspace-operator/pkg/constants"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -99,6 +100,19 @@ var automountPredicates = predicate.Funcs{
GenericFunc: func(_ event.GenericEvent) bool { return false },
}

var certificatePredicates = predicate.Funcs{
CreateFunc: func(ev event.CreateEvent) bool {
return objectIsCertificateConfigmap(ev.Object)
},
DeleteFunc: func(ev event.DeleteEvent) bool {
return objectIsCertificateConfigmap(ev.Object)
},
UpdateFunc: func(ev event.UpdateEvent) bool {
return objectIsCertificateConfigmap(ev.ObjectNew)
},
GenericFunc: func(_ event.GenericEvent) bool { return false },
}

func objectIsAutomountResource(obj client.Object) bool {
labels := obj.GetLabels()
switch {
Expand All @@ -112,3 +126,12 @@ func objectIsAutomountResource(obj client.Object) bool {
}

}

func objectIsCertificateConfigmap(obj client.Object) bool {
configmapRef := config.GetGlobalConfig().Routing.TLSCertificateConfigmapRef
if configmapRef == nil {
return false
} else {
return configmapRef.Name == obj.GetName() && configmapRef.Namespace == obj.GetNamespace()
}
}
2 changes: 1 addition & 1 deletion controllers/workspace/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ var _ = BeforeSuite(func() {
NonCachingClient: nonCachingClient,
Log: ctrl.Log.WithName("controllers").WithName("DevWorkspace"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr)
}).SetupWithManager(mgr, nonCachingClient)
Expect(err).NotTo(HaveOccurred())

// Set HTTP client to fail all requests by default; tests that require HTTP must set this up directly
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func main() {
NonCachingClient: nonCachingClient,
Log: ctrl.Log.WithName("controllers").WithName("DevWorkspace"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
}).SetupWithManager(mgr, nonCachingClient); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DevWorkspace")
os.Exit(1)
}
Expand Down

0 comments on commit 4da785b

Please sign in to comment.