Skip to content

Commit b366ad3

Browse files
authored
Merge pull request #1577 from 0ekk/passvault
Support ansible vault password
2 parents 16894d0 + feccfec commit b366ad3

File tree

8 files changed

+282
-19
lines changed

8 files changed

+282
-19
lines changed

api/constants/constants.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ const (
1616
KeySprayRelease = "kubean.io/sprayRelease"
1717
KeySprayCommit = "kubean.io/sprayCommit"
1818

19+
AnnotationHostsConfVaultPasswordRef = "kubean.io/vault-password-ref"
20+
1921
KubeanConfigMapName = "kubean-config"
2022
DefaultClusterOperationsBackEndLimit = 30
2123
MaxClusterOperationsBackEndLimit = 200

pkg/controllers/clusterops/controller.go

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,27 @@ func (c *Controller) NewKubesprayJob(clusterOps *clusteroperationv1alpha1.Cluste
469469
},
470470
})
471471
}
472+
if vaultRef := c.getVaultSecret(clusterOps); vaultRef != nil {
473+
if len(job.Spec.Template.Spec.Containers) > 0 && job.Spec.Template.Spec.Containers[0].Name == SprayJobPodName {
474+
job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts,
475+
corev1.VolumeMount{
476+
Name: "vault-password",
477+
MountPath: "/auth/vault-password",
478+
SubPath: "vault-password",
479+
ReadOnly: true,
480+
})
481+
}
482+
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
483+
corev1.Volume{
484+
Name: "vault-password",
485+
VolumeSource: corev1.VolumeSource{
486+
Secret: &corev1.SecretVolumeSource{
487+
SecretName: vaultRef.Name,
488+
DefaultMode: &PrivatekeyMode,
489+
},
490+
},
491+
})
492+
}
472493
if clusterOps.Spec.ActiveDeadlineSeconds != nil && *clusterOps.Spec.ActiveDeadlineSeconds > 0 {
473494
job.Spec.ActiveDeadlineSeconds = clusterOps.Spec.ActiveDeadlineSeconds
474495
}
@@ -606,7 +627,7 @@ func (c *Controller) CreateEntryPointShellConfigMap(clusterOps *clusteroperation
606627
if !clusterOps.Spec.EntrypointSHRef.IsEmpty() {
607628
return false, nil
608629
}
609-
entryPointData := entrypoint.NewEntryPoint()
630+
entryPointData := entrypoint.NewEntryPoint(c.getVaultSecret(clusterOps) != nil)
610631
isPrivateKey := !clusterOps.Spec.SSHAuthRef.IsEmpty()
611632
builtinActionSource := clusteroperationv1alpha1.BuiltinActionSource
612633
for _, action := range clusterOps.Spec.PreHook {
@@ -766,8 +787,9 @@ func (c *Controller) CopyConfigMap(clusterOps *clusteroperationv1alpha1.ClusterO
766787
APIVersion: "v1",
767788
},
768789
ObjectMeta: metav1.ObjectMeta{
769-
Name: newName,
770-
Namespace: namespace,
790+
Name: newName,
791+
Namespace: namespace,
792+
Annotations: oldConfigMap.Annotations,
771793
},
772794
Data: oldConfigMap.Data,
773795
}
@@ -977,3 +999,25 @@ func (c *Controller) CheckClusterDataRef(cluster *clusterv1alpha1.Cluster, clust
977999
}
9781000
return nil
9791001
}
1002+
1003+
func (c *Controller) getVaultSecret(clusterOps *clusteroperationv1alpha1.ClusterOperation) *apis.SecretRef {
1004+
if clusterOps.Spec.HostsConfRef.IsEmpty() {
1005+
return nil
1006+
}
1007+
hostsConf, err := c.ClientSet.CoreV1().ConfigMaps(clusterOps.Spec.HostsConfRef.NameSpace).Get(context.Background(), clusterOps.Spec.HostsConfRef.Name, metav1.GetOptions{})
1008+
if err != nil {
1009+
return nil
1010+
}
1011+
vaultRef, ok := hostsConf.Annotations[constants.AnnotationHostsConfVaultPasswordRef]
1012+
if !ok || vaultRef == "" {
1013+
return nil
1014+
}
1015+
if !c.CheckSecretExist(util.GetCurrentNSOrDefault(), vaultRef) {
1016+
klog.Warningf("vault password ref %s not found in namespace %s", vaultRef, util.GetCurrentNSOrDefault())
1017+
return nil
1018+
}
1019+
return &apis.SecretRef{
1020+
NameSpace: util.GetCurrentNSOrDefault(),
1021+
Name: vaultRef,
1022+
}
1023+
}

