Skip to content

Commit 4315294

Browse files
authored
Merge pull request #1636 from 0ekk/add-asc
Support asymmetric encryption for ssh password
2 parents 7c736de + 675d922 commit 4315294

File tree

19 files changed

+690
-340
lines changed

19 files changed

+690
-340
lines changed

api/constants/constants.go

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

19-
AnnotationHostsConfVaultPasswordRef = "kubean.io/vault-password-ref"
20-
2119
KubeanConfigMapName = "kubean-config"
20+
KubeanPubKeyConfigMapName = "kubean-pubkey"
2221
DefaultClusterOperationsBackEndLimit = 30
2322
MaxClusterOperationsBackEndLimit = 200
2423
)

artifacts/ssh_password_encyptor.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
set -e
3+
4+
PUBLICKEY_PATH=${PUBLICKEY_PATH:-}
5+
KUBECONFIG=${KUBECONFIG:-"$HOME/.kube/config"}
6+
7+
for cmd in base64 openssl; do
8+
if ! which "$cmd" &>/dev/null; then
9+
echo "Need $cmd"
10+
exit 1
11+
fi
12+
done
13+
14+
if [ -z "$PUBLICKEY_PATH" ]; then
15+
PUBLICKEY_PATH=$(mktemp)
16+
trap "rm -rf $PUBLICKEY_PATH" exit
17+
kubectl -n kubean-system get configmap kubean-pubkey -ojsonpath='{.data.pk}' | base64 -d > "$PUBLICKEY_PATH"
18+
fi
19+
20+
if [ ! -s "$PUBLICKEY_PATH" ]; then
21+
echo "Cannot get public key, Check PUBLICKEY_PATH env"
22+
exit 1
23+
fi
24+
25+
read -s -r -p "Your password: " password
26+
echo -en "\nEncrypted password: "
27+
echo "VAULT;$(echo -n "$password" | openssl pkeyutl -encrypt -pubin -inkey "$PUBLICKEY_PATH" | base64 -w0)"

build/images/spray-job/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ RUN python3 -m pip install toml
1212
RUN ansible-galaxy collection install sivel.toiletwater
1313

1414
RUN ln -s playbooks/facts.yml facts.yml
15+
16+
RUN apk add --no-cache openssl

charts/kubean/templates/configmap.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,18 @@ metadata:
99
data:
1010
CLUSTER_OPERATIONS_BACKEND_LIMIT: "{{ .Values.kubeanOperator.operationsBackendLimit }}"
1111
SPRAY_JOB_IMAGE_REGISTRY: "{{ .Values.sprayJob.image.registry }}"
12+
{{- if .Values.kubeanOperator.crypto.skBase64 }}
13+
sk: "{{ .Values.kubeanOperator.crypto.skBase64 }}"
14+
{{- end }}
15+
---
16+
{{- if .Values.kubeanOperator.crypto.pkBase64 -}}
17+
apiVersion: v1
18+
kind: ConfigMap
19+
metadata:
20+
name: kubean-pubkey
21+
namespace: {{ include "kubean.namespace" . }}
22+
labels:
23+
{{- include "kubean.labels" . | nindent 4}}
24+
data:
25+
pk: {{ .Values.kubeanOperator.crypto.pkBase64 }}
26+
{{- end -}}

charts/kubean/values.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ kubeanOperator:
9191
values:
9292
- kubean
9393
topologyKey: kubernetes.io/hostname
94+
crypto:
95+
skBase64: null
96+
pkBase64: null
9497

9598
## @section kubean admission parameters
9699
## @param kubeanAdmission.replicaCount Number of kubean-admission replicas to deploy

cmd/kubean-operator/app/app.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/kubean-io/kubean/pkg/controllers/clusterops"
1919
"github.com/kubean-io/kubean/pkg/controllers/infomanifest"
2020
"github.com/kubean-io/kubean/pkg/controllers/offlineversion"
21+
"github.com/kubean-io/kubean/pkg/crypto"
2122
"github.com/kubean-io/kubean/pkg/util"
2223
"github.com/kubean-io/kubean/pkg/version"
2324

