feat: 接通 Database 凭据准备闭环
This commit is contained in:
@@ -6,7 +6,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
bao "github.com/openbao/openbao/api/v2"
|
||||
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/adapter/kubernetes"
|
||||
databasebao "git.ddupan.top/panxiao81/ayatori/internal/database/adapter/openbao"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/adapter/postgresql"
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/database/application"
|
||||
databasecontroller "git.ddupan.top/panxiao81/ayatori/internal/database/controller"
|
||||
@@ -14,14 +17,18 @@ import (
|
||||
)
|
||||
|
||||
type databaseOptions struct {
|
||||
secretNamespace string
|
||||
rootCert string
|
||||
secretNamespace string
|
||||
rootCert string
|
||||
credentialMount string
|
||||
credentialPrefix string
|
||||
}
|
||||
|
||||
func (o *databaseOptions) bindFlags(flags *flag.FlagSet) {
|
||||
flags.StringVar(&o.secretNamespace, "database-secret-namespace", os.Getenv("POD_NAMESPACE"),
|
||||
"固定管理 Secret namespace;为空时不启用 Instance 观测")
|
||||
flags.StringVar(&o.rootCert, "database-root-cert", "", "PostgreSQL 管理连接信任的公开 CA bundle 路径")
|
||||
flags.StringVar(&o.credentialMount, "database-credential-mount", "", "应用凭据 KV v2 mount;为空时不启用凭据准备")
|
||||
flags.StringVar(&o.credentialPrefix, "database-credential-prefix", "applications", "应用凭据路径前缀;已有固定位置不随配置变化迁移")
|
||||
}
|
||||
|
||||
func (o databaseOptions) configureManager(options *ctrl.Options) {
|
||||
@@ -31,7 +38,7 @@ func (o databaseOptions) configureManager(options *ctrl.Options) {
|
||||
}
|
||||
|
||||
// setupDatabase 封装 Database 的内部装配,并返回在 manager 停止后执行的清理。
|
||||
func setupDatabase(ctx context.Context, manager ctrl.Manager, options databaseOptions) (func(), error) {
|
||||
func setupDatabase(ctx context.Context, manager ctrl.Manager, options databaseOptions, baoClient *bao.Client) (func(), error) {
|
||||
cleanup := func() {}
|
||||
if options.secretNamespace != "" {
|
||||
service, err := setupInstanceObservation(manager, options.secretNamespace, options.rootCert)
|
||||
@@ -44,9 +51,27 @@ func setupDatabase(ctx context.Context, manager ctrl.Manager, options databaseOp
|
||||
cleanup()
|
||||
return nil, fmt.Errorf("set up Database binding controller: %w", err)
|
||||
}
|
||||
if err := setupCredentialPreparation(manager, options, baoClient); err != nil {
|
||||
cleanup()
|
||||
return nil, fmt.Errorf("set up Database credential preparation: %w", err)
|
||||
}
|
||||
return cleanup, nil
|
||||
}
|
||||
|
||||
func setupCredentialPreparation(manager ctrl.Manager, options databaseOptions, baoClient *bao.Client) error {
|
||||
if options.credentialMount == "" {
|
||||
return nil
|
||||
}
|
||||
if baoClient == nil {
|
||||
return fmt.Errorf("database credential preparation requires OpenBao authentication configuration")
|
||||
}
|
||||
store, err := databasebao.NewCredentials(baoClient, options.credentialMount, options.credentialPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return (&databasecontroller.CredentialReconciler{Store: store}).SetupWithManager(manager)
|
||||
}
|
||||
|
||||
func setupInstanceObservation(manager ctrl.Manager, namespace, rootCert string) (*application.InstanceService, error) {
|
||||
credentials, err := kubernetes.NewSecretCredentials(manager.GetAPIReader(), namespace)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
bao "github.com/openbao/openbao/api/v2"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
@@ -46,16 +48,28 @@ func TestBootstrapWithRealAPIServer(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := setupOpenBaoAuthentication(manager, options.openBao); err != nil {
|
||||
baoClient, err := setupOpenBaoAuthentication(manager, options.openBao)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
cleanup, err := setupDatabase(ctx, manager, options.database)
|
||||
cleanup, err := setupDatabase(ctx, manager, options.database, baoClient)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer cleanup()
|
||||
// 空 API 中没有供应目标;此处验证启用路径确实注册 controller,不访问外部 Bao。
|
||||
fixtureConfig := bao.NewConfig()
|
||||
fixtureConfig.Address = "http://127.0.0.1:1"
|
||||
fixtureClient, err := bao.NewClient(fixtureConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
options.database.credentialMount = "secret"
|
||||
if err := setupCredentialPreparation(manager, options.database, fixtureClient); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- manager.Start(ctx) }()
|
||||
defer func() {
|
||||
|
||||
@@ -21,6 +21,8 @@ import (
|
||||
"flag"
|
||||
"net/http"
|
||||
|
||||
bao "github.com/openbao/openbao/api/v2"
|
||||
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
|
||||
"git.ddupan.top/panxiao81/ayatori/internal/infra/openbao"
|
||||
@@ -45,28 +47,32 @@ func (o *openBaoOptions) bindFlags(flags *flag.FlagSet) {
|
||||
flags.StringVar(&o.identity.Audience, "openbao-token-audience", "openbao", "SA JWT audience,须匹配 OpenBao role")
|
||||
}
|
||||
|
||||
func setupOpenBaoAuthentication(manager ctrl.Manager, options openBaoOptions) error {
|
||||
func setupOpenBaoAuthentication(manager ctrl.Manager, options openBaoOptions) (*bao.Client, error) {
|
||||
if options.address == "" {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
client, err := openbao.NewClient(options.address, options.caCert)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
// 复用 manager 已装配的 Kubernetes client,不重复加载配置或创建客户端。
|
||||
session, err := openbao.NewKubernetesSession(
|
||||
client, manager.GetClient(), options.mount, options.role, options.identity,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if err := manager.Add(session); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return manager.AddReadyzCheck("openbao-auth", func(_ *http.Request) error {
|
||||
err = manager.AddReadyzCheck("openbao-auth", func(_ *http.Request) error {
|
||||
if !session.Ready() {
|
||||
return errors.New("OpenBao Kubernetes authentication unavailable")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestDefaultConfiguration(t *testing.T) {
|
||||
if options.manager.webhookOptions().Port != 9443 || !options.logging.Development {
|
||||
t.Fatal("webhook or logging defaults changed")
|
||||
}
|
||||
if options.database.secretNamespace != "" || options.openBao.address != "" {
|
||||
if options.database.secretNamespace != "" || options.database.credentialMount != "" || options.openBao.address != "" {
|
||||
t.Fatal("optional backends enabled by default")
|
||||
}
|
||||
if options.openBao.mount != "kubernetes" || options.openBao.identity.Audience != "openbao" {
|
||||
@@ -58,6 +58,19 @@ func TestDefaultConfiguration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCredentialPreparationOptions(t *testing.T) {
|
||||
options := parseTestOptions(t, "--database-credential-mount=applications-kv", "--database-credential-prefix=database")
|
||||
if options.database.credentialMount != "applications-kv" || options.database.credentialPrefix != "database" {
|
||||
t.Fatal("凭据准备参数未传入领域装配")
|
||||
}
|
||||
if err := setupCredentialPreparation(nil, options.database, nil); err == nil {
|
||||
t.Fatal("启用凭据准备必须有显式配置的认证 client")
|
||||
}
|
||||
if err := setupCredentialPreparation(nil, databaseOptions{}, nil); err != nil {
|
||||
t.Fatal("默认停用凭据准备不应要求后端")
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerFlagOverrides(t *testing.T) {
|
||||
options := parseTestOptions(t,
|
||||
"--metrics-bind-address=:9090", "--metrics-secure=false", "--health-probe-bind-address=:9091",
|
||||
|
||||
@@ -23,10 +23,11 @@ func Run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setupOpenBaoAuthentication(manager, options.openBao); err != nil {
|
||||
baoClient, err := setupOpenBaoAuthentication(manager, options.openBao)
|
||||
if err != nil {
|
||||
return fmt.Errorf("set up OpenBao authentication: %w", err)
|
||||
}
|
||||
cleanup, err := setupDatabase(context.Background(), manager, options.database)
|
||||
cleanup, err := setupDatabase(context.Background(), manager, options.database, baoClient)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user