瀏覽代碼

initial support for vault backend

Alexander Belanger 4 年之前
父節點
當前提交
d9456871d5

+ 4 - 0
api/server/shared/config/env/envconfs.go

@@ -93,6 +93,10 @@ type DBConf struct {
 
 	SQLLite     bool   `env:"SQL_LITE,default=false"`
 	SQLLitePath string `env:"SQL_LITE_PATH,default=/porter/porter.db"`
+
+	VaultPrefix    string `env:"VAULT_PREFIX,default=production"`
+	VaultAPIKey    string `env:"VAULT_API_KEY"`
+	VaultServerURL string `env:"VAULT_SERVER_URL"`
 }
 
 // RedisConf is the redis config required for the provisioner container

+ 9 - 0
api/server/shared/config/loader/init_ee.go

@@ -4,6 +4,7 @@ package loader
 
 import (
 	eeBilling "github.com/porter-dev/porter/ee/billing"
+	"github.com/porter-dev/porter/ee/integrations/vault"
 	"github.com/porter-dev/porter/ee/models"
 	eeGorm "github.com/porter-dev/porter/ee/repository/gorm"
 	"github.com/porter-dev/porter/internal/billing"
@@ -38,4 +39,12 @@ func init() {
 	} else {
 		InstanceBillingManager = &billing.NoopBillingManager{}
 	}
+
+	if InstanceEnvConf.DBConf.VaultAPIKey != "" && InstanceEnvConf.DBConf.VaultServerURL != "" && InstanceEnvConf.DBConf.VaultPrefix != "" {
+		InstanceCredentialBackend = vault.NewClient(
+			InstanceEnvConf.DBConf.VaultServerURL,
+			InstanceEnvConf.DBConf.VaultAPIKey,
+			InstanceEnvConf.DBConf.VaultPrefix,
+		)
+	}
 }

+ 3 - 1
api/server/shared/config/loader/loader.go