pkg/controllers/clusterops/controller_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2923,3 +2923,102 @@ func (MockManager) GetLogger() logr.Logger { return logr.Logger{} }
29232923
func (MockManager) GetControllerOptions() v1alpha1.ControllerConfigurationSpec {
29242924
return v1alpha1.ControllerConfigurationSpec{}
29252925
}
2926+
2927+
func TestGetVaultSecret(t *testing.T) {
2928+
controller := Controller{
2929+
Client: newFakeClient(),
2930+
ClientSet: clientsetfake.NewSimpleClientset(),
2931+
}
2932+
clusterOps := &clusteroperationv1alpha1.ClusterOperation{}
2933+
2934+
tests := []struct {
2935+
name string
2936+
setup func()
2937+
want *apis.SecretRef
2938+
}{
2939+
{
2940+
name: "No HostsConfRef",
2941+
setup: func() {
2942+
clusterOps.Spec.HostsConfRef = &apis.ConfigMapRef{}
2943+
},
2944+
want: nil,
2945+
},
2946+
{
2947+
name: "ConfigMap Not Found",
2948+
setup: func() {
2949+
clusterOps.Spec.HostsConfRef = &apis.ConfigMapRef{NameSpace: "default", Name: "nonexistent"}
2950+
},
2951+
want: nil,
2952+
},
2953+
{
2954+
name: "No Vault Annotation",
2955+
setup: func() {
2956+
cm := &corev1.ConfigMap{
2957+
ObjectMeta: metav1.ObjectMeta{
2958+
Namespace: "default",
2959+
Name: "hostsconf",
2960+
},
2961+
}
2962+
controller.ClientSet.CoreV1().ConfigMaps("default").Create(context.Background(), cm, metav1.CreateOptions{})
2963+
clusterOps.Spec.HostsConfRef = &apis.ConfigMapRef{NameSpace: "default", Name: "hostsconf"}
2964+
},
2965+
want: nil,
2966+
},
2967+
{
2968+
name: "Vault Secret Not Found",
2969+
setup: func() {
2970+
cm := &corev1.ConfigMap{
2971+
ObjectMeta: metav1.ObjectMeta{
2972+
Namespace: "default",
2973+
Name: "hostsconf",
2974+
Annotations: map[string]string{constants.AnnotationHostsConfVaultPasswordRef: "vault-secret"},
2975+
},
2976+
}
2977+
controller.ClientSet.CoreV1().ConfigMaps("default").Create(context.Background(), cm, metav1.CreateOptions{})
2978+
clusterOps.Spec.HostsConfRef = &apis.ConfigMapRef{NameSpace: "default", Name: "hostsconf"}
2979+
},
2980+
want: nil,
2981+
},
2982+
{
2983+
name: "Successful Retrieval",
2984+
setup: func() {
2985+
cm := &corev1.ConfigMap{
2986+
ObjectMeta: metav1.ObjectMeta{
2987+
Namespace: "default",
2988+
Name: "hostsconf1",
2989+
Annotations: map[string]string{constants.AnnotationHostsConfVaultPasswordRef: "vault-secret"},
2990+
},
2991+
Data: map[string]string{
2992+
"vault-password": "vault-password",
2993+
},
2994+
}
2995+
_, err := controller.ClientSet.CoreV1().ConfigMaps("default").Create(context.Background(), cm, metav1.CreateOptions{})
2996+
if err != nil {
2997+
t.Fatalf("failed to create config map: %v", err)
2998+
}
2999+
secret := &corev1.Secret{
3000+
ObjectMeta: metav1.ObjectMeta{
3001+
Namespace: "default",
3002+
Name: "vault-secret",
3003+
},
3004+
}
3005+
_, err = controller.ClientSet.CoreV1().Secrets("default").Create(context.Background(), secret, metav1.CreateOptions{})
3006+
if err != nil {
3007+
t.Fatalf("failed to create secret: %v", err)
3008+
}
3009+
clusterOps.Spec.HostsConfRef = &apis.ConfigMapRef{NameSpace: "default", Name: "hostsconf1"}
3010+
},
3011+
want: &apis.SecretRef{NameSpace: "default", Name: "vault-secret"},
3012+
},
3013+
}
3014+
3015+
for _, test := range tests {
3016+
t.Run(test.name, func(t *testing.T) {
3017+
test.setup()
3018+
got := controller.getVaultSecret(clusterOps)
3019+
if !reflect.DeepEqual(got, test.want) {
3020+
t.Fatalf("got %v, want %v", got, test.want)
3021+
}
3022+
})
3023+
}
3024+
}

