Skip to content

Commit

Permalink
Mount secrets in containers (#595)
Browse files Browse the repository at this point in the history
* Mount secrets in containers

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
  • Loading branch information
tolusha authored Dec 24, 2020
1 parent e373bc7 commit 42684b8
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 12 deletions.
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"

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 {
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

0 comments on commit 42684b8

Please sign in to comment.