From b931be3e62d964c731ce9aabc9db2ede83e1cc01 Mon Sep 17 00:00:00 2001
From: Leon
Date: Fri, 3 Jan 2025 13:58:51 +0800
Subject: [PATCH 1/6] init
---
apis/apps/v1/componentdefinition_types.go | 26 +-
apis/apps/v1/types.go | 12 +
apis/apps/v1/zz_generated.deepcopy.go | 28 +-
.../bases/apps.kubeblocks.io_clusters.yaml | 19 ++
...ps.kubeblocks.io_componentdefinitions.yaml | 37 +--
.../bases/apps.kubeblocks.io_components.yaml | 9 +
.../apps/componentdefinition_controller.go | 6 +-
.../transformer_cluster_sharding_account.go | 39 +--
.../apps/transformer_component_account.go | 205 +++++++++++---
...transformer_component_account_provision.go | 267 +++++++++++-------
.../crds/apps.kubeblocks.io_clusters.yaml | 19 ++
...ps.kubeblocks.io_componentdefinitions.yaml | 37 +--
.../crds/apps.kubeblocks.io_components.yaml | 9 +
docs/developer_docs/api-reference/cluster.md | 84 +++++-
pkg/constant/labels.go | 18 +-
.../builder/builder_component_definition.go | 13 -
pkg/controller/component/component_version.go | 3 +
pkg/controller/component/its_convertor.go | 8 +-
.../component/synthesize_component.go | 31 +-
.../apps/componentdefinition_factory.go | 4 +-
pkg/testutil/apps/constant.go | 7 +-
21 files changed, 592 insertions(+), 289 deletions(-)
diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go
index d602b67f9c0..d13980a9e79 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 {
@@ -1736,7 +1752,7 @@ 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_PASSWORD: The password for the system account.
// - KB_ACCOUNT_STATEMENT: The statement used to create 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 0cc99cfc120..1ddbb0b8ce9 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 1e9b6d2c9df..2cea284832e 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)
@@ -3224,12 +3229,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.
@@ -3242,6 +3247,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 2d4cc34c940..97c5f6892a8 100644
--- a/config/crd/bases/apps.kubeblocks.io_clusters.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_clusters.yaml
@@ -5276,6 +5276,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
@@ -5334,6 +5338,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
@@ -13977,6 +13986,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
@@ -14036,6 +14050,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..ec919134eab 100644
--- a/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
@@ -4502,7 +4502,7 @@ spec:
- 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_PASSWORD: The password for the system account.
- KB_ACCOUNT_STATEMENT: The statement used to create the system account.
@@ -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 9da4fd1e563..6629aa574ab 100644
--- a/config/crd/bases/apps.kubeblocks.io_components.yaml
+++ b/config/crd/bases/apps.kubeblocks.io_components.yaml
@@ -5471,6 +5471,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
@@ -5529,6 +5533,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/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go
index ed7518649f8..25040a3a4cd 100644
--- a/controllers/apps/componentdefinition_controller.go
+++ b/controllers/apps/componentdefinition_controller.go
@@ -343,7 +343,7 @@ 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) {
+ if !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)
}
}
@@ -351,8 +351,8 @@ func (r *ComponentDefinitionReconciler) validateSystemAccounts(cli client.Client
return fmt.Errorf("duplicate system accounts are not allowed")
}
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 && (account.Statement == nil || len(account.Statement.Create) == 0) {
+ return fmt.Errorf("the create statement must be provided to provision system account: %s", account.Name)
}
}
return nil
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..fff21d859c4 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,78 @@ 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 := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts, transCtx.Component.Spec.SystemAccounts, false)
+ 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
}
- // 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
+
+ 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) 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:
@@ -115,10 +144,10 @@ func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransform
default:
password = t.buildPassword(ctx, account)
}
- return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password)
+ return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password, account.SecretRef != nil)
}
-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 +156,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 +181,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 +194,108 @@ 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, signature bool) (*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).
GetObject()
+ if signature {
+ if err := signatureSystemAccountPassword(secret); err != nil {
+ return nil, err
+ }
+ }
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 {
+ 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 {
+ continue // ignore it silently
+ }
+ accounts[compAccounts[i].Name] = merge(account, compAccounts[i])
+ }
+
+ if !keepDisabled {
+ for _, name := range maps.Keys(accounts) {
+ if accounts[name].Disabled != nil && *accounts[name].Disabled {
+ delete(accounts, name)
+ }
+ }
+ }
+ return accounts
+}
diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go
index 3408711a905..b3d5762f5b5 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 .
package apps
import (
+ "fmt"
+ "reflect"
"slices"
"strings"
+ "golang.org/x/exp/maps"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "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"
@@ -59,59 +62,148 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont
return nil
}
- if len(transCtx.SynthesizeComponent.SystemAccounts) == 0 {
+ comp := transCtx.Component
+ compDef := transCtx.CompDef
+
+ // provision accounts only when the component is running
+ if comp.Status.Phase != appsv1.RunningComponentPhase {
return nil
}
- if transCtx.Component.Status.Phase != appsv1.RunningComponentPhase {
+
+ // has no lifecycle actions defined, skip the account provision
+ lifecycleActions := compDef.Spec.LifecycleActions
+ if lifecycleActions == nil || lifecycleActions.AccountProvision == nil {
return nil
}
- // TODO: (good-first-issue) if the component's account is deleted by user, we should re-provision it
- cond, provisioned := t.isProvisioned(transCtx)
- if provisioned {
- return nil
+
+ accounts := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true)
+
+ secrets, err1 := listSystemAccountObjects(ctx, transCtx.SynthesizeComponent)
+ if err1 != nil {
+ return err1
}
+ protoNameSet := sets.New(maps.Keys(secrets)...)
- lifecycleActions := transCtx.CompDef.Spec.LifecycleActions
- if lifecycleActions == nil || lifecycleActions.AccountProvision == nil {
+ cond := t.provisionCond(transCtx)
+ provisionedNameSet := sets.New(strings.Split(cond.Message, ",")...)
+
+ createSet, deleteSet, updateSet := setDiff(provisionedNameSet, protoNameSet)
+ if len(createSet) == 0 && len(deleteSet) == 0 && len(updateSet) == 0 {
return nil
}
- lfa, err := t.lifecycleAction(transCtx)
- if err != nil {
- return err
- }
- for _, account := range transCtx.SynthesizeComponent.SystemAccounts {
- // The secret of initAccount should be rendered into the config file,
- // or injected into the container through specific account&password environment variables name supported by the engine.
- // When the engine starts up, it will automatically load and create this account.
- if account.InitAccount {
- continue
+ lfa, err2 := t.lifecycleAction(transCtx)
+ if err2 != nil {
+ return err2
+ }
+
+ var err3 error
+ condCopy := cond.DeepCopy()
+ for _, name := range sets.List(createSet) {
+ if err := t.createAccount(transCtx, lfa, &cond, accounts[name], secrets[name]); err != nil {
+ if err3 == nil {
+ err3 = err
+ }
}
- if t.isAccountProvisioned(cond, account) {
- continue
+ }
+
+ for _, name := range sets.List(deleteSet) {
+ if err := t.deleteAccount(transCtx, lfa, &cond, accounts[name]); err != nil {
+ if err3 == nil {
+ err3 = err
+ }
}
- if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" {
- // TODO: restore account secret from backup.
- // provision account when the component is not recovered from backup
- if err = t.provisionAccount(transCtx, cond, lfa, account); err != nil {
- t.markProvisionAsFailed(transCtx, &cond, err)
- return err
+ }
+
+ for _, name := range sets.List(updateSet) {
+ if err := t.updateAccount(transCtx, lfa, &cond, accounts[name], secrets[name]); err != nil {
+ if err3 == nil {
+ err3 = err
}
}
- t.markAccountProvisioned(&cond, account)
}
- t.markProvisioned(transCtx, cond)
- return nil
+ t.provisionCondDone(transCtx, condCopy, &cond, err3)
+
+ return err3
+}
+
+func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *componentTransformContext) (lifecycle.Lifecycle, error) {
+ synthesizedComp := transCtx.SynthesizeComponent
+ pods, err := component.ListOwnedPods(transCtx.Context, transCtx.Client,
+ synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name)
+ if err != nil {
+ return nil, err
+ }
+ lfa, err := lifecycle.New(transCtx.SynthesizeComponent, nil, pods...)
+ if err != nil {
+ return nil, err
+ }
+ return lfa, nil
+}
+
+func (t *componentAccountProvisionTransformer) createAccount(transCtx *componentTransformContext,
+ lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error {
+ // The secret of initAccount should be rendered into the config file,
+ // or injected into the container through specific account&password environment variables name supported by the engine.
+ // When the engine starts up, it will automatically load and create this account.
+ if account.InitAccount {
+ return nil
+ }
+
+ var err error
+ if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" {
+ // TODO: restore account secret from backup.
+ // provision account when the component is not recovered from backup
+ err = t.provision(transCtx, lfa, account.Statement.Create, secret)
+ }
+
+ if err == nil {
+ // TODO: how about the password restored from backup?
+ t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
+ }
+ return err
+}
+
+func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *componentTransformContext,
+ lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount) error {
+ err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement.Delete, account.Name, "")
+ if lifecycle.IgnoreNotDefined(err) == nil {
+ t.removeProvisionedAccount(cond, account.Name)
+ }
+ return lifecycle.IgnoreNotDefined(err)
+}
+
+func (t *componentAccountProvisionTransformer) updateAccount(transCtx *componentTransformContext,
+ lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error {
+ hashedPassword := t.getHashedPasswordFromCond(cond, account.Name)
+ if verifySystemAccountPassword(secret, []byte(hashedPassword)) {
+ return nil // the password is not changed
+ }
+
+ // TODO: how to notify other apps to update the new password?
+
+ err := t.provision(transCtx, lfa, account.Statement.Update, secret)
+ if err == nil {
+ t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
+ }
+ return err
}
-func (t *componentAccountProvisionTransformer) isProvisioned(transCtx *componentTransformContext) (metav1.Condition, bool) {
+func (t *componentAccountProvisionTransformer) provision(transCtx *componentTransformContext,
+ lfa lifecycle.Lifecycle, statement string, secret *corev1.Secret) error {
+ username, password := secret.Data[constant.AccountNameForSecret], secret.Data[constant.AccountPasswdForSecret]
+ if len(username) == 0 || len(password) == 0 {
+ return nil
+ }
+ err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, statement, string(username), string(password))
+ return lifecycle.IgnoreNotDefined(err)
+}
+
+func (t *componentAccountProvisionTransformer) provisionCond(transCtx *componentTransformContext) metav1.Condition {
for _, cond := range transCtx.Component.Status.Conditions {
if cond.Type == accountProvisionConditionType {
- if cond.Status == metav1.ConditionTrue {
- return cond, true
- }
- return cond, false
+ return cond
}
}
return metav1.Condition{
@@ -121,21 +213,23 @@ func (t *componentAccountProvisionTransformer) isProvisioned(transCtx *component
LastTransitionTime: metav1.Now(),
Reason: accountProvisionConditionReasonInProgress,
Message: "",
- }, false
+ }
}
-func (t *componentAccountProvisionTransformer) markProvisionAsFailed(transCtx *componentTransformContext, cond *metav1.Condition, err error) {
- cond.Status = metav1.ConditionFalse
+func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *componentTransformContext,
+ condCopy, cond *metav1.Condition, err error) {
+ if err == nil {
+ cond.Status = metav1.ConditionTrue
+ cond.Reason = accountProvisionConditionReasonDone
+ } else {
+ cond.Status = metav1.ConditionFalse
+ // cond.Reason = err.Error() // TODO: error
+ }
cond.ObservedGeneration = transCtx.Component.Generation
- cond.LastTransitionTime = metav1.Now()
- // cond.Reason = err.Error() // TODO: error
-}
-func (t *componentAccountProvisionTransformer) markProvisioned(transCtx *componentTransformContext, cond metav1.Condition) {
- cond.Status = metav1.ConditionTrue
- cond.ObservedGeneration = transCtx.Component.Generation
- cond.LastTransitionTime = metav1.Now()
- cond.Reason = accountProvisionConditionReasonDone
+ if !reflect.DeepEqual(cond, condCopy) {
+ cond.LastTransitionTime = metav1.Now()
+ }
conditions := transCtx.Component.Status.Conditions
if conditions == nil {
@@ -145,77 +239,42 @@ func (t *componentAccountProvisionTransformer) markProvisioned(transCtx *compone
for i, c := range conditions {
if c.Type == cond.Type {
existed = true
- conditions[i] = cond
+ conditions[i] = *cond
}
}
if !existed {
- conditions = append(conditions, cond)
+ conditions = append(conditions, *cond)
}
transCtx.Component.Status.Conditions = conditions
}
-func (t *componentAccountProvisionTransformer) isAccountProvisioned(cond metav1.Condition, account appsv1.SystemAccount) bool {
- if len(cond.Message) == 0 {
- return false
- }
- accounts := strings.Split(cond.Message, ",")
- return slices.Contains(accounts, account.Name)
-}
-
-func (t *componentAccountProvisionTransformer) markAccountProvisioned(cond *metav1.Condition, account appsv1.SystemAccount) {
- if len(cond.Message) == 0 {
- cond.Message = account.Name
- return
- }
+func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) {
accounts := strings.Split(cond.Message, ",")
- if slices.Contains(accounts, account.Name) {
- return
+ idx := slices.Index(accounts, account)
+ if idx >= 0 {
+ accounts[idx] = fmt.Sprintf("%s:%s", account, hashedPassword)
+ } else {
+ accounts = append(accounts, fmt.Sprintf("%s:%s", account, hashedPassword))
}
- accounts = append(accounts, account.Name)
cond.Message = strings.Join(accounts, ",")
}
-func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *componentTransformContext) (lifecycle.Lifecycle, error) {
- synthesizedComp := transCtx.SynthesizeComponent
- pods, err := component.ListOwnedPods(transCtx.Context, transCtx.Client,
- synthesizedComp.Namespace, synthesizedComp.ClusterName, synthesizedComp.Name)
- if err != nil {
- return nil, err
- }
- lfa, err := lifecycle.New(transCtx.SynthesizeComponent, nil, pods...)
- if err != nil {
- return nil, err
- }
- return lfa, nil
-}
-
-func (t *componentAccountProvisionTransformer) provisionAccount(transCtx *componentTransformContext,
- _ metav1.Condition, lfa lifecycle.Lifecycle, account appsv1.SystemAccount) error {
-
- synthesizedComp := transCtx.SynthesizeComponent
- secret, err := t.getAccountSecret(transCtx, synthesizedComp, account)
- if err != nil {
- return err
- }
-
- username, password := secret.Data[constant.AccountNameForSecret], secret.Data[constant.AccountPasswdForSecret]
- if len(username) == 0 || len(password) == 0 {
- return nil
- }
-
- err = lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement, string(username), string(password))
- return lifecycle.IgnoreNotDefined(err)
+func (t *componentAccountProvisionTransformer) removeProvisionedAccount(cond *metav1.Condition, account string) {
+ accounts := strings.Split(cond.Message, ",")
+ accounts = slices.DeleteFunc(accounts, func(s string) bool {
+ return s == account
+ })
+ cond.Message = strings.Join(accounts, ",")
}
-func (t *componentAccountProvisionTransformer) getAccountSecret(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),
- }
- secret := &corev1.Secret{}
- if err := ctx.GetClient().Get(ctx.GetContext(), secretKey, secret); err != nil {
- return nil, err
+func (t *componentAccountProvisionTransformer) getHashedPasswordFromCond(cond *metav1.Condition, account string) string {
+ accounts := strings.Split(cond.Message, ",")
+ idx := slices.Index(accounts, account)
+ if idx >= 0 {
+ val := strings.Split(accounts[idx], ":")
+ if len(val) == 2 {
+ return val[1]
+ }
}
- return secret, nil
+ return ""
}
diff --git a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml
index 2d4cc34c940..97c5f6892a8 100644
--- a/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml
+++ b/deploy/helm/crds/apps.kubeblocks.io_clusters.yaml
@@ -5276,6 +5276,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
@@ -5334,6 +5338,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
@@ -13977,6 +13986,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
@@ -14036,6 +14050,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/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
index fe6bec4e932..ec919134eab 100644
--- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
+++ b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
@@ -4502,7 +4502,7 @@ spec:
- 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_PASSWORD: The password for the system account.
- KB_ACCOUNT_STATEMENT: The statement used to create the system account.
@@ -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/deploy/helm/crds/apps.kubeblocks.io_components.yaml b/deploy/helm/crds/apps.kubeblocks.io_components.yaml
index 9da4fd1e563..6629aa574ab 100644
--- a/deploy/helm/crds/apps.kubeblocks.io_components.yaml
+++ b/deploy/helm/crds/apps.kubeblocks.io_components.yaml
@@ -5471,6 +5471,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
@@ -5529,6 +5533,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/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md
index eebf2a02817..744a765a7a9 100644
--- a/docs/developer_docs/api-reference/cluster.md
+++ b/docs/developer_docs/api-reference/cluster.md
@@ -5771,6 +5771,7 @@ and other administrative tasks.
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.
- KB_ACCOUNT_STATEMENT: The statement used to create the system account.
Note: This field is immutable once it has been set.
@@ -6435,6 +6436,18 @@ string
+disabled
+
+bool
+
+ |
+
+(Optional)
+ Specifies whether the system account is disabled.
+ |
+
+
+
passwordConfig
@@ -8661,7 +8674,7 @@ Defaults to 3. Minimum value is 1.
ProvisionSecretRef
-(Appears on:ComponentSystemAccount, SystemAccount)
+(Appears on:ComponentSystemAccount)
ProvisionSecretRef represents the reference to a secret.
@@ -8696,6 +8709,18 @@ string
The namespace where the secret is located.
|
+
+
+password
+
+string
+
+ |
+
+(Optional)
+ The key in the secret data that contains the password.
+ |
+
ReplicaRole
@@ -11086,12 +11111,14 @@ bool
statement
-string
+
+SystemAccountStatement
+
|
(Optional)
- 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.
|
@@ -11110,18 +11137,59 @@ PasswordConfig
This field is immutable once set.
+
+
+SystemAccountStatement
+
+
+(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.
|
diff --git a/pkg/constant/labels.go b/pkg/constant/labels.go
index 05a867e870c..12163c1ca2e 100644
--- a/pkg/constant/labels.go
+++ b/pkg/constant/labels.go
@@ -82,15 +82,6 @@ func GetCompLabelsWithDef(clusterName, compName, compDef string, labels ...map[s
return withShardingNameLabel(m, labels...)
}
-func GetConfigurationLabels(clusterName, compName, cmTplName string) map[string]string {
- return map[string]string{
- AppManagedByLabelKey: AppName,
- AppInstanceLabelKey: clusterName,
- KBAppComponentLabelKey: compName,
- CMTemplateNameLabelKey: cmTplName,
- }
-}
-
func withShardingNameLabel(labels map[string]string, extraLabels ...map[string]string) map[string]string {
for _, m := range extraLabels {
if m != nil {
@@ -102,3 +93,12 @@ func withShardingNameLabel(labels map[string]string, extraLabels ...map[string]s
}
return labels
}
+
+func GetConfigurationLabels(clusterName, compName, cmTplName string) map[string]string {
+ return map[string]string{
+ AppManagedByLabelKey: AppName,
+ AppInstanceLabelKey: clusterName,
+ KBAppComponentLabelKey: compName,
+ CMTemplateNameLabelKey: cmTplName,
+ }
+}
diff --git a/pkg/controller/builder/builder_component_definition.go b/pkg/controller/builder/builder_component_definition.go
index 92497251145..bd439f84dfe 100644
--- a/pkg/controller/builder/builder_component_definition.go
+++ b/pkg/controller/builder/builder_component_definition.go
@@ -192,19 +192,6 @@ func (builder *ComponentDefinitionBuilder) SetReplicasLimit(minReplicas, maxRepl
return builder
}
-func (builder *ComponentDefinitionBuilder) AddSystemAccount(accountName string, initAccount bool, statement string) *ComponentDefinitionBuilder {
- account := appsv1.SystemAccount{
- Name: accountName,
- InitAccount: initAccount,
- Statement: statement,
- }
- if builder.get().Spec.SystemAccounts == nil {
- builder.get().Spec.SystemAccounts = make([]appsv1.SystemAccount, 0)
- }
- builder.get().Spec.SystemAccounts = append(builder.get().Spec.SystemAccounts, account)
- return builder
-}
-
func (builder *ComponentDefinitionBuilder) SetUpdateStrategy(strategy *appsv1.UpdateStrategy) *ComponentDefinitionBuilder {
builder.get().Spec.UpdateStrategy = strategy
return builder
diff --git a/pkg/controller/component/component_version.go b/pkg/controller/component/component_version.go
index d34b6abd16b..16d642d23df 100644
--- a/pkg/controller/component/component_version.go
+++ b/pkg/controller/component/component_version.go
@@ -216,6 +216,9 @@ func actionsToResolveImage(compDef *appsv1.ComponentDefinition) map[string]*apps
if compDef.Spec.LifecycleActions.RoleProbe != nil {
actions[normalize("roleProbe")] = &compDef.Spec.LifecycleActions.RoleProbe.Action
}
+ if compDef.Spec.LifecycleActions.AvailableProbe != nil {
+ actions[normalize("availableProbe")] = &compDef.Spec.LifecycleActions.AvailableProbe.Action
+ }
return actions
}
diff --git a/pkg/controller/component/its_convertor.go b/pkg/controller/component/its_convertor.go
index af8af9b5d16..90ddf863833 100644
--- a/pkg/controller/component/its_convertor.go
+++ b/pkg/controller/component/its_convertor.go
@@ -302,8 +302,8 @@ func (c *itsCredentialConvertor) convert(args ...any) (any, error) {
return nil, err
}
- credential := func(sysAccount kbappsv1.SystemAccount) *workloads.Credential {
- secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, sysAccount.Name)
+ credential := func(sysAccount string) *workloads.Credential {
+ secretName := constant.GenerateAccountSecretName(synthesizeComp.ClusterName, synthesizeComp.Name, sysAccount)
return &workloads.Credential{
Username: workloads.CredentialVar{
ValueFrom: &corev1.EnvVarSource{
@@ -329,9 +329,9 @@ func (c *itsCredentialConvertor) convert(args ...any) (any, error) {
}
// use first init account as the default credential
- for index, sysAccount := range synthesizeComp.SystemAccounts {
+ for _, sysAccount := range synthesizeComp.SystemAccounts {
if sysAccount.InitAccount {
- return credential(synthesizeComp.SystemAccounts[index]), nil
+ return credential(sysAccount.Name), nil
}
}
return nil, nil
diff --git a/pkg/controller/component/synthesize_component.go b/pkg/controller/component/synthesize_component.go
index f3f102ccd8f..91bd46d386d 100644
--- a/pkg/controller/component/synthesize_component.go
+++ b/pkg/controller/component/synthesize_component.go
@@ -95,7 +95,7 @@ func BuildSynthesizedComponent(ctx context.Context, cli client.Reader,
MinReadySeconds: compDefObj.Spec.MinReadySeconds,
PolicyRules: compDefObj.Spec.PolicyRules,
LifecycleActions: compDefObj.Spec.LifecycleActions,
- SystemAccounts: mergeSystemAccounts(compDefObj.Spec.SystemAccounts, comp.Spec.SystemAccounts),
+ SystemAccounts: compDefObj.Spec.SystemAccounts,
Replicas: comp.Spec.Replicas,
Resources: comp.Spec.Resources,
TLSConfig: comp.Spec.TLSConfig,
@@ -210,35 +210,6 @@ func mergeUserDefinedEnv(synthesizedComp *SynthesizedComponent, comp *appsv1.Com
return nil
}
-func mergeSystemAccounts(compDefAccounts []appsv1.SystemAccount,
- compAccounts []appsv1.ComponentSystemAccount) []appsv1.SystemAccount {
- if len(compAccounts) == 0 {
- return compDefAccounts
- }
-
- override := func(compAccount appsv1.ComponentSystemAccount, idx int) {
- if compAccount.PasswordConfig != nil {
- compDefAccounts[idx].PasswordGenerationPolicy = *compAccount.PasswordConfig
- }
- compDefAccounts[idx].SecretRef = compAccount.SecretRef
- }
-
- tbl := make(map[string]int)
- for i, account := range compDefAccounts {
- tbl[account.Name] = i
- }
-
- for _, account := range compAccounts {
- idx, ok := tbl[account.Name]
- if !ok {
- continue // ignore it silently
- }
- override(account, idx)
- }
-
- return compDefAccounts
-}
-
func buildSchedulingPolicy(synthesizedComp *SynthesizedComponent, comp *appsv1.Component) {
if comp.Spec.SchedulingPolicy != nil {
schedulingPolicy := comp.Spec.SchedulingPolicy
diff --git a/pkg/testutil/apps/componentdefinition_factory.go b/pkg/testutil/apps/componentdefinition_factory.go
index 8871c693202..72d57b55472 100644
--- a/pkg/testutil/apps/componentdefinition_factory.go
+++ b/pkg/testutil/apps/componentdefinition_factory.go
@@ -267,7 +267,9 @@ func (f *MockComponentDefinitionFactory) AddSystemAccount(accountName string, in
account := kbappsv1.SystemAccount{
Name: accountName,
InitAccount: initAccount,
- Statement: statement,
+ Statement: &kbappsv1.SystemAccountStatement{
+ Create: statement,
+ },
}
if f.Get().Spec.SystemAccounts == nil {
f.Get().Spec.SystemAccounts = make([]kbappsv1.SystemAccount, 0)
diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go
index c18fa35603d..46ee1fd9e39 100644
--- a/pkg/testutil/apps/constant.go
+++ b/pkg/testutil/apps/constant.go
@@ -196,8 +196,11 @@ var (
},
},
{
- Name: "admin",
- Statement: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);",
+ Name: "admin",
+ Statement: &appsv1.SystemAccountStatement{
+ Create: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);",
+ Delete: "DROP USER '$(USERNAME)'@'%';",
+ },
PasswordGenerationPolicy: appsv1.PasswordConfig{
Length: 10,
NumDigits: 5,
From 791e7a4278c39000116ed6f42a7b12a7c3972b78 Mon Sep 17 00:00:00 2001
From: Leon
Date: Tue, 7 Jan 2025 14:01:05 +0800
Subject: [PATCH 2/6] update
---
apis/apps/v1/componentdefinition_types.go | 4 +-
...ps.kubeblocks.io_componentdefinitions.yaml | 4 +-
.../apps/componentdefinition_controller.go | 16 +++--
...transformer_component_account_provision.go | 62 +++++++++++++++----
...ps.kubeblocks.io_componentdefinitions.yaml | 4 +-
docs/developer_docs/api-reference/cluster.md | 4 +-
.../component/lifecycle/lfa_account.go | 4 +-
7 files changed, 69 insertions(+), 29 deletions(-)
diff --git a/apis/apps/v1/componentdefinition_types.go b/apis/apps/v1/componentdefinition_types.go
index d13980a9e79..b7015ef4771 100644
--- a/apis/apps/v1/componentdefinition_types.go
+++ b/apis/apps/v1/componentdefinition_types.go
@@ -1751,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_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 create 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/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml b/config/crd/bases/apps.kubeblocks.io_componentdefinitions.yaml
index ec919134eab..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_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 create 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/controllers/apps/componentdefinition_controller.go b/controllers/apps/componentdefinition_controller.go
index 25040a3a4cd..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.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 && (account.Statement == nil || len(account.Statement.Create) == 0) {
+ 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_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go
index b3d5762f5b5..72974187426 100644
--- a/controllers/apps/transformer_component_account_provision.go
+++ b/controllers/apps/transformer_component_account_provision.go
@@ -85,7 +85,7 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont
protoNameSet := sets.New(maps.Keys(secrets)...)
cond := t.provisionCond(transCtx)
- provisionedNameSet := sets.New(strings.Split(cond.Message, ",")...)
+ provisionedNameSet := t.getProvisionedAccounts(cond)
createSet, deleteSet, updateSet := setDiff(provisionedNameSet, protoNameSet)
if len(createSet) == 0 && len(deleteSet) == 0 && len(updateSet) == 0 {
@@ -160,13 +160,17 @@ func (t *componentAccountProvisionTransformer) createAccount(transCtx *component
if err == nil {
// TODO: how about the password restored from backup?
- t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
+ t.updateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
}
return err
}
func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *componentTransformContext,
lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount) error {
+ if account.Statement == nil || len(account.Statement.Delete) == 0 {
+ return fmt.Errorf("has no delete statement defined for system account: %s", account.Name)
+ }
+
err := lfa.AccountProvision(transCtx.Context, transCtx.Client, nil, account.Statement.Delete, account.Name, "")
if lifecycle.IgnoreNotDefined(err) == nil {
t.removeProvisionedAccount(cond, account.Name)
@@ -176,16 +180,23 @@ func (t *componentAccountProvisionTransformer) deleteAccount(transCtx *component
func (t *componentAccountProvisionTransformer) updateAccount(transCtx *componentTransformContext,
lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error {
- hashedPassword := t.getHashedPasswordFromCond(cond, account.Name)
+ hashedPassword := t.hashedPasswordFromCond(cond, account.Name)
+ if hashedPassword == "" {
+ return nil // does not support password update?
+ }
if verifySystemAccountPassword(secret, []byte(hashedPassword)) {
return nil // the password is not changed
}
+ if account.Statement == nil || len(account.Statement.Update) == 0 {
+ return fmt.Errorf("has no update statement defined for system account: %s", account.Name)
+ }
+
// TODO: how to notify other apps to update the new password?
err := t.provision(transCtx, lfa, account.Statement.Update, secret)
if err == nil {
- t.addOrUpdateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
+ t.updateProvisionedAccount(cond, account.Name, secret.Annotations[systemAccountHashAnnotation])
}
return err
}
@@ -225,11 +236,11 @@ func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *compo
cond.Status = metav1.ConditionFalse
// cond.Reason = err.Error() // TODO: error
}
- cond.ObservedGeneration = transCtx.Component.Generation
if !reflect.DeepEqual(cond, condCopy) {
cond.LastTransitionTime = metav1.Now()
}
+ cond.ObservedGeneration = transCtx.Component.Generation
conditions := transCtx.Component.Status.Conditions
if conditions == nil {
@@ -248,9 +259,26 @@ func (t *componentAccountProvisionTransformer) provisionCondDone(transCtx *compo
transCtx.Component.Status.Conditions = conditions
}
-func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) {
- accounts := strings.Split(cond.Message, ",")
- idx := slices.Index(accounts, account)
+func (t *componentAccountProvisionTransformer) getProvisionedAccounts(cond metav1.Condition) sets.Set[string] {
+ accounts := sets.New[string]()
+ if len(cond.Message) > 0 {
+ for _, e := range strings.Split(cond.Message, ",") {
+ if len(e) > 0 {
+ accounts.Insert(strings.Split(e, ":")[0])
+ }
+ }
+ }
+ return accounts
+}
+
+func (t *componentAccountProvisionTransformer) updateProvisionedAccount(cond *metav1.Condition, account, hashedPassword string) {
+ accounts := make([]string, 0)
+ if len(cond.Message) > 0 {
+ accounts = strings.Split(cond.Message, ",")
+ }
+ idx := slices.IndexFunc(accounts, func(s string) bool {
+ return strings.HasPrefix(s, fmt.Sprintf("%s:", account))
+ })
if idx >= 0 {
accounts[idx] = fmt.Sprintf("%s:%s", account, hashedPassword)
} else {
@@ -260,16 +288,24 @@ func (t *componentAccountProvisionTransformer) addOrUpdateProvisionedAccount(con
}
func (t *componentAccountProvisionTransformer) removeProvisionedAccount(cond *metav1.Condition, account string) {
- accounts := strings.Split(cond.Message, ",")
+ accounts := make([]string, 0)
+ if len(cond.Message) > 0 {
+ accounts = strings.Split(cond.Message, ",")
+ }
accounts = slices.DeleteFunc(accounts, func(s string) bool {
- return s == account
+ return strings.HasPrefix(s, fmt.Sprintf("%s:", account))
})
cond.Message = strings.Join(accounts, ",")
}
-func (t *componentAccountProvisionTransformer) getHashedPasswordFromCond(cond *metav1.Condition, account string) string {
- accounts := strings.Split(cond.Message, ",")
- idx := slices.Index(accounts, account)
+func (t *componentAccountProvisionTransformer) hashedPasswordFromCond(cond *metav1.Condition, account string) string {
+ accounts := make([]string, 0)
+ if len(cond.Message) > 0 {
+ accounts = strings.Split(cond.Message, ",")
+ }
+ idx := slices.IndexFunc(accounts, func(s string) bool {
+ return strings.HasPrefix(s, fmt.Sprintf("%s:", account))
+ })
if idx >= 0 {
val := strings.Split(accounts[idx], ":")
if len(val) == 2 {
diff --git a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml b/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
index ec919134eab..a3568885f25 100644
--- a/deploy/helm/crds/apps.kubeblocks.io_componentdefinitions.yaml
+++ b/deploy/helm/crds/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_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 create 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/docs/developer_docs/api-reference/cluster.md b/docs/developer_docs/api-reference/cluster.md
index 744a765a7a9..11b4d786f1e 100644
--- a/docs/developer_docs/api-reference/cluster.md
+++ b/docs/developer_docs/api-reference/cluster.md
@@ -5770,9 +5770,9 @@ This action is designed to create system accounts that are utilized for replicat
and other administrative tasks.
The container executing this action has access to following variables:
-- KB_ACCOUNT_NAME: The name of the system account to be created.
+- 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 create 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/pkg/controller/component/lifecycle/lfa_account.go b/pkg/controller/component/lifecycle/lfa_account.go
index bdf438c1eb8..a70b9893ca1 100644
--- a/pkg/controller/component/lifecycle/lfa_account.go
+++ b/pkg/controller/component/lifecycle/lfa_account.go
@@ -46,9 +46,9 @@ func (a *accountProvision) name() string {
func (a *accountProvision) parameters(ctx context.Context, cli client.Reader) (map[string]string, error) {
// The container executing this action has access to following variables:
//
- // - KB_ACCOUNT_NAME: The name of the system account to be created.
+ // - 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 create the system account.
+ // - KB_ACCOUNT_STATEMENT: The statement used to manipulate the system account.
return map[string]string{
accountName: a.user,
accountPassword: a.password,
From f1d249456fbdee05f86257ef84c683d41b0cae0d Mon Sep 17 00:00:00 2001
From: Leon
Date: Tue, 7 Jan 2025 17:42:18 +0800
Subject: [PATCH 3/6] cannot disable init system accounts
---
.../apps/transformer_component_account.go | 20 +++++++++++++------
...transformer_component_account_provision.go | 2 +-
2 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/controllers/apps/transformer_component_account.go b/controllers/apps/transformer_component_account.go
index fff21d859c4..51b6ff794d3 100644
--- a/controllers/apps/transformer_component_account.go
+++ b/controllers/apps/transformer_component_account.go
@@ -73,7 +73,11 @@ func (t *componentAccountTransformer) Transform(ctx graph.TransformContext, dag
runningNameSet := sets.New(maps.Keys(secrets)...)
// proto accounts
- accounts := synthesizeSystemAccounts(transCtx.CompDef.Spec.SystemAccounts, transCtx.Component.Spec.SystemAccounts, false)
+ 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)
@@ -265,7 +269,7 @@ type synthesizedSystemAccount struct {
}
func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount,
- compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) map[string]synthesizedSystemAccount {
+ compAccounts []appsv1.ComponentSystemAccount, keepDisabled bool) (map[string]synthesizedSystemAccount, error) {
accounts := make(map[string]synthesizedSystemAccount)
for _, account := range compDefAccounts {
accounts[account.Name] = synthesizedSystemAccount{
@@ -285,17 +289,21 @@ func synthesizeSystemAccounts(compDefAccounts []appsv1.SystemAccount,
for i := range compAccounts {
account, ok := accounts[compAccounts[i].Name]
if !ok {
- continue // ignore it silently
+ return nil, fmt.Errorf("system account %s not defined in component definition", compAccounts[i].Name)
}
- accounts[compAccounts[i].Name] = merge(account, compAccounts[i])
+ accounts[account.Name] = merge(account, compAccounts[i])
}
if !keepDisabled {
for _, name := range maps.Keys(accounts) {
- if accounts[name].Disabled != nil && *accounts[name].Disabled {
+ 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
+ return accounts, nil
}
diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go
index 72974187426..8c7aef35459 100644
--- a/controllers/apps/transformer_component_account_provision.go
+++ b/controllers/apps/transformer_component_account_provision.go
@@ -76,7 +76,7 @@ func (t *componentAccountProvisionTransformer) Transform(ctx graph.TransformCont
return nil
}
- accounts := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true)
+ accounts, _ := synthesizeSystemAccounts(compDef.Spec.SystemAccounts, comp.Spec.SystemAccounts, true)
secrets, err1 := listSystemAccountObjects(ctx, transCtx.SynthesizeComponent)
if err1 != nil {
From c28695cb37582dd1b6c2787c23692202a3a4c06e Mon Sep 17 00:00:00 2001
From: Leon
Date: Tue, 7 Jan 2025 17:48:48 +0800
Subject: [PATCH 4/6] update init account's password
---
...transformer_component_account_provision.go | 23 ++++++++++---------
1 file changed, 12 insertions(+), 11 deletions(-)
diff --git a/controllers/apps/transformer_component_account_provision.go b/controllers/apps/transformer_component_account_provision.go
index 8c7aef35459..51fc68838ea 100644
--- a/controllers/apps/transformer_component_account_provision.go
+++ b/controllers/apps/transformer_component_account_provision.go
@@ -144,18 +144,19 @@ func (t *componentAccountProvisionTransformer) lifecycleAction(transCtx *compone
func (t *componentAccountProvisionTransformer) createAccount(transCtx *componentTransformContext,
lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error {
- // The secret of initAccount should be rendered into the config file,
- // or injected into the container through specific account&password environment variables name supported by the engine.
- // When the engine starts up, it will automatically load and create this account.
- if account.InitAccount {
- return nil
- }
+ var (
+ err error
+ )
- var err error
- if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" {
+ // The secret of an initial account should be injected into the container through
+ // specific account&password environment variables name supported by the engine.
+ // When the engine starts up, it will automatically load and create this account.
+ if !account.InitAccount {
// TODO: restore account secret from backup.
- // provision account when the component is not recovered from backup
- err = t.provision(transCtx, lfa, account.Statement.Create, secret)
+ if transCtx.SynthesizeComponent.Annotations[constant.RestoreFromBackupAnnotationKey] == "" {
+ // provision account when the component is not recovered from backup
+ err = t.provision(transCtx, lfa, account.Statement.Create, secret)
+ }
}
if err == nil {
@@ -182,7 +183,7 @@ func (t *componentAccountProvisionTransformer) updateAccount(transCtx *component
lfa lifecycle.Lifecycle, cond *metav1.Condition, account synthesizedSystemAccount, secret *corev1.Secret) error {
hashedPassword := t.hashedPasswordFromCond(cond, account.Name)
if hashedPassword == "" {
- return nil // does not support password update?
+ return nil // passwords that generated by KB or restored from backup, do not support updating?
}
if verifySystemAccountPassword(secret, []byte(hashedPassword)) {
return nil // the password is not changed
From 675f06f24badee7f89e4655ad523a4ac9f32e9b2 Mon Sep 17 00:00:00 2001
From: Leon
Date: Wed, 8 Jan 2025 13:42:27 +0800
Subject: [PATCH 5/6] test cases
---
controllers/apps/component_controller_test.go | 417 +++++++++++++-----
.../apps/transformer_cluster_component.go | 1 +
.../apps/transformer_component_account.go | 34 +-
pkg/testutil/apps/cluster_factory.go | 4 +-
pkg/testutil/apps/kb_agent_util.go | 51 ---
pkg/testutil/dataprotection/backup_utils.go | 2 +-
6 files changed, 348 insertions(+), 161 deletions(-)
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/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_component_account.go b/controllers/apps/transformer_component_account.go
index 51b6ff794d3..dcb8616f0b7 100644
--- a/controllers/apps/transformer_component_account.go
+++ b/controllers/apps/transformer_component_account.go
@@ -107,6 +107,9 @@ func (t *componentAccountTransformer) createAccount(transCtx *componentTransform
if err != nil {
return err
}
+ if err = t.buildAccountHash(account, nil, secret); err != nil {
+ return err
+ }
graphCli.Create(dag, secret, inUniversalContext4G())
return nil
}
@@ -122,6 +125,9 @@ func (t *componentAccountTransformer) updateAccount(transCtx *componentTransform
if err != nil {
return err
}
+ if err = t.buildAccountHash(account, running, secret); err != nil {
+ return err
+ }
runningCopy := running.DeepCopy()
if account.SecretRef != nil {
@@ -136,6 +142,23 @@ func (t *componentAccountTransformer) updateAccount(transCtx *componentTransform
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 synthesizedSystemAccount) (*corev1.Secret, error) {
var password []byte
@@ -148,7 +171,7 @@ func (t *componentAccountTransformer) buildAccountSecret(ctx *componentTransform
default:
password = t.buildPassword(ctx, account)
}
- return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password, account.SecretRef != nil)
+ return t.buildAccountSecretWithPassword(ctx, synthesizeComp, account, password)
}
func (t *componentAccountTransformer) getPasswordFromSecret(ctx graph.TransformContext, account synthesizedSystemAccount) ([]byte, error) {
@@ -198,7 +221,7 @@ func (t *componentAccountTransformer) generatePassword(account synthesizedSystem
}
func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *componentTransformContext,
- synthesizeComp *component.SynthesizedComponent, account synthesizedSystemAccount, password []byte, signature bool) (*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
@@ -210,13 +233,8 @@ func (t *componentAccountTransformer) buildAccountSecretWithPassword(ctx *compon
AddAnnotationsInMap(synthesizeComp.DynamicAnnotations).
PutData(constant.AccountNameForSecret, []byte(account.Name)).
PutData(constant.AccountPasswdForSecret, password).
- SetImmutable(true).
+ // SetImmutable(true).
GetObject()
- if signature {
- if err := signatureSystemAccountPassword(secret); err != nil {
- return nil, err
- }
- }
if err := setCompOwnershipNFinalizer(ctx.Component, secret); err != nil {
return nil, err
}
diff --git a/pkg/testutil/apps/cluster_factory.go b/pkg/testutil/apps/cluster_factory.go
index d9c31b53543..ef5961b25fa 100644
--- a/pkg/testutil/apps/cluster_factory.go
+++ b/pkg/testutil/apps/cluster_factory.go
@@ -21,6 +21,7 @@ package apps
import (
corev1 "k8s.io/api/core/v1"
+ "k8s.io/utils/ptr"
appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
)
@@ -206,11 +207,12 @@ func (factory *MockClusterFactory) AddComponentService(serviceName string, servi
})
}
-func (factory *MockClusterFactory) AddSystemAccount(name string, passwordConfig *appsv1.PasswordConfig, secretRef *appsv1.ProvisionSecretRef) *MockClusterFactory {
+func (factory *MockClusterFactory) AddSystemAccount(name string, disabled bool, passwordConfig *appsv1.PasswordConfig, secretRef *appsv1.ProvisionSecretRef) *MockClusterFactory {
return factory.lastComponentRef(func(comp *appsv1.ClusterComponentSpec) {
comp.SystemAccounts = append(comp.SystemAccounts,
appsv1.ComponentSystemAccount{
Name: name,
+ Disabled: ptr.To(disabled),
PasswordConfig: passwordConfig,
SecretRef: secretRef,
})
diff --git a/pkg/testutil/apps/kb_agent_util.go b/pkg/testutil/apps/kb_agent_util.go
index c869bc9cfdf..a813cd20bf6 100644
--- a/pkg/testutil/apps/kb_agent_util.go
+++ b/pkg/testutil/apps/kb_agent_util.go
@@ -85,57 +85,6 @@ func MockKBAgentClient4HScale(testCtx *testutil.TestContext, clusterKey types.Na
})
}
-func MockKBAgentClient4Workload(testCtx *testutil.TestContext, pods []*corev1.Pod) {
- const (
- memberJoinCompletedLabel = "test.kubeblock.io/memberjoin-completed"
- memberLeaveCompletedLabel = "test.kubeblock.io/memberleave-completed"
- )
-
- rsp := kbagentproto.ActionResponse{Message: "mock success"}
- handleMemberLeave := func(podName string) (kbagentproto.ActionResponse, error) {
- for _, pod := range pods {
- if pod.Name != podName {
- continue
- }
- pod.Labels[memberLeaveCompletedLabel] = "true"
- err := testCtx.Cli.Update(testCtx.Ctx, pod)
- if err != nil {
- return kbagentproto.ActionResponse{}, err
- }
- }
- return rsp, nil
- }
-
- handleMemberJoin := func(podName string) (kbagentproto.ActionResponse, error) {
- for _, pod := range pods {
- if pod.Name != podName {
- continue
- }
- pod.Labels[memberJoinCompletedLabel] = "true"
- err := testCtx.Cli.Update(testCtx.Ctx, pod)
- if err != nil {
- return kbagentproto.ActionResponse{}, err
- }
- }
- return rsp, nil
- }
-
- MockKBAgentClient(func(recorder *kbacli.MockClientMockRecorder) {
- recorder.Action(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, req kbagentproto.ActionRequest) (kbagentproto.ActionResponse, error) {
- switch req.Action {
- case "memberLeave":
- podName := req.Parameters["KB_LEAVE_MEMBER_POD_NAME"]
- return handleMemberLeave(podName)
- case "memberJoin":
- podName := req.Parameters["KB_JOIN_MEMBER_POD_NAME"]
- return handleMemberJoin(podName)
- default:
- return rsp, nil
- }
- }).AnyTimes()
- })
-}
-
func MockKBAgentContainer() corev1.Container {
return corev1.Container{
Name: kbagent.ContainerName,
diff --git a/pkg/testutil/dataprotection/backup_utils.go b/pkg/testutil/dataprotection/backup_utils.go
index 29f3f7e8ca6..aaecfeec62a 100644
--- a/pkg/testutil/dataprotection/backup_utils.go
+++ b/pkg/testutil/dataprotection/backup_utils.go
@@ -187,7 +187,7 @@ func NewFakeCluster(testCtx *testutil.TestContext) *BackupClusterInfo {
cluster := testapps.NewClusterFactory(testCtx.DefaultNamespace, ClusterName, "").
AddLabels(constant.AppInstanceLabelKey, ClusterName).
AddComponent("test-cmp", "test-cmpd").
- AddSystemAccount("test-account", nil, nil).
+ AddSystemAccount("test-account", false, nil, nil).
Create(testCtx).GetObject()
podName := ClusterName + "-" + ComponentName
From 62fd592b3e0609057bbe2b90bbf2260df57fa5af Mon Sep 17 00:00:00 2001
From: Leon
Date: Wed, 8 Jan 2025 15:42:56 +0800
Subject: [PATCH 6/6] add update statement
---
pkg/testutil/apps/constant.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/pkg/testutil/apps/constant.go b/pkg/testutil/apps/constant.go
index 46ee1fd9e39..825030be311 100644
--- a/pkg/testutil/apps/constant.go
+++ b/pkg/testutil/apps/constant.go
@@ -200,6 +200,7 @@ var (
Statement: &appsv1.SystemAccountStatement{
Create: "CREATE USER $(USERNAME) IDENTIFIED BY '$(PASSWORD)'; GRANT ALL PRIVILEGES ON *.* TO $(USERNAME);",
Delete: "DROP USER '$(USERNAME)'@'%';",
+ Update: "ALTER USER '$(USERNAME)'@'%' IDENTIFIED BY '$(PASSWORD)';",
},
PasswordGenerationPolicy: appsv1.PasswordConfig{
Length: 10,