@@ -23,6 +23,7 @@ import (
 	"github.com/porter-dev/porter/internal/notifier"
 	"github.com/porter-dev/porter/internal/notifier/sendgrid"
 	"github.com/porter-dev/porter/internal/oauth"
+	"github.com/porter-dev/porter/internal/repository/credentials"
 	"github.com/porter-dev/porter/internal/repository/gorm"
 
 	lr "github.com/porter-dev/porter/internal/logger"
@@ -33,6 +34,7 @@ import (
 var InstanceBillingManager billing.BillingManager
 var InstanceEnvConf *envloader.EnvConf
 var InstanceDB *pgorm.DB
+var InstanceCredentialBackend credentials.CredentialStorage
 
 type EnvConfigLoader struct {
 	version string
@@ -82,7 +84,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		key[i] = b
 	}
 
-	res.Repo = gorm.NewRepository(InstanceDB, &key)
+	res.Repo = gorm.NewRepository(InstanceDB, &key, InstanceCredentialBackend)
 
 	// create the session store
 	res.Store, err = sessionstore.NewStore(

+ 49 - 0
ee/integrations/vault/types.go

@@ -0,0 +1,49 @@
+// +build ee
+
+package vault
+
+import "github.com/porter-dev/porter/internal/repository/credentials"
+
+type CreateVaultSecretRequest struct {
+	Data interface{} `json:"data"`
+}
+
+type GetVaultSecretGenericResponse struct {
+	RequestID string `json:"request_id"`
+}
+
+type VaultMetadata struct {
+	CreatedTime string `json:"created_time"`
+	Destroyed   bool   `json:"destroyed"`
+	Version     uint   `json:"version"`
+}
+
+type GetOAuthCredentialResponse struct {
+	*GetVaultSecretGenericResponse
+	Data *GetOAuthCredentialData `json:"data"`
+}
+
+type GetOAuthCredentialData struct {
+	Metadata *VaultMetadata               `json:"metadata"`
+	Data     *credentials.OAuthCredential `json:"data"`
+}
+
+type GetGCPCredentialResponse struct {
+	*GetVaultSecretGenericResponse
+	Data *GetGCPCredentialData `json:"data"`
+}
+
+type GetGCPCredentialData struct {
+	Metadata *VaultMetadata             `json:"metadata"`
+	Data     *credentials.GCPCredential `json:"data"`
+}
+
+type GetAWSCredentialResponse struct {
+	*GetVaultSecretGenericResponse
+	Data *GetAWSCredentialData `json:"data"`
+}
+
+type GetAWSCredentialData struct {
+	Metadata *VaultMetadata             `json:"metadata"`
+	Data     *credentials.AWSCredential `json:"data"`
+}

+ 247 - 0
ee/integrations/vault/vault.go

@@ -0,0 +1,247 @@
+// +build ee
+
+package vault
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"strings"
+	"time"
+
+	"github.com/porter-dev/porter/internal/models/integrations"
+	"github.com/porter-dev/porter/internal/repository/credentials"
+)
+
+// Client contains an API client for IronPlans
+type Client struct {
+	apiKey       string
+	serverURL    string
+	secretPrefix string
+	httpClient   *http.Client
+}
+
+// NewClient creates a new billing API client
+func NewClient(serverURL, apiKey, secretPrefix string) *Client {
+	httpClient := &http.Client{
+		Timeout: time.Minute,
+	}
+
+	return &Client{apiKey, serverURL, secretPrefix, httpClient}
+}
+
+func (c *Client) WriteOAuthCredential(
+	oauthIntegration *integrations.OAuthIntegration,
+	data *credentials.OAuthCredential,
+) error {
+	reqData := &CreateVaultSecretRequest{
+		Data: data,
+	}
+
+	return c.postRequest(c.getOAuthCredentialPath(oauthIntegration), reqData, nil)
+}
+
+func (c *Client) GetOAuthCredential(oauthIntegration *integrations.OAuthIntegration) (*credentials.OAuthCredential, error) {
+	resp := &GetOAuthCredentialResponse{}
+
+	err := c.getRequest(c.getOAuthCredentialPath(oauthIntegration), resp)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Data.Data, nil
+}
+
+func (c *Client) getOAuthCredentialPath(oauthIntegration *integrations.OAuthIntegration) string {
+	return fmt.Sprintf(
+		"/v1/kv/data/secret/%s/%d/oauth/%d",
+		c.secretPrefix,
+		oauthIntegration.ProjectID,
+		oauthIntegration.ID,
+	)
+}
+
+func (c *Client) WriteGCPCredential(
+	gcpIntegration *integrations.GCPIntegration,
+	data *credentials.GCPCredential) error {
+	reqData := &CreateVaultSecretRequest{
+		Data: data,
+	}
+
+	return c.postRequest(c.getGCPCredentialPath(gcpIntegration), reqData, nil)
+}
+
+func (c *Client) GetGCPCredential(gcpIntegration *integrations.GCPIntegration) (*credentials.GCPCredential, error) {
+	resp := &GetGCPCredentialResponse{}
+
+	err := c.getRequest(c.getGCPCredentialPath(gcpIntegration), resp)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Data.Data, nil
+}
+
+func (c *Client) getGCPCredentialPath(gcpIntegration *integrations.GCPIntegration) string {
+	return fmt.Sprintf(
+		"/v1/kv/data/secret/%s/%d/gcp/%d",
+		c.secretPrefix,
+		gcpIntegration.ProjectID,
+		gcpIntegration.ID,
+	)
+}
+
+func (c *Client) WriteAWSCredential(
+	awsIntegration *integrations.AWSIntegration,
+	data *credentials.AWSCredential) error {
+	reqData := &CreateVaultSecretRequest{
+		Data: data,
+	}
+
+	return c.postRequest(c.getAWSCredentialPath(awsIntegration), reqData, nil)
+}
+
+func (c *Client) GetAWSCredential(awsIntegration *integrations.AWSIntegration) (*credentials.AWSCredential, error) {
+	resp := &GetAWSCredentialResponse{}
+
+	err := c.getRequest(c.getAWSCredentialPath(awsIntegration), resp)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Data.Data, nil
+}
+
+func (c *Client) getAWSCredentialPath(awsIntegration *integrations.AWSIntegration) string {
+	return fmt.Sprintf(
+		"/v1/kv/data/secret/%s/%d/aws/%d",
+		c.secretPrefix,
+		awsIntegration.ProjectID,
+		awsIntegration.ID,
+	)
+}
+
+func (c *Client) postRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("POST", path, data, dst)
+}
+
+func (c *Client) putRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("PUT", path, data, dst)
+}
+
+func (c *Client) deleteRequest(path string, data interface{}, dst interface{}) error {
+	return c.writeRequest("DELETE", path, data, dst)
+}
+
+func (c *Client) getRequest(path string, dst interface{}) error {
+	reqURL, err := url.Parse(c.serverURL)
+
+	if err != nil {
+		return nil
+	}
+
+	reqURL.Path = path
+
+	req, err := http.NewRequest(
+		"GET",
+		reqURL.String(),
+		nil,
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	req.Header.Set("Accept", "application/json; charset=utf-8")
+	req.Header.Set("X-Vault-Token", c.apiKey)
+
+	res, err := c.httpClient.Do(req)
+
+	if err != nil {
+		return err
+	}
+
+	defer res.Body.Close()
+
+	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
+		resBytes, err := ioutil.ReadAll(res.Body)
+
+		if err != nil {
+			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
+		}
+
+		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
+	}
+
+	if dst != nil {
+		return json.NewDecoder(res.Body).Decode(dst)
+	}
+
+	return nil
+}
+
+func (c *Client) writeRequest(method, path string, data interface{}, dst interface{}) error {
+	reqURL, err := url.Parse(c.serverURL)
+
+	if err != nil {
+		return nil
+	}
+
+	reqURL.Path = path
+
+	var strData []byte
+
+	if data != nil {
+		strData, err = json.Marshal(data)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	req, err := http.NewRequest(
+		method,
+		reqURL.String(),
+		strings.NewReader(string(strData)),
+	)
+
+	if err != nil {
+		return err
+	}
+
+	req.Header.Set("Content-Type", "application/json; charset=utf-8")
+	req.Header.Set("Accept", "application/json; charset=utf-8")
+	req.Header.Set("X-Vault-Token", c.apiKey)
+
+	fmt.Println("URL IS", reqURL.String(), string(strData))
+
+	res, err := c.httpClient.Do(req)
+
+	if err != nil {
+		return err
+	}
+
+	defer res.Body.Close()
+
+	if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
+		resBytes, err := ioutil.ReadAll(res.Body)
+
+		if err != nil {
+			return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error())
+		}
+
+		return fmt.Errorf("request failed with status code %d: %s\n", res.StatusCode, string(resBytes))
+	}
+
+	if dst != nil {
+		return json.NewDecoder(res.Body).Decode(dst)
+	}
+
+	return nil
+}

