Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inject certificate to http client from a configmap referenced in the … #1259

Merged
merged 2 commits into from
May 24, 2024
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
10 changes: 10 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 into the
// HTTP client.
TLSCertificateConfigmapRef *ConfigmapReference `json:"tlsCertificateConfigmapRef,omitempty"`
}

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

type ConfigmapReference struct {
AObuchow marked this conversation as resolved.
Show resolved Hide resolved
// Name is the name of the configmap
Name string `json:"name"`
// Namespace is the namespace of the configmap
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
20 changes: 20 additions & 0 deletions apis/controller/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion controllers/workspace/devworkspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
reqLogger = reqLogger.WithValues(constants.DevWorkspaceIDLoggerKey, workspace.Status.DevWorkspaceId)
reqLogger.Info("Reconciling Workspace", "resolvedConfig", configString)

// Inject ca certificates to the http client, if the certificates configmap is created and defined in the config.
InjectCertificates(r.Client, r.Log)

// Check if the DevWorkspaceRouting instance is marked to be deleted, which is
// indicated by the deletion timestamp being set.
if workspace.GetDeletionTimestamp() != nil {
Expand Down Expand Up @@ -668,7 +671,7 @@ func (r *DevWorkspaceReconciler) getWorkspaceId(ctx context.Context, workspace *
}

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

maxConcurrentReconciles, err := wkspConfig.GetMaxConcurrentReconciles()
if err != nil {
Expand Down
54 changes: 53 additions & 1 deletion controllers/workspace/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,21 @@
package controllers

import (
"context"
"crypto/tls"
"crypto/x509"
"net/http"
"net/url"
"time"

"github.com/devfile/devworkspace-operator/pkg/config"

"k8s.io/apimachinery/pkg/types"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

"golang.org/x/net/http/httpproxy"
)

Expand All @@ -28,7 +37,7 @@ var (
healthCheckHttpClient *http.Client
)

func setupHttpClients() {
func setupHttpClients(k8s client.Client, logger logr.Logger) {
transport := http.DefaultTransport.(*http.Transport).Clone()
healthCheckTransport := http.DefaultTransport.(*http.Transport).Clone()
healthCheckTransport.TLSClientConfig = &tls.Config{
Expand Down Expand Up @@ -63,4 +72,47 @@ func setupHttpClients() {
Transport: healthCheckTransport,
Timeout: 500 * time.Millisecond,
}
InjectCertificates(k8s, logger)
}

func InjectCertificates(k8s client.Client, logger logr.Logger) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Exported functions are generally placed above non-exported functions (in the code). However, this is just a style nitpick that I forgot to mention and can be changed later on as I don't want to delay merging this PR due to time zone differences and the upcoming DWO 0.28.0 upstream release that we will hopefully start tomorrow.

if certs, ok := readCertificates(k8s, logger); ok {
for _, certsPem := range certs {
injectCertificates([]byte(certsPem), httpClient.Transport.(*http.Transport), logger)
}
}
}

func readCertificates(k8s client.Client, logger logr.Logger) (map[string]string, bool) {
configmapRef := config.GetGlobalConfig().Routing.TLSCertificateConfigmapRef
AObuchow marked this conversation as resolved.
Show resolved Hide resolved
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 {
logger.Error(err, "Failed to read configmap with certificates")
return nil, false
}
return configMap.Data, true
}

func injectCertificates(certsPem []byte, transport *http.Transport, logger logr.Logger) {
caCertPool := transport.TLSClientConfig.RootCAs
if caCertPool == nil {
systemCertPool, err := x509.SystemCertPool()
if err != nil {
logger.Error(err, "Failed to load system cert pool")
caCertPool = x509.NewCertPool()
} else {
caCertPool = systemCertPool
}
}
if ok := caCertPool.AppendCertsFromPEM(certsPem); ok {
transport.TLSClientConfig = &tls.Config{RootCAs: caCertPool}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions deploy/deployment/kubernetes/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions deploy/deployment/openshift/combined.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions pkg/config/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ func mergeConfig(from, to *controller.OperatorConfiguration) {
}
to.Routing.ProxyConfig = proxy.MergeProxyConfigs(from.Routing.ProxyConfig, defaultConfig.Routing.ProxyConfig)
}
if from.Routing.TLSCertificateConfigmapRef != nil {
if to.Routing.TLSCertificateConfigmapRef == nil {
to.Routing.TLSCertificateConfigmapRef = &controller.ConfigmapReference{}
vinokurig marked this conversation as resolved.
Show resolved Hide resolved
}
to.Routing.TLSCertificateConfigmapRef = mergeTLSCertificateConfigmapRef(from.Routing.TLSCertificateConfigmapRef, defaultConfig.Routing.TLSCertificateConfigmapRef)
}
}
if from.Workspace != nil {
if to.Workspace == nil {
Expand Down Expand Up @@ -429,6 +435,28 @@ func mergeContainerSecurityContext(base, patch *corev1.SecurityContext) *corev1.
return patched
}

func mergeTLSCertificateConfigmapRef(operatorConfig, clusterConfig *controller.ConfigmapReference) *controller.ConfigmapReference {
if clusterConfig == nil {
return operatorConfig
}
if operatorConfig == nil {
return clusterConfig
}
mergedConfigmapReference := &controller.ConfigmapReference{
vinokurig marked this conversation as resolved.
Show resolved Hide resolved
Name: operatorConfig.Name,
Namespace: operatorConfig.Namespace,
}

if mergedConfigmapReference.Name == "" {
mergedConfigmapReference.Name = clusterConfig.Name
}
if mergedConfigmapReference.Namespace == "" {
mergedConfigmapReference.Namespace = clusterConfig.Namespace
}

return mergedConfigmapReference
}

func mergeResources(from, to *corev1.ResourceRequirements) *corev1.ResourceRequirements {
result := to.DeepCopy()
if from.Limits != nil {
Expand Down
Loading