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

Mount secrets in containers #595

Merged
merged 3 commits into from
Dec 24, 2020
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
51 changes: 44 additions & 7 deletions pkg/controller/che/che_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,29 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
return err
}

var toRequestMapper handler.ToRequestsFunc = func(obj handler.MapObject) []reconcile.Request {
var toTrustedBundleConfigMapRequestMapper handler.ToRequestsFunc = func(obj handler.MapObject) []reconcile.Request {
isTrusted, reconcileRequest := isTrustedBundleConfigMap(mgr, obj)
if isTrusted {
return []reconcile.Request{reconcileRequest}
}
return []reconcile.Request{}
}
err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestsFromMapFunc{
ToRequests: toRequestMapper,
}, onAllExceptGenericEventsPredicate)
if err != nil {
if err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, &handler.EnqueueRequestsFromMapFunc{
ToRequests: toTrustedBundleConfigMapRequestMapper,
}, onAllExceptGenericEventsPredicate); err != nil {
return err
}

var toEclipseCheSecretRequestMapper handler.ToRequestsFunc = func(obj handler.MapObject) []reconcile.Request {
isEclipseCheSecret, reconcileRequest := isEclipseCheSecret(mgr, obj)
if isEclipseCheSecret {
return []reconcile.Request{reconcileRequest}
}
return []reconcile.Request{}
}
if err = c.Watch(&source.Kind{Type: &corev1.Secret{}}, &handler.EnqueueRequestsFromMapFunc{
ToRequests: toEclipseCheSecretRequestMapper,
}, onAllExceptGenericEventsPredicate); err != nil {
return err
}

Expand Down Expand Up @@ -311,7 +323,7 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e
}

