Skip to content

Commit

Permalink
chore: switch from aws-sdk-go v1 to v2 (#2059)
Browse files Browse the repository at this point in the history
Signed-off-by: Kent Rancourt <[email protected]>
  • Loading branch information
krancour authored May 25, 2024
1 parent cb46235 commit 3760ec3
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 66 deletions.
18 changes: 17 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Masterminds/semver/v3 v3.2.1
github.com/adrg/xdg v0.4.0
github.com/aws/aws-sdk-go v1.43.16
github.com/aws/aws-sdk-go-v2 v1.27.0
github.com/aws/aws-sdk-go-v2/config v1.27.16
github.com/aws/aws-sdk-go-v2/credentials v1.17.16
github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3
github.com/aws/aws-sdk-go-v2/service/sts v1.28.10
github.com/bacongobbler/browser v1.1.0
github.com/bombsimon/logrusr/v4 v4.1.0
github.com/coreos/go-oidc/v3 v3.10.0
Expand Down Expand Up @@ -53,6 +57,18 @@ require (
sigs.k8s.io/yaml v1.4.0
)

require (
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
)

require (
cloud.google.com/go/compute/metadata v0.3.0 // indirect
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
Expand Down
32 changes: 28 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,34 @@ github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aws/aws-sdk-go v1.43.16 h1:Y7wBby44f+tINqJjw5fLH3vA+gFq4uMITIKqditwM14=
github.com/aws/aws-sdk-go v1.43.16/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.27.0 h1:7bZWKoXhzI+mMR/HjdMx8ZCC5+6fY0lS5tr0bbgiLlo=
github.com/aws/aws-sdk-go-v2 v1.27.0/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
github.com/aws/aws-sdk-go-v2/config v1.27.16 h1:knpCuH7laFVGYTNd99Ns5t+8PuRjDn4HnnZK48csipM=
github.com/aws/aws-sdk-go-v2/config v1.27.16/go.mod h1:vutqgRhDUktwSge3hrC3nkuirzkJ4E/mLj5GvI0BQas=
github.com/aws/aws-sdk-go-v2/credentials v1.17.16 h1:7d2QxY83uYl0l58ceyiSpxg9bSbStqBC6BeEeHEchwo=
github.com/aws/aws-sdk-go-v2/credentials v1.17.16/go.mod h1:Ae6li/6Yc6eMzysRL2BXlPYvnrLLBg3D11/AmOjw50k=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3 h1:dQLK4TjtnlRGb0czOht2CevZ5l6RSyRWAnKeGd7VAFE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.3/go.mod h1:TL79f2P6+8Q7dTsILpiVST+AL9lkF6PPGI167Ny0Cjw=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7 h1:lf/8VTF2cM+N4SLzaYJERKEWAXq8MOMpZfU6wEPWsPk=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.7/go.mod h1:4SjkU7QiqK2M9oozyMzfZ/23LmUY+h3oFqhdeP5OMiI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7 h1:4OYVp0705xu8yjdyoWix0r9wPIRXnIzzOoUpQVHIJ/g=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.7/go.mod h1:vd7ESTEvI76T2Na050gODNmNU7+OyKrIKroYTu4ABiI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3 h1:NsP8PA4Kw1sA6UKl3ZFRIcA9dWomePbmoRIvfOl+HKs=
github.com/aws/aws-sdk-go-v2/service/ecr v1.28.3/go.mod h1:X52zjAVRaXklEU1TE/wO8kyyJSr9cJx9ZsqliWbyRys=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9 h1:Wx0rlZoEJR7JwlSZcHnEa7CNjrSIyVxMFWGAaXy4fJY=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.9/go.mod h1:aVMHdE0aHO3v+f/iw01fmXV/5DbfQ3Bi9nN7nd9bE9Y=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.9 h1:aD7AGQhvPuAxlSUfo0CWU7s6FpkbyykMhGYMvlqTjVs=
github.com/aws/aws-sdk-go-v2/service/sso v1.20.9/go.mod h1:c1qtZUWtygI6ZdvKppzCSXsDOq5I4luJPZ0Ud3juFCA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3 h1:Pav5q3cA260Zqez42T9UhIlsd9QeypszRPwC9LdSSsQ=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.3/go.mod h1:9lmoVDVLz/yUZwLaQ676TK02fhCu4+PgRSmMaKR1ozk=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.10 h1:69tpbPED7jKPyzMcrwSvhWcJ9bPnZsZs18NT40JwM0g=
github.com/aws/aws-sdk-go-v2/service/sts v1.28.10/go.mod h1:0Aqn1MnEuitqfsCNyKsdKLhDUOr4txD/g19EfiUqgws=
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/bacongobbler/browser v1.1.0 h1:6YTctUlzcApit1vpWgh+myjh8lQUyQRD2Ltoyvy2EoM=
github.com/bacongobbler/browser v1.1.0/go.mod h1:T9AaY4DSJ61FNgVTlCP/FWPrJ36TMRwI0Z18eLZ3IKI=
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
Expand Down Expand Up @@ -411,7 +437,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
Expand Down Expand Up @@ -439,7 +464,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down
37 changes: 26 additions & 11 deletions internal/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,11 @@ type Database interface {
// utilizes a Kubernetes controller runtime client to retrieve credentials
// stored in Kubernetes Secrets.
type kubernetesDatabase struct {
kargoClient client.Client
ecrHelper ecr.CredentialHelper
gcpHelper gcp.CredentialHelper
cfg KubernetesDatabaseConfig
kargoClient client.Client
ecrAccessKeyHelper ecr.AccessKeyCredentialHelper
ecrPodIdentityHelper ecr.PodIdentityCredentialHelper
gcpHelper gcp.CredentialHelper
cfg KubernetesDatabaseConfig
}

// KubernetesDatabaseConfig represents configuration for a Kubernetes based
Expand All @@ -98,10 +99,11 @@ func NewKubernetesDatabase(
cfg KubernetesDatabaseConfig,
) Database {
return &kubernetesDatabase{
kargoClient: kargoClient,
ecrHelper: ecr.NewCredentialHelper(),
gcpHelper: gcp.NewCredentialHelper(),
cfg: cfg,
kargoClient: kargoClient,
ecrAccessKeyHelper: ecr.NewAccessKeyCredentialHelper(),
ecrPodIdentityHelper: ecr.NewPodIdentityCredentialHelper(),
gcpHelper: gcp.NewCredentialHelper(),
cfg: cfg,
}
}

Expand Down Expand Up @@ -151,11 +153,24 @@ func (k *kubernetesDatabase) Get(
}
}

if secret == nil {
if secret != nil {
if creds, err = k.secretToCreds(ctx, credType, secret); err != nil {
return creds, false, err
}
return creds, true, nil
}

if credType != TypeImage {
// If we are not not looking for image repository credentials, there's
// nothing left to try.
return creds, false, nil
}

if creds, err = k.secretToCreds(ctx, credType, secret); err != nil {
// If we get to here, we have not found any secret that we can pick apart
// in any way, but we can still try to resolve a username and password via
// workload identity.
if creds.Username, creds.Password, err =
k.ecrPodIdentityHelper.GetUsernameAndPassword(ctx, repoURL, namespace); err != nil {
return creds, false, err
}

Expand Down Expand Up @@ -242,7 +257,7 @@ func (k *kubernetesDatabase) secretToCreds(
var username, password string
var err error
// Try AWS
if username, password, err = k.ecrHelper.GetUsernameAndPassword(secret); err != nil {
if username, password, err = k.ecrAccessKeyHelper.GetUsernameAndPassword(ctx, secret); err != nil {
return Credentials{}, err
}
if username == "" { // Try GCP
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package ecr

import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/patrickmn/go-cache"
corev1 "k8s.io/api/core/v1"
)
Expand All @@ -21,43 +21,50 @@ const (
secretKey = "awsSecretAccessKey"
)

// CredentialHelper is an interface for components that can extract a username
// and password from a Secret containing an AWS region, access key id, and
// secret access key.
type CredentialHelper interface {
// AccessKeyCredentialHelper is an interface for components that can extract a
// username and password from a Secret containing an AWS region, access key id,
// and secret access key.
type AccessKeyCredentialHelper interface {
// GetUsernameAndPassword extracts username and password (a token that lives
// for 12 hours) from a Secret IF the Secret contains an AWS region, access
// key id, and secret access key. If the Secret does not contain ANY of these
// fields, this function will return empty strings and a nil error. If the
// Secret contains some but not all of these fields, this function will return
// an error. Implementations may cache the token for efficiency.
GetUsernameAndPassword(*corev1.Secret) (string, string, error)
GetUsernameAndPassword(context.Context, *corev1.Secret) (string, string, error)
}

type credentialHelper struct {
type accessKeyCredentialHelper struct {
tokenCache *cache.Cache

// The following behaviors are overridable for testing purposes:

getAuthTokenFn func(string, string, string) (string, error)
getAuthTokenFn func(
ctx context.Context,
region string,
accessKeyID string,
secretAccessKey string,
) (string, error)
}

// NewCredentialHelper returns an implementation of the CredentialHelper
// interface that utilizes a cache to avoid unnecessary calls to AWS.
func NewCredentialHelper() CredentialHelper {
return &credentialHelper{
// NewAccessKeyCredentialHelper returns an implementation of the
// AccessKeyCredentialHelper interface that utilizes a cache to avoid
// unnecessary calls to AWS.
func NewAccessKeyCredentialHelper() AccessKeyCredentialHelper {
a := &accessKeyCredentialHelper{
tokenCache: cache.New(
// Tokens live for 12 hours. We'll hang on to them for 10.
10*time.Hour, // Default ttl for each entry
time.Hour, // Cleanup interval
),
getAuthTokenFn: getAuthToken,
}
a.getAuthTokenFn = a.getAuthToken
return a
}

// GetUsernameAndPassword implements the CredentialHelper interface.
func (c *credentialHelper) GetUsernameAndPassword(
secret *corev1.Secret,
// GetUsernameAndPassword implements the AccessKeyCredentialHelper interface.
func (a *accessKeyCredentialHelper) GetUsernameAndPassword(
ctx context.Context, secret *corev1.Secret,
) (string, string, error) {
region := string(secret.Data[regionKey])
accessKeyID := string(secret.Data[idKey])
Expand All @@ -74,33 +81,33 @@ func (c *credentialHelper) GetUsernameAndPassword(
regionKey, idKey, secretKey,
)
}
return c.getUsernameAndPassword(region, accessKeyID, secretAccessKey)
return a.getUsernameAndPassword(ctx, region, accessKeyID, secretAccessKey)
}

func (c *credentialHelper) getUsernameAndPassword(
region, accessKeyID, secretAccessKey string,
func (a *accessKeyCredentialHelper) getUsernameAndPassword(
ctx context.Context, region, accessKeyID, secretAccessKey string,
) (string, string, error) {
cacheKey := tokenCacheKey(region, accessKeyID, secretAccessKey)
cacheKey := a.tokenCacheKey(region, accessKeyID, secretAccessKey)

if entry, exists := c.tokenCache.Get(cacheKey); exists {
if entry, exists := a.tokenCache.Get(cacheKey); exists {
return decodeAuthToken(entry.(string)) // nolint: forcetypeassert
}

encodedToken, err := c.getAuthTokenFn(region, accessKeyID, secretAccessKey)
encodedToken, err := a.getAuthTokenFn(ctx, region, accessKeyID, secretAccessKey)
if err != nil {
return "", "", fmt.Errorf("error getting ECR auth token: %w", err)
}

// Cache the encoded token
c.tokenCache.Set(cacheKey, encodedToken, cache.DefaultExpiration)
a.tokenCache.Set(cacheKey, encodedToken, cache.DefaultExpiration)

return decodeAuthToken(encodedToken)
}

// tokenCacheKey returns a cache key for an ECR authorization token. The key is
// a hash of the region, access key ID, and secret access key. Using a hash
// ensures that the secret access key is not stored in plaintext in the cache.
func tokenCacheKey(region, accessKeyID, secretAccessKey string) string {
func (a *accessKeyCredentialHelper) tokenCacheKey(region, accessKeyID, secretAccessKey string) string {
return fmt.Sprintf(
"%x",
sha256.Sum256([]byte(
Expand All @@ -111,18 +118,14 @@ func tokenCacheKey(region, accessKeyID, secretAccessKey string) string {

// getAuthToken returns an ECR authorization token by calling out to AWS with
// the provided credentials.
func getAuthToken(
region, accessKeyID, secretAccessKey string,
func (a *accessKeyCredentialHelper) getAuthToken(
ctx context.Context, region, accessKeyID, secretAccessKey string,
) (string, error) {
sess, err := session.NewSession(&aws.Config{
Region: aws.String(region),
Credentials: credentials.NewStaticCredentials(accessKeyID, secretAccessKey, ""),
svc := ecr.NewFromConfig(aws.Config{
Region: region,
Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
})
if err != nil {
return "", fmt.Errorf("error creating AWS session: %w", err)
}
svc := ecr.New(sess)
output, err := svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
output, err := svc.GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{})
if err != nil {
return "", fmt.Errorf("error getting ECR authorization token: %w", err)
}
Expand Down
Loading

0 comments on commit 3760ec3

Please sign in to comment.