pkg/util/entrypoint/entrypoint.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ const (
4848
//go:embed entrypoint.sh.template
4949
var entrypointTemplate string
5050

51+
//go:embed inventory_decrypt.sh
52+
var inventoryDecryptScript string
53+
5154
type void struct{}
5255

5356
var member void
@@ -90,14 +93,20 @@ func (argsError ArgsError) Error() string {
9093
}
9194

9295
type EntryPoint struct {
93-
PreHookCMDs []string
94-
SprayCMD string
95-
PostHookCMDs []string
96-
Actions *Actions
96+
PrerequisitesCMDs []string
97+
PreHookCMDs []string
98+
SprayCMD string
99+
PostHookCMDs []string
100+
Actions *Actions
101+
102+
isVaultEncryped bool
97103
}
98104

99-
func NewEntryPoint() *EntryPoint {
100-
ep := &EntryPoint{}
105+
func NewEntryPoint(isVaultEncryped bool) *EntryPoint {
106+
ep := &EntryPoint{
107+
isVaultEncryped: isVaultEncryped,
108+
}
109+
ep.PrerequisiteRunPart()
101110
ep.Actions = NewActions()
102111
return ep
103112
}
@@ -108,7 +117,11 @@ func (ep *EntryPoint) buildPlaybookCmd(action, extraArgs string, isPrivateKey, b
108117
return "", ArgsError{fmt.Sprintf("unknown playbook type, the currently supported ranges include: %s", ep.Actions.Playbooks.List)}
109118
}
110119
}
111-
playbookCmd := "ansible-playbook -i /conf/hosts.yml -b --become-user root -e \"@/conf/group_vars.yml\""
120+
inventory := "/conf/hosts.yml"
121+
if ep.isVaultEncryped {
122+
inventory = "/dev/fd/200"
123+
}
124+
playbookCmd := fmt.Sprintf("ansible-playbook -i %s -b --become-user root -e \"@/conf/group_vars.yml\"", inventory)
112125
if isPrivateKey {
113126
playbookCmd = fmt.Sprintf("%s --private-key /auth/ssh-privatekey", playbookCmd)
114127
}
@@ -145,6 +158,12 @@ func (ep *EntryPoint) hookRunPart(actionType, action, extraArgs string, isPrivat
145158
return hookRunCmd, nil
146159
}
147160

161+
func (ep *EntryPoint) PrerequisiteRunPart() {
162+
if ep.isVaultEncryped {
163+
ep.PrerequisitesCMDs = append(ep.PrerequisitesCMDs, inventoryDecryptScript)
164+
}
165+
}
166+
148167
func (ep *EntryPoint) PreHookRunPart(actionType, action, extraArgs string, isPrivateKey, builtinAction bool) error {
149168
prehook, err := ep.hookRunPart(actionType, action, extraArgs, isPrivateKey, builtinAction)
150169
if err != nil {

pkg/util/entrypoint/entrypoint.sh.template

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ set -o errexit
44
set -o nounset
55
set -o pipefail
66

7+
# prerequisite
8+
{{ range $prerequisiteCMD := .PrerequisitesCMDs }}
9+
{{- $prerequisiteCMD }}
10+
{{ end }}
11+
712
# preinstall
813
{{ range $preCMD := .PreHookCMDs }}
914
{{- $preCMD }}

0 commit comments

Comments
 (0)