+ 43 - 0
internal/repository/credentials/credentials.go

@@ -0,0 +1,43 @@
+package credentials
+
+import "github.com/porter-dev/porter/internal/models/integrations"
+
+type OAuthCredential struct {
+	// The ID issued to the client
+	ClientID []byte `json:"client_id"`
+
+	// The end-users's access token
+	AccessToken []byte `json:"access_token"`
+
+	// The end-user's refresh token
+	RefreshToken []byte `json:"refresh_token"`
+}
+
+type GCPCredential struct {
+	// KeyData for a service account for GCP connectors
+	GCPKeyData []byte `json:"gcp_key_data"`
+}
+
+type AWSCredential struct {
+	// The AWS cluster ID
+	// See https://github.com/kubernetes-sigs/aws-iam-authenticator#what-is-a-cluster-id
+	AWSClusterID []byte `json:"aws_cluster_id"`
+
+	// The AWS access key for this IAM user
+	AWSAccessKeyID []byte `json:"aws_access_key_id"`
+
+	// The AWS secret key for this IAM user
+	AWSSecretAccessKey []byte `json:"aws_secret_access_key"`
+
+	// An optional session token, if the user is assuming a role
+	AWSSessionToken []byte `json:"aws_session_token"`
+}
+
+type CredentialStorage interface {
+	WriteOAuthCredential(oauthIntegration *integrations.OAuthIntegration, data *OAuthCredential) error
+	GetOAuthCredential(oauthIntegration *integrations.OAuthIntegration) (*OAuthCredential, error)
+	WriteGCPCredential(gcpIntegration *integrations.GCPIntegration, data *GCPCredential) error
+	GetGCPCredential(gcpIntegration *integrations.GCPIntegration) (*GCPCredential, error)
+	WriteAWSCredential(awsIntegration *integrations.AWSIntegration, data *AWSCredential) error
+	GetAWSCredential(awsIntegration *integrations.AWSIntegration) (*AWSCredential, error)
+}

+ 156 - 33
internal/repository/gorm/auth.go

@@ -3,6 +3,7 @@ package gorm
 import (
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/repository/credentials"
 	"gorm.io/gorm"
 
 	ints "github.com/porter-dev/porter/internal/models/integrations"
@@ -83,10 +84,6 @@ func (repo *KubeIntegrationRepository) ListKubeIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, ki := range kis {
-		repo.DecryptKubeIntegrationData(ki, repo.key)
-	}
-
 	return kis, nil
 }
 
