From 7b24fd5aaf6b8792e693708b5324074732f1e388 Mon Sep 17 00:00:00 2001 From: Jacob Lindgren Date: Mon, 31 Oct 2022 14:51:42 -0500 Subject: [PATCH] Add Secrets DAO secrets-manager implementation --- dao/application_authentication_dao.go | 5 +- dao/authentication_dao_common.go | 33 ++++- dao/authentication_secrets_manager_dao.go | 4 +- dao/{secret_dao.go => secret_db_dao.go} | 30 ----- ...cret_dao_test.go => secret_db_dao_test.go} | 0 dao/secret_secrets_manager_dao.go | 118 ++++++++++++++++++ dao/test_factories.go | 3 +- secret_handlers.go | 6 +- 8 files changed, 158 insertions(+), 41 deletions(-) rename dao/{secret_dao.go => secret_db_dao.go} (78%) rename dao/{secret_dao_test.go => secret_db_dao_test.go} (100%) create mode 100644 dao/secret_secrets_manager_dao.go diff --git a/dao/application_authentication_dao.go b/dao/application_authentication_dao.go index 4f8682302..e8d795af3 100644 --- a/dao/application_authentication_dao.go +++ b/dao/application_authentication_dao.go @@ -77,7 +77,7 @@ func (a *applicationAuthenticationDaoImpl) ApplicationAuthenticationsByAuthentic query := a.getDb().Preload("Tenant") switch config.Get().SecretStore { - case config.DatabaseStore: + case config.DatabaseStore, config.SecretsManagerStore: authIds := make([]int64, len(authentications)) for _, value := range authentications { @@ -95,9 +95,6 @@ func (a *applicationAuthenticationDaoImpl) ApplicationAuthenticationsByAuthentic query.Where("authentication_uid IN ?", authUuids) - case config.SecretsManagerStore: - return nil, m.ErrBadSecretStore - default: return nil, m.ErrBadSecretStore } diff --git a/dao/authentication_dao_common.go b/dao/authentication_dao_common.go index c65143f02..7d37a51a9 100644 --- a/dao/authentication_dao_common.go +++ b/dao/authentication_dao_common.go @@ -5,21 +5,30 @@ import ( ) /* - Common DAO code for the 1..n authentication DAOs we support. - Currently, that list is: + Common DAO code for the 1..n authentication DAOs we support. Currently, that + list is: 1. DB Dao, using postgres and an encrypted column as a backend - 2. Hashicorp vault, as the name implies storing authentication things in the vault - 3. Amazon Secrets Manager, a WIP that uses Amazon Secrets Manager to store authentication things + 2. Hashicorp vault, as the name implies storing authentication things in the + vault + 3. Amazon Secrets Manager, a WIP that uses Amazon Secrets Manager to store + authentication things + + Also including the functions for the SecretDAO's since they are in effect + specialized Authentication DAOs */ +const secretResourceType = "Tenant" + // init sets the default DAO implementation so that other packages can request it easily. func init() { GetAuthenticationDao = getDefaultAuthenticationDao + GetSecretDao = getDefaultSecretDao } // GetAuthenticationDao is a function definition that can be replaced in runtime in case some other DAO provider is // needed. var GetAuthenticationDao func(daoParams *RequestParams) AuthenticationDao +var GetSecretDao func(daoParams *RequestParams) SecretDao // getDefaultAuthenticationDao gets the default DAO implementation which will have the given tenant ID. func getDefaultAuthenticationDao(daoParams *RequestParams) AuthenticationDao { @@ -37,3 +46,19 @@ func getDefaultAuthenticationDao(daoParams *RequestParams) AuthenticationDao { return &noSecretStoreAuthenticationDao{} } } + +func getDefaultSecretDao(daoParams *RequestParams) SecretDao { + switch config.Get().SecretStore { + case config.DatabaseStore: + return &secretDaoDbImpl{RequestParams: daoParams} + case config.VaultStore: + return &noSecretStoreSecretsDao{} + case config.SecretsManagerStore: + return &secretDaoSecretsManagerImpl{ + RequestParams: daoParams, + secretDaoDbImpl: secretDaoDbImpl{RequestParams: daoParams}, + } + default: + return &noSecretStoreSecretsDao{} + } +} diff --git a/dao/authentication_secrets_manager_dao.go b/dao/authentication_secrets_manager_dao.go index b5a46fa37..ef9cde517 100644 --- a/dao/authentication_secrets_manager_dao.go +++ b/dao/authentication_secrets_manager_dao.go @@ -2,7 +2,9 @@ package dao import ( "fmt" + "strings" + "github.com/RedHatInsights/sources-api-go/config" "github.com/RedHatInsights/sources-api-go/dao/amazon" m "github.com/RedHatInsights/sources-api-go/model" ) @@ -72,7 +74,7 @@ func (a *authenticationSecretsManagerDaoImpl) BulkCreate(auth *m.Authentication) func (a *authenticationSecretsManagerDaoImpl) Update(auth *m.Authentication) error { // only reach out to amazon if there is a password present, otherwise pass // straight through to the db dao. - if auth.Password != nil { + if auth.Password != nil && !strings.HasPrefix(*auth.Password, config.Get().SecretsManagerPrefix) { // fetch the ARN of the current password (since we overwrote it in memory) arns := make([]*string, 1) err := a.getDbWithModel(). diff --git a/dao/secret_dao.go b/dao/secret_db_dao.go similarity index 78% rename from dao/secret_dao.go rename to dao/secret_db_dao.go index bb9ccc11e..39069e55b 100644 --- a/dao/secret_dao.go +++ b/dao/secret_db_dao.go @@ -3,37 +3,15 @@ package dao import ( "fmt" - "github.com/RedHatInsights/sources-api-go/config" m "github.com/RedHatInsights/sources-api-go/model" "github.com/RedHatInsights/sources-api-go/util" "gorm.io/gorm" ) -const secretResourceType = "Tenant" - -var GetSecretDao func(daoParams *RequestParams) SecretDao - type secretDaoDbImpl struct { *RequestParams } -func getDefaultSecretDao(daoParams *RequestParams) SecretDao { - switch config.Get().SecretStore { - case config.DatabaseStore: - return &secretDaoDbImpl{RequestParams: daoParams} - case config.VaultStore: - return &noSecretStoreSecretsDao{} - case config.SecretsManagerStore: - return &noSecretStoreSecretsDao{} - default: - return &noSecretStoreSecretsDao{} - } -} - -func init() { - GetSecretDao = getDefaultSecretDao -} - func (secret *secretDaoDbImpl) getDb() *gorm.DB { if secret.TenantID == nil { panic("nil tenant found in sourceDaoImpl DAO") @@ -75,14 +53,6 @@ func (secret *secretDaoDbImpl) Create(authentication *m.Authentication) error { authentication.TenantID = *secret.TenantID // the TenantID gets injected in the middleware authentication.ResourceType = secretResourceType authentication.ResourceID = *secret.TenantID - if authentication.Password != nil { - encryptedValue, err := util.Encrypt(*authentication.Password) - if err != nil { - return err - } - - authentication.Password = &encryptedValue - } return DB. Debug(). diff --git a/dao/secret_dao_test.go b/dao/secret_db_dao_test.go similarity index 100% rename from dao/secret_dao_test.go rename to dao/secret_db_dao_test.go diff --git a/dao/secret_secrets_manager_dao.go b/dao/secret_secrets_manager_dao.go new file mode 100644 index 000000000..583d8521a --- /dev/null +++ b/dao/secret_secrets_manager_dao.go @@ -0,0 +1,118 @@ +package dao + +import ( + "fmt" + "strings" + + "github.com/RedHatInsights/sources-api-go/config" + "github.com/RedHatInsights/sources-api-go/dao/amazon" + m "github.com/RedHatInsights/sources-api-go/model" + "github.com/RedHatInsights/sources-api-go/util" +) + +type secretDaoSecretsManagerImpl struct { + *RequestParams + + secretDaoDbImpl +} + +func (s *secretDaoSecretsManagerImpl) Create(auth *m.Authentication) error { + // only reach out to amazon if there is a password present, otherwise pass + // straight through to the db dao. + if auth.Password != nil { + sm, err := amazon.NewSecretsManagerClient() + if err != nil { + return err + } + + auth.TenantID = *s.TenantID + arn, err := sm.CreateSecret(auth, *auth.Password) + if err != nil { + return err + } + + err = auth.SetPassword(arn) + if err != nil { + return err + } + } + + return s.secretDaoDbImpl.Create(auth) +} + +func (s *secretDaoSecretsManagerImpl) Delete(id *int64) error { + auth, err := s.secretDaoDbImpl.GetById(id) + if err != nil { + return util.NewErrNotFound("secret") + } + + if auth.Password != nil { + sm, err := amazon.NewSecretsManagerClient() + if err != nil { + return err + } + + err = sm.DeleteSecret(*auth.Password) + if err != nil { + return err + } + } + + return s.secretDaoDbImpl.Delete(id) +} + +func (s *secretDaoSecretsManagerImpl) GetById(id *int64) (*m.Authentication, error) { + auth, err := s.secretDaoDbImpl.GetById(id) + if err != nil { + return nil, err + } + + if auth.Password != nil { + sm, err := amazon.NewSecretsManagerClient() + if err != nil { + return nil, err + } + + pass, err := sm.GetSecret(*auth.Password) + if err != nil { + return nil, err + } + + // set the password in-memory to the secrets-manager password + auth.Password = pass + } + + return auth, nil +} + +func (s *secretDaoSecretsManagerImpl) Update(auth *m.Authentication) error { + if auth.Password != nil && !strings.HasPrefix(*auth.Password, config.Get().SecretsManagerPrefix) { + // fetch the ARN of the current password (since we overwrote it in memory) + arns := make([]*string, 1) + err := s.getDbWithModel(). + Where("id = ?", auth.GetID()). + Limit(1). + Pluck("password_hash", &arns).Error + if err != nil { + return err + } + if *arns[0] == "" { + return fmt.Errorf("failed to fetch ARN for authentication %v", auth.GetID()) + } + + sm, err := amazon.NewSecretsManagerClient() + if err != nil { + return err + } + + err = sm.UpdateSecret(*arns[0], *auth.Password) + if err != nil { + return err + } + + // set the password back to the ARN so that we don't overwrite it. + auth.Password = arns[0] + } + + return s.secretDaoDbImpl.Update(auth) +} diff --git a/dao/test_factories.go b/dao/test_factories.go index f8d89808a..6fa6f4982 100644 --- a/dao/test_factories.go +++ b/dao/test_factories.go @@ -285,6 +285,7 @@ func TestSuiteForSourceWithOwnership(performTest func(suiteData *SourceOwnership func CreateSecretByName(name string, tenantID *int64, userID *int64) (*m.Authentication, error) { secretDao := GetSecretDao(&RequestParams{TenantID: tenantID}) + pass, _ := util.Encrypt("password") secret := &m.Authentication{ Name: util.StringRef(name), @@ -292,7 +293,7 @@ func CreateSecretByName(name string, tenantID *int64, userID *int64) (*m.Authent Username: util.StringRef("Y"), ResourceType: secretResourceType, ResourceID: *tenantID, - Password: util.StringRef("password"), + Password: util.StringRef(pass), } if userID != nil { diff --git a/secret_handlers.go b/secret_handlers.go index 794b887a6..3e8e87fd6 100644 --- a/secret_handlers.go +++ b/secret_handlers.go @@ -47,7 +47,6 @@ func SecretCreate(c echo.Context) error { Name: createRequest.Name, AuthType: createRequest.AuthType, Username: createRequest.Username, - Password: createRequest.Password, } err = secret.SetExtra(createRequest.Extra) @@ -55,6 +54,11 @@ func SecretCreate(c echo.Context) error { return err } + err = secret.SetPassword(createRequest.Password) + if err != nil { + return util.NewErrBadRequest(err) + } + if createRequest.UserScoped && requestParams.UserID != nil { secret.UserID = requestParams.UserID }