// delete oAuthClient before CR is deleted
// todo check
// todo check
// instance.Status.OpenShiftoAuthProvisioned
if util.IsOpenShift && util.IsOAuthEnabled(instance) {
if err := r.ReconcileFinalizer(instance); err != nil {
Expand Down Expand Up @@ -1071,7 +1083,7 @@ func isTrustedBundleConfigMap(mgr manager.Manager, obj handler.MapObject) (bool,
// Check for labels

// Check for part of Che label
if value, exists := obj.Meta.GetLabels()[deploy.PartOfCheLabelKey]; !exists || value != deploy.PartOfCheLabelValue {
if value, exists := obj.Meta.GetLabels()[deploy.KubernetesPartOfLabelKey]; !exists || value != deploy.CheEclipseOrg {
// Labels do not match
return false, reconcile.Request{}
}
Expand Down Expand Up @@ -1137,3 +1149,28 @@ func (r *ReconcileChe) autoEnableOAuth(cr *orgv1.CheCluster, request reconcile.R

return reconcile.Result{}, nil
}

// isEclipseCheSecret indicates if there is a secret with
// the label 'app.kubernetes.io/part-of=che.eclipse.org' in a che namespace
func isEclipseCheSecret(mgr manager.Manager, obj handler.MapObject) (bool, reconcile.Request) {
checlusters := &orgv1.CheClusterList{}
if err := mgr.GetClient().List(context.TODO(), checlusters, &client.ListOptions{}); err != nil {
return false, reconcile.Request{}
}

if len(checlusters.Items) != 1 {
return false, reconcile.Request{}
}

if value, exists := obj.Meta.GetLabels()[deploy.KubernetesPartOfLabelKey]; !exists || value != deploy.CheEclipseOrg {
// Labels do not match
return false, reconcile.Request{}
}

return true, reconcile.Request{
NamespacedName: types.NamespacedName{
Namespace: checlusters.Items[0].Namespace,
Name: checlusters.Items[0].Name,
},
}
}
12 changes: 10 additions & 2 deletions pkg/deploy/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,16 @@ const (
OldDefaultCodeReadyServerImageTag = "1.2"
OldCrwPluginRegistryUrl = "https://che-plugin-registry.openshift.io"

PartOfCheLabelKey = "app.kubernetes.io/part-of"
PartOfCheLabelValue = "che.eclipse.org"
// kubernetes default labels
KubernetesComponentLabelKey = "app.kubernetes.io/component"
KubernetesPartOfLabelKey = "app.kubernetes.io/part-of"
tolusha marked this conversation as resolved.
Show resolved Hide resolved

CheEclipseOrg = "che.eclipse.org"

// che.eclipse.org annotations
CheEclipseOrgMountPath = "che.eclipse.org/mount-path"
CheEclipseOrgMountAs = "che.eclipse.org/mount-as"
CheEclipseOrgEnvName = "che.eclipse.org/env-name"
)

func InitDefaults(defaultsPath string) {
Expand Down
111 changes: 110 additions & 1 deletion pkg/deploy/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ package deploy
import (
"context"
"fmt"
"sort"
"strings"

"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/selection"
"sigs.k8s.io/controller-runtime/pkg/client"
runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
Expand All @@ -23,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
runtimeClient "sigs.k8s.io/controller-runtime/pkg/client"
)

var DeploymentDiffOpts = cmp.Options{
Expand All @@ -33,6 +39,7 @@ var DeploymentDiffOpts = cmp.Options{
cmpopts.IgnoreFields(corev1.Container{}, "TerminationMessagePath", "TerminationMessagePolicy"),
cmpopts.IgnoreFields(corev1.PodSpec{}, "DNSPolicy", "SchedulerName", "SecurityContext", "DeprecatedServiceAccount"),
cmpopts.IgnoreFields(corev1.ConfigMapVolumeSource{}, "DefaultMode"),
cmpopts.IgnoreFields(corev1.SecretVolumeSource{}, "DefaultMode"),
cmpopts.IgnoreFields(corev1.VolumeSource{}, "EmptyDir"),
cmp.Comparer(func(x, y resource.Quantity) bool {
return x.Cmp(y) == 0
Expand All @@ -51,6 +58,12 @@ func SyncDeploymentToCluster(
additionalDeploymentDiffOpts cmp.Options,
additionalDeploymentMerge func(*appsv1.Deployment, *appsv1.Deployment) *appsv1.Deployment) DeploymentProvisioningStatus {

if err := MountSecrets(specDeployment, deployContext); err != nil {
return DeploymentProvisioningStatus{
ProvisioningStatus: ProvisioningStatus{Requeue: true, Err: err},
}
}

clusterDeployment, err := GetClusterDeployment(specDeployment.Name, specDeployment.Namespace, deployContext.ClusterAPI.Client)
if err != nil {
return DeploymentProvisioningStatus{
Expand Down Expand Up @@ -118,3 +131,99 @@ func GetClusterDeployment(name string, namespace string, client runtimeClient.Cl
}
return deployment, nil
}

// MountSecrets mounts secrets into a container as a file or as environment variable.
// Secrets are selected by the following labels:
// - app.kubernetes.io/part-of=che.eclipse.org
// - app.kubernetes.io/component=<DEPLOYMENT-NAME>-secret
func MountSecrets(specDeployment *appsv1.Deployment, deployContext *DeployContext) error {
tolusha marked this conversation as resolved.
Show resolved Hide resolved
secrets := &corev1.SecretList{}

kubernetesPartOfLabelSelectorRequirement, _ := labels.NewRequirement(KubernetesPartOfLabelKey, selection.Equals, []string{CheEclipseOrg})
kubernetesComponentLabelSelectorRequirement, _ := labels.NewRequirement(KubernetesComponentLabelKey, selection.Equals, []string{specDeployment.Name + "-secret"})

listOptions := &client.ListOptions{
LabelSelector: labels.NewSelector().Add(*kubernetesPartOfLabelSelectorRequirement).Add(*kubernetesComponentLabelSelectorRequirement),
}
if err := deployContext.ClusterAPI.Client.List(context.TODO(), secrets, listOptions); err != nil {
return err
}

// sort secrets by name first to have the same order every time
sort.Slice(secrets.Items, func(i, j int) bool {
return strings.Compare(secrets.Items[i].Name, secrets.Items[j].Name) < 0
})

for _, secretObj := range secrets.Items {
switch secretObj.Annotations[CheEclipseOrgMountAs] {
case "file":
voluseSource := corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretObj.Name,
},
}

volume := corev1.Volume{
Name: secretObj.Name,
VolumeSource: voluseSource,
}

volumeMount := corev1.VolumeMount{
Name: secretObj.Name,
MountPath: secretObj.Annotations[CheEclipseOrgMountPath],
}

specDeployment.Spec.Template.Spec.Volumes = append(specDeployment.Spec.Template.Spec.Volumes, volume)
specDeployment.Spec.Template.Spec.Containers[0].VolumeMounts = append(specDeployment.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMount)

case "env":
secret, err := GetClusterSecret(secretObj.Name, deployContext.CheCluster.Namespace, deployContext.ClusterAPI)
if err != nil {
return err
} else if secret == nil {
return fmt.Errorf("Secret '%s' not found", secretObj.Name)
}

// grab all keys and sort first to have the same order every time
keys := make([]string, 0)
for k := range secret.Data {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
return strings.Compare(keys[i], keys[j]) < 0
})

for _, key := range keys {
var envName string

// check if evn name defined explicitly
envNameAnnotation := CheEclipseOrg + "/" + key + "_env-name"
envName, envNameExists := secretObj.Annotations[envNameAnnotation]
if !envNameExists {
// check if there is only one env name to mount
envName, envNameExists = secretObj.Annotations[CheEclipseOrgEnvName]
if len(secret.Data) > 1 {
return fmt.Errorf("There are more than one environment variable to mount. Use annotation '%s' to specify a name", envNameAnnotation)
} else if !envNameExists {
return fmt.Errorf("Environment name to mount secret key not found. Use annotation '%s' to specify a name", CheEclipseOrgEnvName)
}
}

env := corev1.EnvVar{
Name: envName,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
Key: key,
LocalObjectReference: corev1.LocalObjectReference{
Name: secretObj.Name,
},
},
},
}
specDeployment.Spec.Template.Spec.Containers[0].Env = append(specDeployment.Spec.Template.Spec.Containers[0].Env, env)
}
}
}

return nil
}
Loading