@@ -112,6 +113,12 @@ func setupManager(mgr controllerruntime.Manager, opt *Options, stopChan <-chan s
112113
if err != nil {
113114
return err
114115
}
116+
117+
if crypto.InitConfiguration(ClientSet) != nil {
118+
klog.ErrorS(err, "Failed to init crypto configuration")
119+
return err
120+
}
121+
115122
clusterClientSet, err := kubeanClusterClientSet.NewForConfig(resetConfig)
116123
if err != nil {
117124
return err

hack/staticcheck.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ set -o nounset
88
set -o pipefail
99

1010
REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
11-
GOLANGCI_LINT_PKG="github.com/golangci/golangci-lint/cmd/golangci-lint"
12-
GOLANGCI_LINT_VER="v1.52.2"
11+
GOLANGCI_LINT_VER="v2.1.0"
1312

1413
cd "${REPO_ROOT}"
1514
source "hack/util.sh"
1615

17-
util::install_tools ${GOLANGCI_LINT_PKG} ${GOLANGCI_LINT_VER}
16+
# binary will be $(go env GOPATH)/bin/golangci-lint
17+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCI_LINT_VER
1818

1919
if golangci-lint run --fix --verbose; then
2020
echo 'Congratulations! All Go source files have passed staticcheck.'

pkg/controllers/clusterops/controller.go

Lines changed: 20 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414
"unicode"
1515

16+
"github.com/kubean-io/kubean/pkg/crypto"
1617
"github.com/kubean-io/kubean/pkg/util"
1718
"github.com/kubean-io/kubean/pkg/util/entrypoint"
1819

@@ -382,14 +383,26 @@ func (c *Controller) NewKubesprayJob(clusterOps *clusteroperationv1alpha1.Cluste
382383
ServiceAccountName: serviceAccountName,
383384
Containers: []corev1.Container{
384385
{
385-
Name: SprayJobPodName,
386-
Image: c.ProcessKubeanOperationImage(clusterOps.Spec.Image, c.FetchGlobalManifestImageTag()),
387-
Command: []string{"/bin/entrypoint.sh"},
386+
Name: SprayJobPodName,
387+
Image: c.ProcessKubeanOperationImage(clusterOps.Spec.Image, c.FetchGlobalManifestImageTag()),
388+
ImagePullPolicy: corev1.PullIfNotPresent,
389+
Command: []string{"/bin/entrypoint.sh"},
388390
Env: []corev1.EnvVar{
389391
{
390392
Name: "CLUSTER_NAME",
391393
Value: clusterOps.Spec.Cluster,
392394
},
395+
{
396+
Name: "VAULT_PRIVATE_KEY",
397+
ValueFrom: &corev1.EnvVarSource{
398+
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
399+
LocalObjectReference: corev1.LocalObjectReference{
400+
Name: constants.KubeanConfigMapName,
401+
},
402+
Key: crypto.PrivateKey,
403+
},
404+
},
405+
},
393406
},
394407
VolumeMounts: []corev1.VolumeMount{
395408
{
@@ -480,27 +493,6 @@ func (c *Controller) NewKubesprayJob(clusterOps *clusteroperationv1alpha1.Cluste
480493
},
481494
})
482495
}
483-
if vaultRef := c.getVaultSecret(clusterOps); vaultRef != nil {
484-
if len(job.Spec.Template.Spec.Containers) > 0 && job.Spec.Template.Spec.Containers[0].Name == SprayJobPodName {
485-
job.Spec.Template.Spec.Containers[0].VolumeMounts = append(job.Spec.Template.Spec.Containers[0].VolumeMounts,
486-
corev1.VolumeMount{
487-
Name: "vault-password",
488-
MountPath: "/auth/vault-password",
489-
SubPath: "vault-password",
490-
ReadOnly: true,
491-
})
492-
}
493-
job.Spec.Template.Spec.Volumes = append(job.Spec.Template.Spec.Volumes,
494-
corev1.Volume{
495-
Name: "vault-password",
496-
VolumeSource: corev1.VolumeSource{
497-
Secret: &corev1.SecretVolumeSource{
498-
SecretName: vaultRef.Name,
499-
DefaultMode: &PrivatekeyMode,
500-
},
501-
},
502-
})
503-
}
504496
if clusterOps.Spec.ActiveDeadlineSeconds != nil && *clusterOps.Spec.ActiveDeadlineSeconds > 0 {
505497
job.Spec.ActiveDeadlineSeconds = clusterOps.Spec.ActiveDeadlineSeconds
506498
}
@@ -638,7 +630,8 @@ func (c *Controller) CreateEntryPointShellConfigMap(clusterOps *clusteroperation
638630
if !clusterOps.Spec.EntrypointSHRef.IsEmpty() {
639631
return false, nil
640632
}
641-
entryPointData := entrypoint.NewEntryPoint(c.getVaultSecret(clusterOps) != nil)
633+
634+
entryPointData := entrypoint.NewEntryPoint()
642635
isPrivateKey := !clusterOps.Spec.SSHAuthRef.IsEmpty()
643636
builtinActionSource := clusteroperationv1alpha1.BuiltinActionSource
644637
for _, action := range clusterOps.Spec.PreHook {
@@ -798,9 +791,8 @@ func (c *Controller) CopyConfigMap(clusterOps *clusteroperationv1alpha1.ClusterO
798791
APIVersion: "v1",
799792
},
800793
ObjectMeta: metav1.ObjectMeta{
801-
Name: newName,
802-
Namespace: namespace,
803-
Annotations: oldConfigMap.Annotations,
794+
Name: newName,
795+
Namespace: namespace,
804796
},
805797
Data: oldConfigMap.Data,
806798
}
@@ -1010,25 +1002,3 @@ func (c *Controller) CheckClusterDataRef(cluster *clusterv1alpha1.Cluster, clust
10101002
}
10111003
return nil
10121004
}
1013-
1014-
func (c *Controller) getVaultSecret(clusterOps *clusteroperationv1alpha1.ClusterOperation) *apis.SecretRef {
1015-
if clusterOps.Spec.HostsConfRef.IsEmpty() {
1016-
return nil
1017-
}
1018-
hostsConf, err := c.ClientSet.CoreV1().ConfigMaps(clusterOps.Spec.HostsConfRef.NameSpace).Get(context.Background(), clusterOps.Spec.HostsConfRef.Name, metav1.GetOptions{})
1019-
if err != nil {
1020-
return nil
1021-
}
1022-
vaultRef, ok := hostsConf.Annotations[constants.AnnotationHostsConfVaultPasswordRef]
1023-
if !ok || vaultRef == "" {
1024-
return nil
1025-
}
1026-
if !c.CheckSecretExist(util.GetCurrentNSOrDefault(), vaultRef) {
1027-
klog.Warningf("vault password ref %s not found in namespace %s", vaultRef, util.GetCurrentNSOrDefault())
1028-
return nil
1029-
}
1030-
return &apis.SecretRef{
1031-
NameSpace: util.GetCurrentNSOrDefault(),
1032-
Name: vaultRef,
1033-
}
1034-
}

pkg/controllers/clusterops/controller_test.go

Lines changed: 0 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2923,102 +2923,3 @@ 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/crypto/crypto.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package crypto
2+
3+
import (
4+
"context"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/client-go/kubernetes"
9+
klog "k8s.io/klog/v2"
10+
11+
"github.com/kubean-io/kubean-api/constants"
12+
"github.com/kubean-io/kubean/pkg/util"
13+
)
14+
15+
const (
16+
PrivateKey = "sk"
17+
PublicKey = "pk"
18+
)
19+
20+
func InitConfiguration(clientset kubernetes.Interface) error {
21+
kubeanConfig, err := clientset.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Get(context.Background(), constants.KubeanConfigMapName, metav1.GetOptions{})
22+
if err != nil {
23+
return err
24+
}
25+
if privateKey, ok := kubeanConfig.Data[PrivateKey]; ok && privateKey != "" {
26+
return nil
27+
}
28+
29+
sk, pk, err := util.GenerateRSAKeyPairB64()
30+
if err != nil {
31+
return err
32+
}
33+
34+
klog.Infof("inject %s into %s", PrivateKey, constants.KubeanConfigMapName)
35+
kubeanConfig.Data[PrivateKey] = sk
36+
_, err = clientset.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Update(context.Background(), kubeanConfig, metav1.UpdateOptions{})
37+
if err != nil {
38+
return err
39+
}
40+
41+
kubeanPubkey := &corev1.ConfigMap{
42+
ObjectMeta: metav1.ObjectMeta{
43+
Name: constants.KubeanPubKeyConfigMapName,
44+
Namespace: util.GetCurrentNSOrDefault(),
45+
},
46+
Data: map[string]string{
47+
PublicKey: pk,
48+
},
49+
}
50+
klog.Infof("create %s", kubeanPubkey.Name)
51+
_, err = clientset.CoreV1().ConfigMaps(util.GetCurrentNSOrDefault()).Create(context.Background(), kubeanPubkey, metav1.CreateOptions{})
52+
if err != nil {
53+
return err
54+
}
55+
return nil
56+
}

0 commit comments

Comments
 (0)