diff --git a/Gopkg.lock b/Gopkg.lock index 881eaf9d4..021e6b352 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -41,20 +41,45 @@ version = "v1.4.0" [[projects]] - digest = "1:aec6720081aad903219cf427bb9acdefc9c440743add4d02df38ff387c69886a" - name = "github.com/Masterminds/sprig" - packages = ["."] + digest = "1:2ab85e1c0578804ec09602b075bb7637f7cfb6892d827d39707b58f49eec613a" + name = "github.com/aws/aws-sdk-go" + packages = [ + "aws", + "aws/awserr", + "aws/awsutil", + "aws/client", + "aws/client/metadata", + "aws/corehandlers", + "aws/credentials", + "aws/credentials/ec2rolecreds", + "aws/credentials/endpointcreds", + "aws/credentials/processcreds", + "aws/credentials/stscreds", + "aws/csm", + "aws/defaults", + "aws/ec2metadata", + "aws/endpoints", + "aws/request", + "aws/session", + "aws/signer/v4", + "internal/ini", + "internal/sdkio", + "internal/sdkrand", + "internal/sdkuri", + "internal/shareddefaults", + "private/protocol", + "private/protocol/json/jsonutil", + "private/protocol/jsonrpc", + "private/protocol/query", + "private/protocol/query/queryutil", + "private/protocol/rest", + "private/protocol/xml/xmlutil", + "service/ecr", + "service/sts", + ] pruneopts = "" - revision = "15f9564e7e9cf0da02a48e0d25f12a7b83559aa6" - version = "v2.16.0" - -[[projects]] - digest = "1:24cff84fefa06791f78f9bc6e05c2a3a90710c81ae6b76225280daf33b267d36" - name = "github.com/aokoli/goutils" - packages = ["."] - pruneopts = "" - revision = "3391d3790d23d03408670993e957e8f408993c34" - version = "v1.0.1" + revision = "3991042237b45cf58c9d5f34295942d5533c28c6" + version = "v1.16.11" [[projects]] branch = "master" @@ -272,14 +297,6 @@ pruneopts = "" revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" -[[projects]] - digest = "1:5247b135b5492aa232a731acdcb52b08f32b874cb398f21ab460396eadbe866b" - name = "github.com/google/uuid" - packages = ["."] - pruneopts = "" - revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494" - version = "v1.0.0" - [[projects]] digest = "1:2a131706ff80636629ab6373f2944569b8252ecc018cda8040931b05d32e3c16" name = "github.com/googleapis/gnostic" @@ -362,14 +379,6 @@ pruneopts = "" revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3" -[[projects]] - digest = "1:35979179658d20a73693589e67bdc3baf4dc0ef9f524b1dfd3cc55fb5f6ae384" - name = "github.com/huandu/xstrings" - packages = ["."] - pruneopts = "" - revision = "f02667b379e2fb5916c3cda2cf31e0eb885d79f8" - version = "v1.2.0" - [[projects]] digest = "1:23bc0b496ba341c6e3ba24d6358ff4a40a704d9eb5f9a3bd8e8fbd57ad869013" name = "github.com/imdario/mergo" @@ -386,6 +395,13 @@ revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" +[[projects]] + digest = "1:13fe471d0ed891e8544eddfeeb0471fd3c9f2015609a1c000aefdedf52a19d40" + name = "github.com/jmespath/go-jmespath" + packages = ["."] + pruneopts = "" + revision = "c2b33e84" + [[projects]] digest = "1:31c6f3c4f1e15fcc24fcfc9f5f24603ff3963c56d6fa162116493b4025fb6acc" name = "github.com/json-iterator/go" @@ -1027,7 +1043,26 @@ [[projects]] digest = "1:ec6534bd79f79351514d62ff48f4f547aeb53ea0c9ee45ca5594fbdfbd2fc258" name = "k8s.io/helm" - packages = [] + packages = [ + "pkg/chartutil", + "pkg/getter", + "pkg/helm", + "pkg/helm/environment", + "pkg/helm/helmpath", + "pkg/ignore", + "pkg/plugin", + "pkg/proto/hapi/chart", + "pkg/proto/hapi/release", + "pkg/proto/hapi/services", + "pkg/proto/hapi/version", + "pkg/provenance", + "pkg/repo", + "pkg/storage/driver", + "pkg/sympath", + "pkg/tlsutil", + "pkg/urlutil", + "pkg/version", + ] pruneopts = "" revision = "9ad53aac42165a5fadc6c87be0dea6b115f93090" version = "v2.10.0" @@ -1127,6 +1162,8 @@ analyzer-version = 1 input-imports = [ "github.com/Masterminds/semver", + "github.com/aws/aws-sdk-go/aws/session", + "github.com/aws/aws-sdk-go/service/ecr", "github.com/bradfitz/gomemcache/memcache", "github.com/docker/distribution", "github.com/docker/distribution/manifest/manifestlist", diff --git a/registry/aws.go b/registry/aws.go new file mode 100644 index 000000000..bf5ad1143 --- /dev/null +++ b/registry/aws.go @@ -0,0 +1,95 @@ +package registry + +// References: +// - https://github.com/bzon/ecr-k8s-secret-creator +// - https://github.com/kubernetes/kubernetes/blob/master/pkg/credentialprovider/aws/aws_credentials.go +// - https://github.com/weaveworks/flux/pull/1455 + +import ( + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/go-kit/kit/log" +) + +const ( + // For recognising ECR hosts + ecrHostSuffix = ".amazonaws.com" + // How long AWS tokens remain valid + tokenValid = 12 * time.Hour +) + +type AWSRegistryConfig struct { + Region string + RegistryIDs []string +} + +func ImageCredsWithAWS(lookup func() ImageCreds, logger log.Logger, config AWSRegistryConfig) (func() ImageCreds, error) { + awsCreds := NoCredentials() + var credsExpire time.Time + + refresh := func(now time.Time) error { + var err error + awsCreds, err = fetchAWSCreds(config) + if err != nil { + // bump this along so we don't spam the log + credsExpire = now.Add(time.Hour) + return err + } + credsExpire = now.Add(tokenValid) + return nil + } + + // pre-flight check + if err := refresh(time.Now()); err != nil { + return nil, err + } + + return func() ImageCreds { + imageCreds := lookup() + + now := time.Now() + if now.After(credsExpire) { + if err := refresh(now); err != nil { + logger.Log("warning", "AWS token not refreshed", "err", err) + } + } + + for name, creds := range imageCreds { + if strings.HasSuffix(name.Domain, ecrHostSuffix) { + newCreds := NoCredentials() + newCreds.Merge(awsCreds) + newCreds.Merge(creds) + imageCreds[name] = newCreds + } + } + return imageCreds + }, nil +} + +func fetchAWSCreds(config AWSRegistryConfig) (Credentials, error) { + sess := session.Must(session.NewSession(&aws.Config{Region: &config.Region})) + svc := ecr.New(sess) + ecrToken, err := svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{ + RegistryIds: aws.StringSlice(config.RegistryIDs), + }) + if err != nil { + return Credentials{}, err + } + auths := make(map[string]creds) + for _, v := range ecrToken.AuthorizationData { + // Remove the https prefix + host := strings.TrimPrefix(*v.ProxyEndpoint, "https://") + creds, err := parseAuth(*v.AuthorizationToken) + if err != nil { + return Credentials{}, err + } + creds.provenance = "AWS API" + creds.registry = host + auths[host] = creds + } + return Credentials{m: auths}, nil +} diff --git a/registry/credentials.go b/registry/credentials.go index 07b133df1..278568bca 100644 --- a/registry/credentials.go +++ b/registry/credentials.go @@ -36,6 +36,22 @@ func NoCredentials() Credentials { } } +func parseAuth(auth string) (creds, error) { + decodedAuth, err := base64.StdEncoding.DecodeString(auth) + if err != nil { + return creds{}, err + } + authParts := strings.SplitN(string(decodedAuth), ":", 2) + if len(authParts) != 2 { + return creds{}, + fmt.Errorf("decoded credential has wrong number of fields (expected 2, got %d)", len(authParts)) + } + return creds{ + username: authParts[0], + password: authParts[1], + }, nil +} + func ParseCredentials(from string, b []byte) (Credentials, error) { var config struct { Auths map[string]struct { @@ -53,15 +69,10 @@ func ParseCredentials(from string, b []byte) (Credentials, error) { } m := map[string]creds{} for host, entry := range config.Auths { - decodedAuth, err := base64.StdEncoding.DecodeString(entry.Auth) + creds, err := parseAuth(entry.Auth) if err != nil { return Credentials{}, err } - authParts := strings.SplitN(string(decodedAuth), ":", 2) - if len(authParts) != 2 { - return Credentials{}, - fmt.Errorf("decoded credential for %v has wrong number of fields (expected 2, got %d)", host, len(authParts)) - } // Some users were passing in credentials in the form of // http://docker.io and http://docker.io/v1/, etc. @@ -87,12 +98,9 @@ func ParseCredentials(from string, b []byte) (Credentials, error) { } host = u.Host - m[host] = creds{ - registry: host, - provenance: from, - username: authParts[0], - password: authParts[1], - } + creds.registry = host + creds.provenance = from + m[host] = creds } return Credentials{m: m}, nil }