diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go
index d602b67f9c0..b7015ef4771 100644
--- a/apis/apps/v1/componentdefinition_types.go
+++ b/apis/apps/v1/componentdefinition_types.go
@@ -1212,12 +1212,12 @@ type SystemAccount struct {
// +optional
InitAccount bool `json:"initAccount,omitempty"`
- // Defines the statement used to create the account with the necessary privileges.
+ // Defines the statements used to create, delete, and update the account.
//
// This field is immutable once set.
//
// +optional
- Statement string `json:"statement,omitempty"`
+ Statement *SystemAccountStatement `json:"statement,omitempty"`
// Specifies the policy for generating the account's password.
//
@@ -1225,13 +1225,29 @@ type SystemAccount struct {
//
// +optional
PasswordGenerationPolicy PasswordConfig `json:"passwordGenerationPolicy"`
+}
+
+type SystemAccountStatement struct {
+ // The statement to create a new account with the necessary privileges.
+ //
+ // This field is immutable once set.
+ //
+ // +optional
+ Create string `json:"create,omitempty"`
+
+ // The statement to delete a account.
+ //
+ // This field is immutable once set.
+ //
+ // +optional
+ Delete string `json:"delete,omitempty"`
- // Refers to the secret from which data will be copied to create the new account.
+ // The statement to update an existing account.
//
// This field is immutable once set.
//
// +optional
- SecretRef *ProvisionSecretRef `json:"secretRef,omitempty"`
+ Update string `json:"update,omitempty"`
}
type TLS struct {
@@ -1735,9 +1751,9 @@ type ComponentLifecycleActions struct {
//
// The container executing this action has access to following variables:
//
- // - KB_ACCOUNT_NAME: The name of the system account to be created.
- // - KB_ACCOUNT_PASSWORD: The password for the system account. // TODO: how to pass the password securely?
- // - KB_ACCOUNT_STATEMENT: The statement used to create the system account.
+ // - KB_ACCOUNT_NAME: The name of the system account to be manipulated.
+ // - KB_ACCOUNT_PASSWORD: The password for the system account.
+ // - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account.
//
// Note: This field is immutable once it has been set.
//
diff --git a/apis/apps/v1/types.go b/apis/apps/v1/types.go
index 5cf93cd6f2e..f31372d0376 100644
--- a/apis/apps/v1/types.go
+++ b/apis/apps/v1/types.go
@@ -347,6 +347,12 @@ type ComponentSystemAccount struct {
// +kubebuilder:validation:Required
Name string `json:"name"`
+ // Specifies whether the system account is disabled.
+ //
+ // +kubebuilder:default=false
+ // +optional
+ Disabled *bool `json:"disabled,omitempty"`
+
// Specifies the policy for generating the account's password.
//
// This field is immutable once set.
@@ -429,6 +435,12 @@ type ProvisionSecretRef struct {
//
// +kubebuilder:validation:Required
Namespace string `json:"namespace"`
+
+ // The key in the secret data that contains the password.
+ //
+ // +kubebuilder:default="password"
+ // +optional
+ Password string `json:"password,omitempty"`
}
// ClusterComponentConfig represents a config with its source bound.
diff --git a/apis/apps/v1/zz_generated.deepcopy.go b/apis/apps/v1/zz_generated.deepcopy.go
index 4524d020a5e..977074f8339 100644
--- a/apis/apps/v1/zz_generated.deepcopy.go
+++ b/apis/apps/v1/zz_generated.deepcopy.go
@@ -1553,6 +1553,11 @@ func (in *ComponentStatus) DeepCopy() *ComponentStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentSystemAccount) DeepCopyInto(out *ComponentSystemAccount) {
*out = *in
+ if in.Disabled != nil {
+ in, out := &in.Disabled, &out.Disabled
+ *out = new(bool)
+ **out = **in
+ }
if in.PasswordConfig != nil {
in, out := &in.PasswordConfig, &out.PasswordConfig
*out = new(PasswordConfig)
@@ -3265,12 +3270,12 @@ func (in *SidecarDefinitionStatus) DeepCopy() *SidecarDefinitionStatus {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SystemAccount) DeepCopyInto(out *SystemAccount) {
*out = *in
- out.PasswordGenerationPolicy = in.PasswordGenerationPolicy
- if in.SecretRef != nil {
- in, out := &in.SecretRef, &out.SecretRef
- *out = new(ProvisionSecretRef)
+ if in.Statement != nil {
+ in, out := &in.Statement, &out.Statement
+ *out = new(SystemAccountStatement)
**out = **in
}
+ out.PasswordGenerationPolicy = in.PasswordGenerationPolicy
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemAccount.
@@ -3283,6 +3288,21 @@ func (in *SystemAccount) DeepCopy() *SystemAccount {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SystemAccountStatement) DeepCopyInto(out *SystemAccountStatement) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SystemAccountStatement.
+func (in *SystemAccountStatement) DeepCopy() *SystemAccountStatement {
+ if in == nil {
+ return nil
+ }
+ out := new(SystemAccountStatement)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLS) DeepCopyInto(out *TLS) {
*out = *in
diff --git a/config/crd/bases/apps.kubeblocks.io_clusters.yaml b/config/crd/bases/apps.kubeblocks.io_clusters.yaml
index 8d2fa8aa8c8..9dd64a43b69 100644
--- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml
@@ -5311,6 +5311,10 @@ spec:
ComponentDefinition.
items:
properties:
+ disabled:
+ default: false
+ description: Specifies whether the system account is disabled.
+ type: boolean
name:
description: The name of the system account.
type: string
@@ -5369,6 +5373,11 @@ spec:
namespace:
description: The namespace where the secret is located.
type: string
+ password:
+ default: password
+ description: The key in the secret data that contains
+ the password.
+ type: string
required:
- name
- namespace
@@ -14047,6 +14056,11 @@ spec:
ComponentDefinition.
items:
properties:
+ disabled:
+ default: false
+ description: Specifies whether the system account
+ is disabled.
+ type: boolean
name:
description: The name of the system account.
type: string
@@ -14106,6 +14120,11 @@ spec:
description: The namespace where the secret is
located.
type: string
+ password:
+ default: password
+ description: The key in the secret data that contains
+ the password.
+ type: string
required:
- name
- namespace
diff --git a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
index fe6bec4e932..a3568885f25 100644
--- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
@@ -4501,9 +4501,9 @@ spec:
The container executing this action has access to following variables:
- - KB_ACCOUNT_NAME: The name of the system account to be created.
- - KB_ACCOUNT_PASSWORD: The password for the system account. // TODO: how to pass the password securely?
- - KB_ACCOUNT_STATEMENT: The statement used to create the system account.
+ - KB_ACCOUNT_NAME: The name of the system account to be manipulated.
+ - KB_ACCOUNT_PASSWORD: The password for the system account.
+ - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account.
Note: This field is immutable once it has been set.
@@ -16727,30 +16727,35 @@ spec:
Cannot be updated.
type: string
type: object
- secretRef:
+ statement:
description: |-
- Refers to the secret from which data will be copied to create the new account.
+ Defines the statements used to create, delete, and update the account.
This field is immutable once set.
properties:
- name:
- description: The unique identifier of the secret.
+ create:
+ description: |-
+ The statement to create a new account with the necessary privileges.
+
+
+ This field is immutable once set.
type: string
- namespace:
- description: The namespace where the secret is located.
+ delete:
+ description: |-
+ The statement to delete a account.
+
+
+ This field is immutable once set.
type: string
- required:
- - name
- - namespace
- type: object
- statement:
- description: |-
- Defines the statement used to create the account with the necessary privileges.
+ update:
+ description: |-
+ The statement to update an existing account.
- This field is immutable once set.
- type: string
+ This field is immutable once set.
+ type: string
+ type: object
required:
- name
type: object
diff --git a/config/crd/bases/apps.kubeblocks.io_components.yaml b/config/crd/bases/apps.kubeblocks.io_components.yaml
index 4b2c768c021..7f2a443af93 100644
--- a/config/crd/bases/apps.kubeblocks.io_components.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_components.yaml
@@ -5505,6 +5505,10 @@ spec:
description: Overrides system accounts defined in referenced ComponentDefinition.
items:
properties:
+ disabled:
+ default: false
+ description: Specifies whether the system account is disabled.
+ type: boolean
name:
description: The name of the system account.
type: string
@@ -5563,6 +5567,11 @@ spec:
namespace:
description: The namespace where the secret is located.
type: string
+ password:
+ default: password
+ description: The key in the secret data that contains the
+ password.
+ type: string
required:
- name
- namespace
diff --git a/controllers/apps/component_controller_test.go b/controllers/apps/component_controller_test.go
index b98dda0faed..7ebc97f9b0c 100644
--- a/controllers/apps/component_controller_test.go
+++ b/controllers/apps/component_controller_test.go
@@ -963,98 +963,6 @@ var _ = Describe("Component Controller", func() {
})).Should(Succeed())
}
- testCompSystemAccount := func(compName, compDefName string) {
- createClusterObj(compName, compDefName, nil)
-
- By("check root account")
- rootSecretKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
- }
- Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) {
- g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root")))
- g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
- })).Should(Succeed())
-
- By("check admin account")
- adminSecretKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
- }
- Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
- g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
- g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
- })).Should(Succeed())
-
- By("mock component as Running")
- mockCompRunning(compName)
-
- By("wait accounts to be provisioned")
- Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) {
- g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue())
- var cond *metav1.Condition
- for i, c := range comp.Status.Conditions {
- if c.Type == accountProvisionConditionType {
- cond = &comp.Status.Conditions[i]
- break
- }
- }
- g.Expect(cond).ShouldNot(BeNil())
- g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue))
- g.Expect(cond.Message).ShouldNot(ContainSubstring("root"))
- g.Expect(cond.Message).Should(ContainSubstring("admin"))
- })).Should(Succeed())
- }
-
- testCompSystemAccountOverride := func(compName, compDefName string) {
- passwordConfig := &kbappsv1.PasswordConfig{
- Length: 29,
- }
- secret := corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: testCtx.DefaultNamespace,
- Name: "sysaccount-override",
- },
- StringData: map[string]string{
- constant.AccountPasswdForSecret: "sysaccount-override",
- },
- }
- secretRef := func() *kbappsv1.ProvisionSecretRef {
- Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed())
- return &kbappsv1.ProvisionSecretRef{
- Name: secret.Name,
- Namespace: testCtx.DefaultNamespace,
- }
- }
-
- createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
- f.AddSystemAccount("root", passwordConfig, nil).
- AddSystemAccount("admin", nil, secretRef()).
- AddSystemAccount("not-exist", nil, nil)
- })
-
- By("check root account")
- rootSecretKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
- }
- Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) {
- g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root")))
- g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
- g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length)))
- })).Should(Succeed())
-
- By("check admin account")
- adminSecretKey := types.NamespacedName{
- Namespace: compObj.Namespace,
- Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
- }
- Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
- g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
- g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, secret.Data[constant.AccountPasswdForSecret]))
- })).Should(Succeed())
- }
-
testCompVars := func(compName, compDefName string) {
compDefKey := client.ObjectKeyFromObject(compDefObj)
Eventually(testapps.GetAndChangeObj(&testCtx, compDefKey, func(compDef *kbappsv1.ComponentDefinition) {
@@ -1426,6 +1334,293 @@ var _ = Describe("Component Controller", func() {
checkRBACResourcesExistence(saName, true)
}
+ testCompSystemAccount := func(compName, compDefName string) {
+ secret := corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: testCtx.DefaultNamespace,
+ Name: "sysaccount-admin",
+ },
+ StringData: map[string]string{
+ constant.AccountPasswdForSecret: "sysaccount-admin",
+ },
+ }
+ secretRef := func() *kbappsv1.ProvisionSecretRef {
+ Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed())
+ return &kbappsv1.ProvisionSecretRef{
+ Name: secret.Name,
+ Namespace: testCtx.DefaultNamespace,
+ }
+ }
+
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.AddSystemAccount("admin", false, nil, secretRef())
+ })
+
+ By("check root account")
+ var rootHashedPassword string
+ rootSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root")))
+ g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
+ rootHashedPassword = secret.Annotations[systemAccountHashAnnotation]
+ g.Expect(rootHashedPassword).Should(BeEmpty()) // kb generated password
+ })).Should(Succeed())
+
+ By("check admin account")
+ var adminHashedPassword string
+ adminSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
+ g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
+ adminHashedPassword = secret.Annotations[systemAccountHashAnnotation]
+ g.Expect(adminHashedPassword).ShouldNot(BeEmpty()) // user-provided
+ })).Should(Succeed())
+
+ By("mock component as Running")
+ mockCompRunning(compName)
+
+ By("wait accounts to be provisioned")
+ Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) {
+ g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue())
+ var cond *metav1.Condition
+ for i, c := range comp.Status.Conditions {
+ if c.Type == accountProvisionConditionType {
+ cond = &comp.Status.Conditions[i]
+ break
+ }
+ }
+ g.Expect(cond).ShouldNot(BeNil())
+ g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue))
+ g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "root", rootHashedPassword)))
+ g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "admin", adminHashedPassword)))
+ })).Should(Succeed())
+ }
+
+ testCompSystemAccountOverride := func(compName, compDefName string) {
+ passwordConfig := &kbappsv1.PasswordConfig{
+ Length: 29,
+ }
+ secret := corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: testCtx.DefaultNamespace,
+ Name: "sysaccount-override",
+ },
+ StringData: map[string]string{
+ constant.AccountPasswdForSecret: "sysaccount-override",
+ },
+ }
+ secretRef := func() *kbappsv1.ProvisionSecretRef {
+ Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed())
+ return &kbappsv1.ProvisionSecretRef{
+ Name: secret.Name,
+ Namespace: testCtx.DefaultNamespace,
+ }
+ }
+
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.AddSystemAccount("root", false, passwordConfig, nil).
+ AddSystemAccount("admin", false, nil, secretRef())
+ })
+
+ By("check root account")
+ rootSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root")))
+ g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
+ g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length)))
+ })).Should(Succeed())
+
+ By("check admin account")
+ adminSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, secret.Data[constant.AccountPasswdForSecret]))
+ })).Should(Succeed())
+ }
+
+ testCompSystemAccountDisable := func(compName, compDefName string) {
+ passwordConfig := &kbappsv1.PasswordConfig{
+ Length: 29,
+ }
+
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.AddSystemAccount("root", false, passwordConfig, nil).
+ // disable the admin account
+ AddSystemAccount("admin", true, passwordConfig, nil)
+ })
+
+ By("check root account")
+ rootSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
+ }
+ rootSecret := &corev1.Secret{}
+ Eventually(testapps.CheckObjExists(&testCtx, rootSecretKey, rootSecret, true)).Should(Succeed())
+
+ By("check admin account")
+ adminSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
+ }
+ adminSecret := &corev1.Secret{}
+ Consistently(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, false)).Should(Succeed())
+ }
+
+ testCompSystemAccountDisableAfterProvision := func(compName, compDefName string) {
+ passwordConfig := &kbappsv1.PasswordConfig{
+ Length: 29,
+ }
+
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.AddSystemAccount("root", false, passwordConfig, nil).
+ AddSystemAccount("admin", false, passwordConfig, nil)
+ })
+
+ By("check the root account")
+ rootSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
+ }
+ rootSecret := &corev1.Secret{}
+ Eventually(testapps.CheckObjExists(&testCtx, rootSecretKey, rootSecret, true)).Should(Succeed())
+
+ By("check the admin account")
+ adminSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
+ }
+ adminSecret := &corev1.Secret{}
+ Eventually(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, true)).Should(Succeed())
+
+ By("disable the admin account")
+ Expect(testapps.GetAndChangeObj(&testCtx, clusterKey, func(cluster *kbappsv1.Cluster) {
+ for i, comp := range cluster.Spec.ComponentSpecs {
+ if comp.Name == compName {
+ for j, account := range cluster.Spec.ComponentSpecs[i].SystemAccounts {
+ if account.Name == "admin" {
+ cluster.Spec.ComponentSpecs[i].SystemAccounts[j].Disabled = ptr.To(true)
+ }
+ }
+ }
+ }
+ })()).Should(Succeed())
+
+ By("check the admin account is disabled")
+ Eventually(testapps.CheckObjExists(&testCtx, adminSecretKey, adminSecret, false)).Should(Succeed())
+ }
+
+ testCompSystemAccountUpdate := func(compName, compDefName string) {
+ passwordConfig := &kbappsv1.PasswordConfig{
+ Length: 29,
+ }
+ secret := corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Namespace: testCtx.DefaultNamespace,
+ Name: "sysaccount-update",
+ },
+ StringData: map[string]string{
+ "sysaccount-update": "sysaccount-update",
+ },
+ }
+ secretRef := func() *kbappsv1.ProvisionSecretRef {
+ Expect(testCtx.CreateObj(testCtx.Ctx, &secret)).Should(Succeed())
+ return &kbappsv1.ProvisionSecretRef{
+ Name: secret.Name,
+ Namespace: testCtx.DefaultNamespace,
+ Password: "sysaccount-update",
+ }
+ }
+
+ createClusterObj(compName, compDefName, func(f *testapps.MockClusterFactory) {
+ f.AddSystemAccount("root", false, passwordConfig, nil).
+ AddSystemAccount("admin", false, nil, secretRef())
+ })
+
+ By("check root account")
+ var rootHashedPassword string
+ rootSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "root"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, rootSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("root")))
+ g.Expect(secret.Data).Should(HaveKey(constant.AccountPasswdForSecret))
+ g.Expect(secret.Data[constant.AccountPasswdForSecret]).Should(HaveLen(int(passwordConfig.Length)))
+ rootHashedPassword = secret.Annotations[systemAccountHashAnnotation]
+ g.Expect(rootHashedPassword).Should(BeEmpty()) // kb generated password
+ })).Should(Succeed())
+
+ By("check admin account")
+ var adminHashedPassword string
+ adminSecretKey := types.NamespacedName{
+ Namespace: compObj.Namespace,
+ Name: constant.GenerateAccountSecretName(clusterObj.Name, compName, "admin"),
+ }
+ Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, []byte("sysaccount-update")))
+ adminHashedPassword = secret.Annotations[systemAccountHashAnnotation]
+ g.Expect(adminHashedPassword).ShouldNot(BeEmpty()) // user-provided
+ })).Should(Succeed())
+
+ By("mock component as Running")
+ mockCompRunning(compName)
+
+ By("update the password of admin account")
+ Expect(testapps.GetAndChangeObj(&testCtx, client.ObjectKeyFromObject(&secret), func(obj *corev1.Secret) {
+ if obj.StringData == nil {
+ obj.StringData = map[string]string{}
+ }
+ obj.StringData["sysaccount-update"] = "sysaccount-update-new"
+ })()).Should(Succeed())
+
+ By("trigger the component to reconcile")
+ Expect(testapps.GetAndChangeObj(&testCtx, compKey, func(comp *kbappsv1.Component) {
+ if comp.Annotations == nil {
+ comp.Annotations = map[string]string{}
+ }
+ comp.Annotations["reconcile"] = time.Now().String()
+ })()).Should(Succeed())
+
+ By("check the admin account updated")
+ var updatedAdminHashedPassword string
+ Eventually(testapps.CheckObj(&testCtx, adminSecretKey, func(g Gomega, secret *corev1.Secret) {
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountNameForSecret, []byte("admin")))
+ g.Expect(secret.Data).Should(HaveKeyWithValue(constant.AccountPasswdForSecret, []byte("sysaccount-update-new")))
+ updatedAdminHashedPassword = secret.Annotations[systemAccountHashAnnotation]
+ g.Expect(updatedAdminHashedPassword).ShouldNot(BeEmpty()) // user-provided
+ g.Expect(updatedAdminHashedPassword).ShouldNot(Equal(adminHashedPassword))
+ })).Should(Succeed())
+
+ By("wait accounts to be updated")
+ Eventually(testapps.CheckObj(&testCtx, compKey, func(g Gomega, comp *kbappsv1.Component) {
+ g.Expect(len(comp.Status.Conditions) > 0).Should(BeTrue())
+ var cond *metav1.Condition
+ for i, c := range comp.Status.Conditions {
+ if c.Type == accountProvisionConditionType {
+ cond = &comp.Status.Conditions[i]
+ break
+ }
+ }
+ g.Expect(cond).ShouldNot(BeNil())
+ g.Expect(cond.Status).Should(BeEquivalentTo(metav1.ConditionTrue))
+ g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "root", rootHashedPassword)))
+ g.Expect(cond.Message).Should(ContainSubstring(fmt.Sprintf("%s:%s", "admin", updatedAdminHashedPassword)))
+ })).Should(Succeed())
+ }
+
testThreeReplicas := func(compName, compDefName string) {
const replicas = 3
@@ -1621,14 +1816,6 @@ var _ = Describe("Component Controller", func() {
testCompService(defaultCompName, compDefName)
})
- It("with component system accounts", func() {
- testCompSystemAccount(defaultCompName, compDefName)
- })
-
- It("with component system accounts - override", func() {
- testCompSystemAccountOverride(defaultCompName, compDefName)
- })
-
It("with component vars", func() {
testCompVars(defaultCompName, compDefName)
})
@@ -1662,6 +1849,36 @@ var _ = Describe("Component Controller", func() {
})
})
+ Context("system account", func() {
+ BeforeEach(func() {
+ createAllDefinitionObjects()
+ })
+
+ AfterEach(func() {
+ cleanEnv()
+ })
+
+ It("provisioning", func() {
+ testCompSystemAccount(defaultCompName, compDefName)
+ })
+
+ It("override", func() {
+ testCompSystemAccountOverride(defaultCompName, compDefName)
+ })
+
+ It("disable", func() {
+ testCompSystemAccountDisable(defaultCompName, compDefName)
+ })
+
+ It("disable - after provision", func() {
+ testCompSystemAccountDisableAfterProvision(defaultCompName, compDefName)
+ })
+
+ It("update", func() {
+ testCompSystemAccountUpdate(defaultCompName, compDefName)
+ })
+ })
+
Context("h-scaling", func() {
BeforeEach(func() {
createAllDefinitionObjects()
diff --git a/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go
index ed7518649f8..6dc6d24d18d 100644
--- a/controllers/apps/componentdefinition_controller.go
+++ b/controllers/apps/componentdefinition_controller.go
@@ -342,19 +342,23 @@ func (r *ComponentDefinitionReconciler) validateConfigs(cli client.Client, rctx
func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client, rctx intctrlutil.RequestCtx,
cmpd *appsv1.ComponentDefinition) error {
- for _, v := range cmpd.Spec.SystemAccounts {
- if v.SecretRef == nil && !v.InitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) {
- return fmt.Errorf(`the AccountProvision action is needed to provision system account %s`, v.Name)
- }
- }
if !checkUniqueItemWithValue(cmpd.Spec.SystemAccounts, "Name", nil) {
return fmt.Errorf("duplicate system accounts are not allowed")
}
+
+ hasNonInitAccount := false
for _, account := range cmpd.Spec.SystemAccounts {
- if !account.InitAccount && len(account.Statement) == 0 && account.SecretRef == nil {
- return fmt.Errorf("the Statement or SecretRef must be provided to create system account: %s", account.Name)
+ if account.InitAccount {
+ continue
+ }
+ hasNonInitAccount = true
+ if account.Statement == nil || len(account.Statement.Create) == 0 {
+ return fmt.Errorf("the create statement must be provided to provision system account: %s", account.Name)
}
}
+ if hasNonInitAccount && (cmpd.Spec.LifecycleActions == nil || cmpd.Spec.LifecycleActions.AccountProvision == nil) {
+ return fmt.Errorf("the AccountProvision action is needed to provision system accounts")
+ }
return nil
}
diff --git a/controllers/apps/transformer_cluster_component.go b/controllers/apps/transformer_cluster_component.go
index 49db7eedb8e..97365facfbf 100644
--- a/controllers/apps/transformer_cluster_component.go
+++ b/controllers/apps/transformer_cluster_component.go
@@ -196,6 +196,7 @@ func copyAndMergeComponent(oldCompObj, newCompObj *appsv1.Component) *appsv1.Com
compObjCopy.Spec.VolumeClaimTemplates = compProto.Spec.VolumeClaimTemplates
compObjCopy.Spec.Volumes = compProto.Spec.Volumes
compObjCopy.Spec.Services = compProto.Spec.Services
+ compObjCopy.Spec.SystemAccounts = compProto.Spec.SystemAccounts
compObjCopy.Spec.Replicas = compProto.Spec.Replicas
compObjCopy.Spec.Configs = compProto.Spec.Configs
compObjCopy.Spec.ServiceAccountName = compProto.Spec.ServiceAccountName
diff --git a/controllers/apps/transformer_cluster_sharding_account.go b/controllers/apps/transformer_cluster_sharding_account.go
index 3718ed19153..5e87e47fd24 100644
--- a/controllers/apps/transformer_cluster_sharding_account.go
+++ b/controllers/apps/transformer_cluster_sharding_account.go
@@ -118,17 +118,7 @@ func (t *clusterShardingAccountTransformer) newSystemAccountSecret(transCtx *clu
if err != nil {
return nil, err
}
-
- var password []byte
- switch {
- case account.SecretRef != nil:
- var err error
- if password, err = t.getPasswordFromSecret(transCtx, account); err != nil {
- return nil, err
- }
- default:
- password = t.buildPassword(transCtx, account, sharding.Name)
- }
+ password := t.buildPassword(transCtx, account, sharding.Name)
return t.newAccountSecretWithPassword(transCtx, sharding, accountName, password)
}
@@ -152,7 +142,6 @@ func (t *clusterShardingAccountTransformer) definedSystemAccount(transCtx *clust
if compAccount.PasswordConfig != nil {
account.PasswordGenerationPolicy = *compAccount.PasswordConfig
}
- account.SecretRef = compAccount.SecretRef
}
return *account
}
@@ -165,21 +154,6 @@ func (t *clusterShardingAccountTransformer) definedSystemAccount(transCtx *clust
return appsv1.SystemAccount{}, fmt.Errorf("system account %s not found in component definition %s", accountName, compDef.Name)
}
-func (t *clusterShardingAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account appsv1.SystemAccount) ([]byte, error) {
- secretKey := types.NamespacedName{
- Namespace: account.SecretRef.Namespace,
- Name: account.SecretRef.Name,
- }
- secret := &corev1.Secret{}
- if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil {
- return nil, err
- }
- if len(secret.Data) == 0 || len(secret.Data[constant.AccountPasswdForSecret]) == 0 {
- return nil, fmt.Errorf("referenced account secret has no required credential field")
- }
- return secret.Data[constant.AccountPasswdForSecret], nil
-}
-
func (t *clusterShardingAccountTransformer) buildPassword(transCtx *clusterTransformContext, account appsv1.SystemAccount, shardingName string) []byte {
password := []byte(factory.GetRestoreSystemAccountPassword(transCtx.Cluster.Annotations, shardingName, account.Name))
if len(password) == 0 {
@@ -227,20 +201,21 @@ func (t *clusterShardingAccountTransformer) rewriteSystemAccount(transCtx *clust
var (
cluster = transCtx.Cluster
)
- account := appsv1.ComponentSystemAccount{
+ newAccount := appsv1.ComponentSystemAccount{
Name: accountName,
SecretRef: &appsv1.ProvisionSecretRef{
Name: shardingAccountSecretName(cluster.Name, sharding.Name, accountName),
Namespace: cluster.Namespace,
},
}
- for i := range sharding.Template.SystemAccounts {
- if sharding.Template.SystemAccounts[i].Name == accountName {
- sharding.Template.SystemAccounts[i] = account
+ for i, account := range sharding.Template.SystemAccounts {
+ if account.Name == accountName {
+ newAccount.Disabled = account.Disabled
+ sharding.Template.SystemAccounts[i] = newAccount
return
}
}
- sharding.Template.SystemAccounts = []appsv1.ComponentSystemAccount{account}
+ sharding.Template.SystemAccounts = []appsv1.ComponentSystemAccount{newAccount}
}
func shardingAccountSecretName(cluster, sharding, account string) string {
diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/transformer_component_account.go
index 71412527443..dcb8616f0b7 100644
--- a/controllers/apps/transformer_component_account.go
+++ b/controllers/apps/transformer_component_account.go
@@ -24,9 +24,11 @@ import (
"reflect"
"strings"
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
+ "k8s.io/apimachinery/pkg/util/sets"
"sigs.k8s.io/controller-runtime/pkg/client"
appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
@@ -40,6 +42,11 @@ import (
ctrlutil "github.com/apecloud/kubeblocks/pkg/controllerutil"
)
+const (
+ systemAccountLabel = "apps.kubeblocks.io/system-account"
+ systemAccountHashAnnotation = "apps.kubeblocks.io/system-account-hash"
+)
+
// componentAccountTransformer handles component system accounts.
type componentAccountTransformer struct{}
@@ -55,56 +62,105 @@ func (t *componentAccountTransformer) Transform(ctx graph.TransformContext, dag
return nil
}
- synthesizeComp := transCtx.SynthesizeComponent
+ synthesizedComp := transCtx.SynthesizeComponent
graphCli, _ := transCtx.Client.(model.GraphClient)
- for _, account := range synthesizeComp.SystemAccounts {
- existSecret, err := t.checkAccountSecretExist(ctx, synthesizeComp, account)
- if err != nil {
+ // exist account objects
+ secrets, err := listSystemAccountObjects(ctx, synthesizedComp)
+ if err != nil {
+ return err
+ }
+ runningNameSet := sets.New(maps.Keys(secrets)...)
+
+ // proto accounts
+ accounts, err := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts,
+ transCtx.Component.Spec.SystemAccounts, false)
+ if err != nil {
+ return err
+ }
+ protoNameSet := sets.New(maps.Keys(accounts)...)
+
+ createSet, deleteSet, updateSet := setDiff(runningNameSet, protoNameSet)
+
+ for _, name := range sets.List(createSet) {
+ if err := t.createAccount(transCtx, dag, graphCli, accounts[name]); err != nil {
return err
}
- secret, err := t.buildAccountSecret(transCtx, synthesizeComp, account)
- if err != nil {
+ }
+
+ for _, name := range sets.List(deleteSet) {
+ t.deleteAccount(transCtx, dag, graphCli, secrets[name])
+ }
+
+ for _, name := range sets.List(updateSet) {
+ if err := t.updateAccount(transCtx, dag, graphCli, accounts[name], secrets[name]); err != nil {
return err
}
+ }
- if existSecret == nil {
- graphCli.Create(dag, secret, inUniversalContext4G())
- continue
- }
+ return nil
+}
- // just update existed account secret metadata if needed
- existSecretCopy := existSecret.DeepCopy()
- ctrlutil.MergeMetadataMapInplace(secret.Labels, &existSecretCopy.Labels)
- ctrlutil.MergeMetadataMapInplace(secret.Annotations, &existSecretCopy.Annotations)
- if !reflect.DeepEqual(existSecret, existSecretCopy) {
- graphCli.Update(dag, existSecret, existSecretCopy, inUniversalContext4G())
- }
+func (t *componentAccountTransformer) createAccount(transCtx *componentTransformContext,
+ dag *graph.DAG, graphCli model.GraphClient, account synthesizedSystemAccount) error {
+ secret, err := t.buildAccountSecret(transCtx, transCtx.SynthesizeComponent, account)
+ if err != nil {
+ return err
+ }
+ if err = t.buildAccountHash(account, nil, secret); err != nil {
+ return err
}
- // TODO: (good-first-issue) if an account is deleted from the Spec, the secret and account should be deleted
+ graphCli.Create(dag, secret, inUniversalContext4G())
return nil
}
-func (t *componentAccountTransformer) checkAccountSecretExist(ctx graph.TransformContext,
- synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount) (*corev1.Secret, error) {
- secretKey := types.NamespacedName{
- Namespace: synthesizeComp.Namespace,
- Name: constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name),
+func (t *componentAccountTransformer) deleteAccount(transCtx *componentTransformContext,
+ dag *graph.DAG, graphCli model.GraphClient, secret *corev1.Secret) {
+ graphCli.Delete(dag, secret, inUniversalContext4G())
+}
+
+func (t *componentAccountTransformer) updateAccount(transCtx *componentTransformContext,
+ dag *graph.DAG, graphCli model.GraphClient, account synthesizedSystemAccount, running *corev1.Secret) error {
+ secret, err := t.buildAccountSecret(transCtx, transCtx.SynthesizeComponent, account)
+ if err != nil {
+ return err
}
- secret := &corev1.Secret{}
- err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret)
- switch {
- case err == nil:
- return secret, nil
- case apierrors.IsNotFound(err):
- return nil, nil
- default:
- return nil, err
+ if err = t.buildAccountHash(account, running, secret); err != nil {
+ return err
+ }
+
+ runningCopy := running.DeepCopy()
+ if account.SecretRef != nil {
+ // sync password from the external secret
+ runningCopy.Data[constant.AccountPasswdForSecret] = secret.Data[constant.AccountPasswdForSecret]
+ }
+ ctrlutil.MergeMetadataMapInplace(secret.Labels, &runningCopy.Labels)
+ ctrlutil.MergeMetadataMapInplace(secret.Annotations, &runningCopy.Annotations)
+ if !reflect.DeepEqual(running, runningCopy) {
+ graphCli.Update(dag, running, runningCopy, inUniversalContext4G())
+ }
+ return nil
+}
+
+func (t *componentAccountTransformer) buildAccountHash(account synthesizedSystemAccount, running, secret *corev1.Secret) error {
+ if account.SecretRef == nil {
+ return nil
+ }
+ if running != nil {
+ hashedPassword := running.Annotations[systemAccountHashAnnotation]
+ if verifySystemAccountPassword(secret, []byte(hashedPassword)) {
+ if secret.Annotations == nil {
+ secret.Annotations = map[string]string{}
+ }
+ secret.Annotations[systemAccountHashAnnotation] = hashedPassword
+ return nil // have the same password
+ }
}
+ return signatureSystemAccountPassword(secret)
}
func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransformContext,
- synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount) (*corev1.Secret, error) {
+ synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount) (*corev1.Secret, error) {
var password []byte
switch {
case account.SecretRef != nil:
@@ -118,7 +174,7 @@ func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransform
return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password)
}
-func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account appsv1.SystemAccount) ([]byte, error) {
+func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account synthesizedSystemAccount) ([]byte, error) {
secretKey := types.NamespacedName{
Namespace: account.SecretRef.Namespace,
Name: account.SecretRef.Name,
@@ -127,13 +183,18 @@ func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformC
if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil {
return nil, err
}
- if len(secret.Data) == 0 || len(secret.Data[constant.AccountPasswdForSecret]) == 0 {
- return nil, fmt.Errorf("referenced account secret has no required credential field")
+
+ passwordKey := constant.AccountPasswdForSecret
+ if len(account.SecretRef.Password) > 0 {
+ passwordKey = account.SecretRef.Password
+ }
+ if len(secret.Data) == 0 || len(secret.Data[passwordKey]) == 0 {
+ return nil, fmt.Errorf("referenced account secret has no required credential field: %s", passwordKey)
}
- return secret.Data[constant.AccountPasswdForSecret], nil
+ return secret.Data[passwordKey], nil
}
-func (t *componentAccountTransformer) buildPassword(ctx *componentTransformContext, account appsv1.SystemAccount) []byte {
+func (t *componentAccountTransformer) buildPassword(ctx *componentTransformContext, account synthesizedSystemAccount) []byte {
// get restore password if exists during recovery.
password := factory.GetRestoreSystemAccountPassword(ctx.SynthesizeComponent.Annotations, ctx.SynthesizeComponent.Name, account.Name)
if account.InitAccount && password == "" {
@@ -147,7 +208,7 @@ func (t *componentAccountTransformer) buildPassword(ctx *componentTransformConte
return []byte(password)
}
-func (t *componentAccountTransformer) generatePassword(account appsv1.SystemAccount) []byte {
+func (t *componentAccountTransformer) generatePassword(account synthesizedSystemAccount) []byte {
config := account.PasswordGenerationPolicy
passwd, _ := common.GeneratePassword((int)(config.Length), (int)(config.NumDigits), (int)(config.NumSymbols), false, config.Seed)
switch config.LetterCase {
@@ -160,21 +221,107 @@ func (t *componentAccountTransformer) generatePassword(account appsv1.SystemAcco
}
func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *componentTransformContext,
- synthesizeComp *component.SynthesizedComponent, account appsv1.SystemAccount, password []byte) (*corev1.Secret, error) {
+ synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount, password []byte) (*corev1.Secret, error) {
secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, account.Name)
secret := builder.NewSecretBuilder(synthesizeComp.Namespace, secretName).
// Priority: static < dynamic < built-in
AddLabelsInMap(synthesizeComp.StaticLabels).
AddLabelsInMap(synthesizeComp.DynamicLabels).
AddLabelsInMap(constant.GetCompLabels(synthesizeComp.ClusterName, synthesizeComp.Name)).
+ AddLabels(systemAccountLabel, account.Name).
AddAnnotationsInMap(synthesizeComp.StaticAnnotations).
AddAnnotationsInMap(synthesizeComp.DynamicAnnotations).
PutData(constant.AccountNameForSecret, []byte(account.Name)).
PutData(constant.AccountPasswdForSecret, password).
- SetImmutable(true).
+ // SetImmutable(true).
GetObject()
if err := setCompOwnershipNFinalizer(ctx.Component, secret); err != nil {
return nil, err
}
return secret, nil
}
+
+func listSystemAccountObjects(ctx graph.TransformContext,
+ synthesizedComp *component.SynthesizedComponent) (map[string]*corev1.Secret, error) {
+ opts := []client.ListOption{
+ client.InNamespace(synthesizedComp.Namespace),
+ client.MatchingLabels(constant.GetCompLabels(synthesizedComp.ClusterName, synthesizedComp.Name)),
+ }
+ secretList := &corev1.SecretList{}
+ if err := ctx.GetClient().List(ctx.GetContext(), secretList, opts...); err != nil {
+ return nil, err
+ }
+
+ m := make(map[string]*corev1.Secret)
+ for i, secret := range secretList.Items {
+ if accountName, ok := secret.Labels[systemAccountLabel]; ok {
+ m[accountName] = &secretList.Items[i]
+ }
+ }
+ return m, nil
+}
+
+func signatureSystemAccountPassword(secret *corev1.Secret) error {
+ password := secret.Data[constant.AccountPasswdForSecret]
+ hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
+ if err != nil {
+ return err
+ }
+ if secret.Annotations == nil {
+ secret.Annotations = map[string]string{}
+ }
+ secret.Annotations[systemAccountHashAnnotation] = string(hashedPassword)
+ return nil
+}
+
+func verifySystemAccountPassword(secret *corev1.Secret, hashedPassword []byte) bool {
+ password := secret.Data[constant.AccountPasswdForSecret]
+ err := bcrypt.CompareHashAndPassword(hashedPassword, password)
+ return err == nil
+}
+
+type synthesizedSystemAccount struct {
+ appsv1.SystemAccount
+ Disabled *bool
+ SecretRef *appsv1.ProvisionSecretRef
+}
+
+func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount,
+ compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) (map[string]synthesizedSystemAccount, error) {
+ accounts := make(map[string]synthesizedSystemAccount)
+ for _, account := range compDefAccounts {
+ accounts[account.Name] = synthesizedSystemAccount{
+ SystemAccount: account,
+ }
+ }
+
+ merge := func(account synthesizedSystemAccount, compAccount appsv1.ComponentSystemAccount) synthesizedSystemAccount {
+ if compAccount.PasswordConfig != nil {
+ account.PasswordGenerationPolicy = *compAccount.PasswordConfig
+ }
+ account.Disabled = compAccount.Disabled
+ account.SecretRef = compAccount.SecretRef
+ return account
+ }
+
+ for i := range compAccounts {
+ account, ok := accounts[compAccounts[i].Name]
+ if !ok {
+ return nil, fmt.Errorf("system account %s not defined in component definition", compAccounts[i].Name)
+ }
+ accounts[account.Name] = merge(account, compAccounts[i])
+ }
+
+ if !keepDisabled {
+ for _, name := range maps.Keys(accounts) {
+ account := accounts[name]
+ if account.Disabled != nil && *account.Disabled {
+ if account.InitAccount {
+ return nil, fmt.Errorf("cannot disable init system account: %s", name)
+ }
+ delete(accounts, name)
+ }
+ }
+ }
+ return accounts, nil
+}
diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go
index 1598da82963..2431b7f7675 100644
--- a/controllers/apps/transformer_component_account_provision.go
+++ b/controllers/apps/transformer_component_account_provision.go
@@ -20,12 +20,15 @@ along with this program. If not, see
The container executing this action has access to following variables:
Note: This field is immutable once it has been set.
@@ -6435,6 +6436,18 @@ stringdisabled
Specifies whether the system account is disabled.
+passwordConfig
-(Appears on:ComponentSystemAccount, SystemAccount) +(Appears on:ComponentSystemAccount)
ProvisionSecretRef represents the reference to a secret.
@@ -8754,6 +8767,18 @@ stringThe namespace where the secret is located.
password
The key in the secret data that contains the password.
+statement
Defines the statement used to create the account with the necessary privileges.
+Defines the statements used to create, delete, and update the account.
This field is immutable once set.
This field is immutable once set.
+ + ++(Appears on:SystemAccount) +
+Field | +Description | +
---|---|
-secretRef + create - -ProvisionSecretRef - +string |
(Optional)
- Refers to the secret from which data will be copied to create the new account. +The statement to create a new account with the necessary privileges. +This field is immutable once set. + |
+
+delete + +string + + |
+
+(Optional)
+ The statement to delete a account. +This field is immutable once set. + |
+
+update + +string + + |
+
+(Optional)
+ The statement to update an existing account. This field is immutable once set. |