@@ -303,10 +300,6 @@ func (repo *BasicIntegrationRepository) ListBasicIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, basic := range basics {
-		repo.DecryptBasicIntegrationData(basic, repo.key)
-	}
-
 	return basics, nil
 }
 
@@ -443,10 +436,6 @@ func (repo *OIDCIntegrationRepository) ListOIDCIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, oidc := range oidcs {
-		repo.DecryptOIDCIntegrationData(oidc, repo.key)
-	}
-
 	return oidcs, nil
 }
 
@@ -590,8 +579,9 @@ func (repo *OIDCIntegrationRepository) DecryptOIDCIntegrationData(
 
 // OAuthIntegrationRepository uses gorm.DB for querying the database
 type OAuthIntegrationRepository struct {
-	db  *gorm.DB
-	key *[32]byte
+	db             *gorm.DB
+	key            *[32]byte
+	storageBackend credentials.CredentialStorage
 }
 
 // NewOAuthIntegrationRepository returns a OAuthIntegrationRepository which uses
@@ -600,8 +590,9 @@ type OAuthIntegrationRepository struct {
 func NewOAuthIntegrationRepository(
 	db *gorm.DB,
 	key *[32]byte,
+	storageBackend credentials.CredentialStorage,
 ) repository.OAuthIntegrationRepository {
-	return &OAuthIntegrationRepository{db, key}
+	return &OAuthIntegrationRepository{db, key, storageBackend}
 }
 
 // CreateOAuthIntegration creates a new oauth auth mechanism
@@ -614,6 +605,19 @@ func (repo *OAuthIntegrationRepository) CreateOAuthIntegration(
 		return nil, err
 	}
 
+	// if storage backend is not nil, strip out credential data, which will be stored in credential
+	// storage backend after write to DB
+	var credentialData = &credentials.OAuthCredential{}
+
+	if repo.storageBackend != nil {
+		credentialData.AccessToken = am.AccessToken
+		credentialData.RefreshToken = am.RefreshToken
+		credentialData.ClientID = am.ClientID
+		am.AccessToken = []byte{}
+		am.RefreshToken = []byte{}
+		am.ClientID = []byte{}
+	}
+
 	project := &models.Project{}
 
 	if err := repo.db.Where("id = ?", am.ProjectID).First(&project).Error; err != nil {
@@ -630,6 +634,14 @@ func (repo *OAuthIntegrationRepository) CreateOAuthIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		err = repo.storageBackend.WriteOAuthCredential(am, credentialData)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return am, nil
 }
 
@@ -643,6 +655,18 @@ func (repo *OAuthIntegrationRepository) ReadOAuthIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		credentialData, err := repo.storageBackend.GetOAuthCredential(oauth)
+
+		if err != nil {
+			return nil, err
+		}
+
+		oauth.AccessToken = credentialData.AccessToken
+		oauth.RefreshToken = credentialData.RefreshToken
+		oauth.ClientID = credentialData.ClientID
+	}
+
 	err := repo.DecryptOAuthIntegrationData(oauth, repo.key)
 
 	if err != nil {
@@ -663,10 +687,6 @@ func (repo *OAuthIntegrationRepository) ListOAuthIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, oauth := range oauths {
-		repo.DecryptOAuthIntegrationData(oauth, repo.key)
-	}
-
 	return oauths, nil
 }
 
@@ -680,6 +700,19 @@ func (repo *OAuthIntegrationRepository) UpdateOAuthIntegration(
 		return nil, err
 	}
 
+	// if storage backend is not nil, strip out credential data, which will be stored in credential
+	// storage backend after write to DB
+	var credentialData = &credentials.OAuthCredential{}
+
+	if repo.storageBackend != nil {
+		credentialData.AccessToken = am.AccessToken
+		credentialData.RefreshToken = am.RefreshToken
+		credentialData.ClientID = am.ClientID
+		am.AccessToken = []byte{}
+		am.RefreshToken = []byte{}
+		am.ClientID = []byte{}
+	}
+
 	if err := repo.db.Save(am).Error; err != nil {
 		return nil, err
 	}
@@ -690,6 +723,14 @@ func (repo *OAuthIntegrationRepository) UpdateOAuthIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		err = repo.storageBackend.WriteOAuthCredential(am, credentialData)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return am, nil
 }
 
