Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

APPS-1383 Support Secret Agent keys for GCP and Azure credentials configuration #281

Merged
merged 11 commits into from
Dec 9, 2024
Prev Previous commit
Next Next commit
add sa to azure
korotkov-aerospike committed Dec 8, 2024
commit 2ee485169d559954be33d7fbeb699ded6b5c8923
17 changes: 16 additions & 1 deletion pkg/dto/convert_test.go
Original file line number Diff line number Diff line change
@@ -89,6 +89,21 @@ func TestConfigModelConversionIsLossless(t *testing.T) {
},
},
},
"azure": {
AzureStorage: &AzureStorage{
SecretAgentConfig: SecretAgentConfig{
SecretAgentName: util.Ptr("agent1"),
},
Endpoint: "http://localhost",
ContainerName: "container",
Path: "backup",
AccountName: "",
AccountKey: "",
TenantID: "",
ClientID: "",
ClientSecret: "",
},
},
},
BackupPolicies: map[string]*BackupPolicy{"policy1": {}},
BackupRoutines: map[string]*BackupRoutine{"routine1": {
@@ -109,7 +124,7 @@ func TestConfigModelConversionIsLossless(t *testing.T) {
// Step 2: Convert the Config to a model.Config
nsValidator := &aerospike.NoopNamespaceValidator{}
modelConfig, err := originalConfig.ToModel(nsValidator)
require.NoError(t, err, "ToModel should not return an error")
require.NoError(t, err, "toModel should not return an error")

// Step 3: Convert the model.Config back to a Config
newConfig := NewConfigFromModel(modelConfig)
110 changes: 5 additions & 105 deletions pkg/dto/storage.go
Original file line number Diff line number Diff line change
@@ -56,114 +56,30 @@ func (s *Storage) Validate() error {
return validStorage.Validate()
}

// LocalStorage represents the configuration for local storage.
type LocalStorage struct {
// The root path for the backup repository.
Path string `yaml:"path" json:"path" example:"backups" validate:"required"`
}

// Validate checks if the LocalStorage is valid.
func (l *LocalStorage) Validate() error {
if l.Path == "" {
return errors.New("local storage path is not specified")
}
return nil
}

// AzureStorage represents the configuration for Azure Blob storage.
type AzureStorage struct {
// Endpoint is the Azure Blob service endpoint URL.
Endpoint string `yaml:"endpoint" json:"endpoint" validate:"required"`
// ContainerName is the name of the Azure Blob container.
ContainerName string `yaml:"container-name" json:"container-name" validate:"required"`
// Path is the root path for the backup repository within the container.
// If not specified, backups will be saved in the container's root.
Path string `yaml:"path,omitempty" json:"path,omitempty" example:"backups"`
// AccountName is the Azure storage account name for Shared Key authentication.
AccountName string `yaml:"account-name,omitempty" json:"account-name,omitempty"`
// AccountKey is the Azure storage account key for Shared Key authentication.
AccountKey string `yaml:"account-key,omitempty" json:"account-key,omitempty"`
// TenantID is the Azure Active Directory tenant ID for AAD authentication.
TenantID string `yaml:"tenant-id,omitempty" json:"tenant-id,omitempty"`
// ClientID is the Azure Active Directory client ID for AAD authentication.
ClientID string `yaml:"client-id,omitempty" json:"client-id,omitempty"`
// ClientSecret is the Azure Active Directory client secret for AAD authentication.
ClientSecret string `yaml:"client-secret,omitempty" json:"client-secret,omitempty"`
}

// Validate checks if the AzureStorage is valid.
func (a *AzureStorage) Validate() error {
if a.Endpoint == "" {
return errors.New("azure storage endpoint is not specified")
}
if a.ContainerName == "" {
return errors.New("azure storage container name is not specified")
}

// Check for valid authentication method.
hasSharedKey := a.AccountName != "" && a.AccountKey != ""
hasAAD := a.TenantID != "" && a.ClientID != "" && a.ClientSecret != ""

if hasSharedKey && hasAAD {
return errors.New(`azure storage authentication method is ambiguous:
use either AccountName/AccountKey or TenantID/ClientID/ClientSecret, not both`)
}

return nil
}

// ToModel converts the Storage DTO to its corresponding model.
func (s *Storage) ToModel(c *model.Config) (model.Storage, error) {
if s.LocalStorage != nil {
return &model.LocalStorage{
Path: s.LocalStorage.Path,
}, nil
return s.LocalStorage.toModel()
}
if s.S3Storage != nil {
return s.S3Storage.ToModel(c)
return s.S3Storage.toModel(c)
}
if s.GcpStorage != nil {
return s.GcpStorage.toModel(c)
}
if s.AzureStorage != nil {
return &model.AzureStorage{
Endpoint: s.AzureStorage.Endpoint,
ContainerName: s.AzureStorage.ContainerName,
Path: s.AzureStorage.Path,
Auth: getAzureAuth(s),
}, nil
return s.AzureStorage.toModel(c)
}

return nil, errors.New("error converting storage dto to model: no storage configuration provided")
}

func getAzureAuth(s *Storage) model.AzureAuth {
if s.AzureStorage.AccountName != "" && s.AzureStorage.AccountKey != "" {
return model.AzureSharedKeyAuth{
AccountName: s.AzureStorage.AccountName,
AccountKey: s.AzureStorage.AccountKey,
}
}

if s.AzureStorage.TenantID != "" && s.AzureStorage.ClientID != "" && s.AzureStorage.ClientSecret != "" {
return model.AzureADAuth{
TenantID: s.AzureStorage.TenantID,
ClientID: s.AzureStorage.ClientID,
ClientSecret: s.AzureStorage.ClientSecret,
}
}

return nil
}

// NewStorageFromModel creates a new Storage DTO from the model.
func NewStorageFromModel(m model.Storage, config *model.Config) *Storage {
switch s := m.(type) {
case *model.LocalStorage:
return &Storage{
LocalStorage: &LocalStorage{
Path: s.Path,
},
LocalStorage: newLocalStorageFromModel(s),
}
case *model.S3Storage:
return &Storage{
@@ -174,24 +90,8 @@ func NewStorageFromModel(m model.Storage, config *model.Config) *Storage {
GcpStorage: newGcpStorageFromModel(s, config),
}
case *model.AzureStorage:
azureStorage := &AzureStorage{
Endpoint: s.Endpoint,
ContainerName: s.ContainerName,
Path: s.Path,
}

switch auth := s.Auth.(type) {
case model.AzureSharedKeyAuth:
azureStorage.AccountName = auth.AccountName
azureStorage.AccountKey = auth.AccountKey
case model.AzureADAuth:
azureStorage.TenantID = auth.TenantID
azureStorage.ClientID = auth.ClientID
azureStorage.ClientSecret = auth.ClientSecret
}

return &Storage{
AzureStorage: azureStorage,
AzureStorage: newAzureStorageFromModel(s, config),
}
default:
return nil
104 changes: 104 additions & 0 deletions pkg/dto/storage_azure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package dto

import (
"errors"

"github.com/aerospike/aerospike-backup-service/v2/pkg/model"
)

// AzureStorage represents the configuration for Azure Blob storage.
type AzureStorage struct {
SecretAgentConfig
// Endpoint is the Azure Blob service endpoint URL.
Endpoint string `yaml:"endpoint" json:"endpoint" validate:"required"`
// ContainerName is the name of the Azure Blob container.
ContainerName string `yaml:"container-name" json:"container-name" validate:"required"`
// Path is the root path for the backup repository within the container.
// If not specified, backups will be saved in the container's root.
Path string `yaml:"path,omitempty" json:"path,omitempty" example:"backups"`
// AccountName is the Azure storage account name for Shared Key authentication.
AccountName string `yaml:"account-name,omitempty" json:"account-name,omitempty"`
// AccountKey is the Azure storage account key for Shared Key authentication.
AccountKey string `yaml:"account-key,omitempty" json:"account-key,omitempty"`
// TenantID is the Azure Active Directory tenant ID for AAD authentication.
TenantID string `yaml:"tenant-id,omitempty" json:"tenant-id,omitempty"`
// ClientID is the Azure Active Directory client ID for AAD authentication.
ClientID string `yaml:"client-id,omitempty" json:"client-id,omitempty"`
// ClientSecret is the Azure Active Directory client secret for AAD authentication.
ClientSecret string `yaml:"client-secret,omitempty" json:"client-secret,omitempty"`
}

// Validate checks if the AzureStorage is valid.
func (a *AzureStorage) Validate() error {
if a.Endpoint == "" {
return errors.New("azure storage endpoint is not specified")
}
if a.ContainerName == "" {
return errors.New("azure storage container name is not specified")
}

// Check for valid authentication method.
hasSharedKey := a.AccountName != "" && a.AccountKey != ""
hasAAD := a.TenantID != "" && a.ClientID != "" && a.ClientSecret != ""

if hasSharedKey && hasAAD {
return errors.New(`azure storage authentication method is ambiguous:
use either AccountName/AccountKey or TenantID/ClientID/ClientSecret, not both`)
}

return nil
}

func (a *AzureStorage) toModel(c *model.Config) (model.Storage, error) {
agent, err := c.ResolveSecretAgent(a.SecretAgentName, a.SecretAgent.ToModel())
if err != nil {
return nil, err
}
return &model.AzureStorage{
Endpoint: a.Endpoint,
ContainerName: a.ContainerName,
Path: a.Path,
Auth: getAzureAuth(a),
SecretAgent: agent,
}, nil
}

func getAzureAuth(a *AzureStorage) model.AzureAuth {
if a.AccountName != "" && a.AccountKey != "" {
return model.AzureSharedKeyAuth{
AccountName: a.AccountName,
AccountKey: a.AccountKey,
}
}

if a.TenantID != "" && a.ClientID != "" && a.ClientSecret != "" {
return model.AzureADAuth{
TenantID: a.TenantID,
ClientID: a.ClientID,
ClientSecret: a.ClientSecret,
}
}

return nil
}

func newAzureStorageFromModel(s *model.AzureStorage, config *model.Config) *AzureStorage {
azureStorage := &AzureStorage{
Endpoint: s.Endpoint,
ContainerName: s.ContainerName,
Path: s.Path,
SecretAgentConfig: ResolveSecretAgentFromModel(s.SecretAgent, config),
}

switch auth := s.Auth.(type) {
case model.AzureSharedKeyAuth:
azureStorage.AccountName = auth.AccountName
azureStorage.AccountKey = auth.AccountKey
case model.AzureADAuth:
azureStorage.TenantID = auth.TenantID
azureStorage.ClientID = auth.ClientID
azureStorage.ClientSecret = auth.ClientSecret
}

return azureStorage
}
33 changes: 33 additions & 0 deletions pkg/dto/storage_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dto

import (
"errors"

"github.com/aerospike/aerospike-backup-service/v2/pkg/model"
)

// LocalStorage represents the configuration for local storage.
type LocalStorage struct {
// The root path for the backup repository.
Path string `yaml:"path" json:"path" example:"backups" validate:"required"`
}

// Validate checks if the LocalStorage is valid.
func (l *LocalStorage) Validate() error {
if l.Path == "" {
return errors.New("local storage path is not specified")
}
return nil
}

func (l *LocalStorage) toModel() (model.Storage, error) {
return &model.LocalStorage{
Path: l.Path,
}, nil
}

func newLocalStorageFromModel(s *model.LocalStorage) *LocalStorage {
return &LocalStorage{
Path: s.Path,
}
}
2 changes: 1 addition & 1 deletion pkg/dto/storage_s3.go
Original file line number Diff line number Diff line change
@@ -56,7 +56,7 @@ func (s *S3Storage) Validate() error {
return s.SecretAgentConfig.validate()
}

func (s *S3Storage) ToModel(config *model.Config) (*model.S3Storage, error) {
func (s *S3Storage) toModel(config *model.Config) (*model.S3Storage, error) {
var auth *model.S3Authentication
if s.AccessKeyID != nil {
agent, err := config.ResolveSecretAgent(s.SecretAgentName, s.SecretAgent.ToModel())
18 changes: 2 additions & 16 deletions pkg/model/storage.go → pkg/model/storage_azure.go
Original file line number Diff line number Diff line change
@@ -2,22 +2,6 @@ package model

import "fmt"

// Storage represents the configuration for a backup storage details.
// This interface is implemented by all specific storage types.
type Storage interface {
storage()
}

type LocalStorage struct {
// Path is the root directory where backups will be stored locally.
Path string
}

func (s *LocalStorage) storage() {}
func (s *LocalStorage) String() string {
return fmt.Sprintf("LocalStorage(Path: %s)", s.Path)
}

// AzureStorage represents the configuration for Azure Blob storage.
type AzureStorage struct {
// Path is the root directory within the Azure Blob container where backups will be stored.
@@ -29,6 +13,8 @@ type AzureStorage struct {
// Auth holds the authentication details for Azure Blob storage.
// It can be nil or AzureSharedKeyAuth or AzureADAuth.
Auth AzureAuth
// SecretAgent configuration to fetch keyfile from a secret store (optional).
SecretAgent *SecretAgent
}

func (s *AzureStorage) storage() {}
2 changes: 1 addition & 1 deletion pkg/model/storage_gcp.go
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ type GcpStorage struct {
// Endpoint is an alternative URL for the GCS API.
// This should only be used for testing or in specific non-production scenarios.
Endpoint string
// Optional secret agent configuration to fetch keyfile from a secret store.
// SecretAgent configuration to fetch keyfile from a secret store (optional).
SecretAgent *SecretAgent
}

19 changes: 19 additions & 0 deletions pkg/model/storage_local.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package model

import "fmt"

// Storage represents the configuration for a backup storage details.
// This interface is implemented by all specific storage types.
type Storage interface {
storage()
}

type LocalStorage struct {
// Path is the root directory where backups will be stored locally.
Path string
}

func (s *LocalStorage) storage() {}
func (s *LocalStorage) String() string {
return fmt.Sprintf("LocalStorage(Path: %s)", s.Path)
}