diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index aefc93a3..f7e47008 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -21,11 +21,14 @@ import ( "context" "crypto/tls" "crypto/x509" + "encoding/base64" "encoding/json" "errors" "fmt" "net/http" "net/url" + "os" + "regexp" "strings" "time" @@ -44,6 +47,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" + "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/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/metrics" @@ -177,6 +184,54 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{RequeueAfter: when}, nil } +func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion, awsPartition, ecrRepository, tag string, err error) { + err = nil + registryUrlPartRe := regexp.MustCompile("([0-9+]*).dkr.ecr.([^/.]*).(amazonaws.com[.cn]*)/([^:]+):?(.*)") + registryUrlParts := registryUrlPartRe.FindAllStringSubmatch(imageUrl, -1) + if len(registryUrlParts) < 1 { + err = errors.New("imageUrl does not match AWS elastic container registry URL pattern") + return + } + accountId = registryUrlParts[0][1] + awsEcrRegion = registryUrlParts[0][2] + awsPartition = registryUrlParts[0][3] + ecrRepository = registryUrlParts[0][4] + if len(registryUrlParts[0]) <= 4 { + tag = "" + return + } + tag = registryUrlParts[0][5] + return +} + +// TODO: Still missing from Flux 1: +// Caching of tokens (one per account/region pair), this fetches a fresh token every time +// handling of expiry +// Back-Off in case of errors +// Possibly: special behaviour for non-global partitions (China, GovCloud) +func getAwsECRLoginAuth(accountId, awsEcrRegion string) (authConfig authn.AuthConfig, err error) { + accountIDs := []string{accountId} + ecrService := ecr.New(session.Must(session.NewSession(&aws.Config{Region: aws.String(awsEcrRegion)}))) + ecrToken, err := ecrService.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{ + RegistryIds: aws.StringSlice(accountIDs), + }) + if err != nil { + return + } + + token, err := base64.StdEncoding.DecodeString(*ecrToken.AuthorizationData[0].AuthorizationToken) + if err != nil { + return + } + + tokenSplit := strings.Split(string(token), ":") + authConfig = authn.AuthConfig{ + Username: tokenSplit[0], + Password: tokenSplit[1], + } + return +} + func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1.ImageRepository, ref name.Reference) error { timeout := imageRepo.GetTimeout() ctx, cancel := context.WithTimeout(ctx, timeout) @@ -210,6 +265,28 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1 options = append(options, remote.WithAuth(auth)) } + if accountId, awsEcrRegion, _, _, _, err := parseAwsImageURL(imageRepo.Spec.Image); err == nil { + if _, present := os.LookupEnv("USE_ECR"); present { + logr.FromContext(ctx).Info("Logging in to AWS ECR for " + imageRepo.Spec.Image) + + authConfig, err := getAwsECRLoginAuth(accountId, awsEcrRegion) + if err != nil { + imagev1.SetImageRepositoryReadiness( + imageRepo, + metav1.ConditionFalse, + meta.ReconciliationFailedReason, + err.Error(), + ) + return err + } + + auth := authn.FromConfig(authConfig) + options = append(options, remote.WithAuth(auth)) + } else { + logr.FromContext(ctx).Info("AWS ECR authentication is not enabled, to enable, set USE_ECR environment variable") + } + } + if imageRepo.Spec.CertSecretRef != nil { var certSecret corev1.Secret if imageRepo.Spec.SecretRef != nil && imageRepo.Spec.SecretRef.Name == imageRepo.Spec.CertSecretRef.Name { diff --git a/go.mod b/go.mod index 695e3d86..6421129e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ replace github.com/fluxcd/image-reflector-controller/api => ./api require ( github.com/Masterminds/semver/v3 v3.1.1 + github.com/aws/aws-sdk-go v1.33.18 github.com/dgraph-io/badger/v3 v3.2011.1 github.com/fluxcd/image-reflector-controller/api v0.9.1 github.com/fluxcd/pkg/apis/meta v0.9.0 diff --git a/go.sum b/go.sum index 0ca0de26..0d942d53 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.33.18 h1:Ccy1SV2SsgJU3rfrD+SOhQ0jvuzfrFuja/oKI86ruPw= +github.com/aws/aws-sdk-go v1.33.18/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -164,6 +166,8 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -276,6 +280,8 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=