@@ -773,8 +814,9 @@ func (repo *OAuthIntegrationRepository) DecryptOAuthIntegrationData(
 
 // GCPIntegrationRepository uses gorm.DB for querying the database
 type GCPIntegrationRepository struct {
-	db  *gorm.DB
-	key *[32]byte
+	db             *gorm.DB
+	key            *[32]byte
+	storageBackend credentials.CredentialStorage
 }
 
 // NewGCPIntegrationRepository returns a GCPIntegrationRepository which uses
@@ -783,8 +825,9 @@ type GCPIntegrationRepository struct {
 func NewGCPIntegrationRepository(
 	db *gorm.DB,
 	key *[32]byte,
+	storageBackend credentials.CredentialStorage,
 ) repository.GCPIntegrationRepository {
-	return &GCPIntegrationRepository{db, key}
+	return &GCPIntegrationRepository{db, key, storageBackend}
 }
 
 // CreateGCPIntegration creates a new gcp auth mechanism
@@ -797,6 +840,15 @@ func (repo *GCPIntegrationRepository) CreateGCPIntegration(
 		return nil, err
 	}
 
+	// if storage backend is not nil, strip out credential data, which will be stored in credential
+	// storage backend after write to DB
+	var credentialData = &credentials.GCPCredential{}
+
+	if repo.storageBackend != nil {
+		credentialData.GCPKeyData = am.GCPKeyData
+		am.GCPKeyData = []byte{}
+	}
+
 	project := &models.Project{}
 
 	if err := repo.db.Where("id = ?", am.ProjectID).First(&project).Error; err != nil {
@@ -813,6 +865,14 @@ func (repo *GCPIntegrationRepository) CreateGCPIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		err = repo.storageBackend.WriteGCPCredential(am, credentialData)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return am, nil
 }
 
@@ -826,6 +886,16 @@ func (repo *GCPIntegrationRepository) ReadGCPIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		credentialData, err := repo.storageBackend.GetGCPCredential(gcp)
+
+		if err != nil {
+			return nil, err
+		}
+
+		gcp.GCPKeyData = credentialData.GCPKeyData
+	}
+
 	err := repo.DecryptGCPIntegrationData(gcp, repo.key)
 
 	if err != nil {
@@ -846,10 +916,6 @@ func (repo *GCPIntegrationRepository) ListGCPIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, gcp := range gcps {
-		repo.DecryptGCPIntegrationData(gcp, repo.key)
-	}
-
 	return gcps, nil
 }
 
@@ -893,8 +959,9 @@ func (repo *GCPIntegrationRepository) DecryptGCPIntegrationData(
 
 // AWSIntegrationRepository uses gorm.DB for querying the database
 type AWSIntegrationRepository struct {
-	db  *gorm.DB
-	key *[32]byte
+	db             *gorm.DB
+	key            *[32]byte
+	storageBackend credentials.CredentialStorage
 }
 
 // NewAWSIntegrationRepository returns a AWSIntegrationRepository which uses
@@ -903,8 +970,9 @@ type AWSIntegrationRepository struct {
 func NewAWSIntegrationRepository(
 	db *gorm.DB,
 	key *[32]byte,
+	storageBackend credentials.CredentialStorage,
 ) repository.AWSIntegrationRepository {
-	return &AWSIntegrationRepository{db, key}
+	return &AWSIntegrationRepository{db, key, storageBackend}
 }
 
 // CreateAWSIntegration creates a new aws auth mechanism
@@ -917,6 +985,21 @@ func (repo *AWSIntegrationRepository) CreateAWSIntegration(
 		return nil, err
 	}
 
+	// if storage backend is not nil, strip out credential data, which will be stored in credential
+	// storage backend after write to DB
+	var credentialData = &credentials.AWSCredential{}
+
+	if repo.storageBackend != nil {
+		credentialData.AWSAccessKeyID = am.AWSAccessKeyID
+		credentialData.AWSClusterID = am.AWSClusterID
+		credentialData.AWSSecretAccessKey = am.AWSSecretAccessKey
+		credentialData.AWSSessionToken = am.AWSSessionToken
+		am.AWSAccessKeyID = []byte{}
+		am.AWSClusterID = []byte{}
+		am.AWSSecretAccessKey = []byte{}
+		am.AWSSessionToken = []byte{}
+	}
+
 	project := &models.Project{}
 
 	if err := repo.db.Where("id = ?", am.ProjectID).First(&project).Error; err != nil {
@@ -933,6 +1016,14 @@ func (repo *AWSIntegrationRepository) CreateAWSIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		err = repo.storageBackend.WriteAWSCredential(am, credentialData)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return am, nil
 }
 
@@ -946,10 +1037,33 @@ func (repo *AWSIntegrationRepository) OverwriteAWSIntegration(
 		return nil, err
 	}
 
+	// if storage backend is not nil, strip out credential data, which will be stored in credential
+	// storage backend after write to DB
+	var credentialData = &credentials.AWSCredential{}
+
+	if repo.storageBackend != nil {
+		credentialData.AWSAccessKeyID = am.AWSAccessKeyID
+		credentialData.AWSClusterID = am.AWSClusterID
+		credentialData.AWSSecretAccessKey = am.AWSSecretAccessKey
+		credentialData.AWSSessionToken = am.AWSSessionToken
+		am.AWSAccessKeyID = []byte{}
+		am.AWSClusterID = []byte{}
+		am.AWSSecretAccessKey = []byte{}
+		am.AWSSessionToken = []byte{}
+	}
+
 	if err := repo.db.Save(am).Error; err != nil {
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		err = repo.storageBackend.WriteAWSCredential(am, credentialData)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+
 	return am, nil
 }
 
@@ -963,6 +1077,19 @@ func (repo *AWSIntegrationRepository) ReadAWSIntegration(
 		return nil, err
 	}
 
+	if repo.storageBackend != nil {
+		credentialData, err := repo.storageBackend.GetAWSCredential(aws)
+
+		if err != nil {
+			return nil, err
+		}
+
+		aws.AWSAccessKeyID = credentialData.AWSAccessKeyID
+		aws.AWSClusterID = credentialData.AWSClusterID
+		aws.AWSSecretAccessKey = credentialData.AWSSecretAccessKey
+		aws.AWSSessionToken = credentialData.AWSSessionToken
+	}
+
 	err := repo.DecryptAWSIntegrationData(aws, repo.key)
 
 	if err != nil {
@@ -983,10 +1110,6 @@ func (repo *AWSIntegrationRepository) ListAWSIntegrationsByProjectID(
 		return nil, err
 	}
 
-	for _, aws := range awss {
-		repo.DecryptAWSIntegrationData(aws, repo.key)
-	}
-
 	return awss, nil
 }
 

+ 1 - 1
internal/repository/gorm/helpers_test.go

@@ -88,7 +88,7 @@ func setupTestEnv(tester *tester, t *testing.T) {
 
 	tester.key = &key
 
-	tester.repo = gorm.NewRepository(db, &key)
+	tester.repo = gorm.NewRepository(db, &key, nil)
 }
 
 func cleanup(tester *tester, t *testing.T) {

+ 5 - 4
internal/repository/gorm/repository.go

@@ -2,6 +2,7 @@ package gorm
 
 import (
 	"github.com/porter-dev/porter/internal/repository"
+	"github.com/porter-dev/porter/internal/repository/credentials"
 	"gorm.io/gorm"
 )
 
@@ -140,7 +141,7 @@ func (t *GormRepository) ProjectUsage() repository.ProjectUsageRepository {
 
 // NewRepository returns a Repository which persists users in memory
 // and accepts a parameter that can trigger read/write errors
-func NewRepository(db *gorm.DB, key *[32]byte) repository.Repository {
+func NewRepository(db *gorm.DB, key *[32]byte, storageBackend credentials.CredentialStorage) repository.Repository {
 	return &GormRepository{
 		user:                      NewUserRepository(db),
 		session:                   NewSessionRepository(db),
@@ -159,9 +160,9 @@ func NewRepository(db *gorm.DB, key *[32]byte) repository.Repository {
 		kubeIntegration:           NewKubeIntegrationRepository(db, key),
 		basicIntegration:          NewBasicIntegrationRepository(db, key),
 		oidcIntegration:           NewOIDCIntegrationRepository(db, key),
-		oauthIntegration:          NewOAuthIntegrationRepository(db, key),
-		gcpIntegration:            NewGCPIntegrationRepository(db, key),
-		awsIntegration:            NewAWSIntegrationRepository(db, key),
+		oauthIntegration:          NewOAuthIntegrationRepository(db, key, storageBackend),
+		gcpIntegration:            NewGCPIntegrationRepository(db, key, storageBackend),
+		awsIntegration:            NewAWSIntegrationRepository(db, key, storageBackend),
 		githubAppInstallation:     NewGithubAppInstallationRepository(db),
 		githubAppOAuthIntegration: NewGithubAppOAuthIntegrationRepository(db),
 		slackIntegration:          NewSlackIntegrationRepository(db, key),