From fd5a039f7b09ceeb45c66bd2be0abbe7adcd00e3 Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Tue, 1 Jun 2021 20:31:13 +0200 Subject: [PATCH 01/10] Log in to AWS ECR automatically using the cluster-provided credentials. As discussed in fluxcd/image-reflector-controller#139 if the environment variable USE_ECR is set, logs in to the AWS Elastic Container Registry in the image URL by fetching a temporary token from AWS. Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 77 +++++++++++++++++++++++ go.mod | 1 + go.sum | 6 ++ 3 files changed, 84 insertions(+) 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= From a56624b96dd7fd641342f8bfa80c4cf4337d8d32 Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Thu, 24 Jun 2021 19:22:20 +0200 Subject: [PATCH 02/10] Enable AWS ECR login with flag instead of env variable (... and quote a dot in the ECR URL pattern regexp) Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 6 +++--- main.go | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 7ccec452..5356cb52 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -27,7 +27,6 @@ import ( "fmt" "net/http" "net/url" - "os" "regexp" "strings" "time" @@ -81,6 +80,7 @@ type ImageRepositoryReconciler struct { DatabaseWriter DatabaseReader } + UseAwsEcr bool } type ImageRepositoryReconcilerOptions struct { @@ -191,7 +191,7 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion, awsPartition, ecrRepository, tag string, err error) { err = nil - registryUrlPartRe := regexp.MustCompile("([0-9+]*).dkr.ecr.([^/.]*).(amazonaws.com[.cn]*)/([^:]+):?(.*)") + 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") @@ -271,7 +271,7 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1 } if accountId, awsEcrRegion, _, _, _, err := parseAwsImageURL(imageRepo.Spec.Image); err == nil { - if _, present := os.LookupEnv("USE_ECR"); present { + if r.UseAwsEcr { logr.FromContext(ctx).Info("Logging in to AWS ECR for " + imageRepo.Spec.Image) authConfig, err := getAwsECRLoginAuth(accountId, awsEcrRegion) diff --git a/main.go b/main.go index 511d8eee..d0d9f2be 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func main() { storagePath string storageValueLogFileSize int64 concurrent int + useAwsEcr bool ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -79,6 +80,8 @@ func main() { flag.StringVar(&storagePath, "storage-path", "/data", "Where to store the persistent database of image metadata") flag.Int64Var(&storageValueLogFileSize, "storage-value-log-file-size", 1<<28, "Set the database's memory mapped value log file size in bytes. Effective memory usage is about two times this size.") flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.") + flag.BoolVar(&useAwsEcr, "use-aws-ecr", false, "Log in to AWS Elastic Container Registry with IAM") + clientOptions.BindFlags(flag.CommandLine) logOptions.BindFlags(flag.CommandLine) leaderElectionOptions.BindFlags(flag.CommandLine) @@ -144,6 +147,7 @@ func main() { ExternalEventRecorder: eventRecorder, MetricsRecorder: metricsRecorder, Database: db, + UseAwsEcr: useAwsEcr, }).SetupWithManager(mgr, controllers.ImageRepositoryReconcilerOptions{ MaxConcurrentReconciles: concurrent, }); err != nil { From dd9fbbf9a7cba1d98920daf97f7c23b43c1160cb Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Thu, 24 Jun 2021 20:08:45 +0200 Subject: [PATCH 03/10] removed (as of now) unused return values Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 5356cb52..33bb7193 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -189,7 +189,7 @@ 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) { +func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion string, err error) { err = nil registryUrlPartRe := regexp.MustCompile(`([0-9+]*).dkr.ecr.([^/.]*)\.(amazonaws\.com[.cn]*)/([^:]+):?(.*)`) registryUrlParts := registryUrlPartRe.FindAllStringSubmatch(imageUrl, -1) @@ -199,13 +199,6 @@ func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion, awsPartition, e } 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 } @@ -270,7 +263,7 @@ 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 accountId, awsEcrRegion, err := parseAwsImageURL(imageRepo.Spec.Image); err == nil { if r.UseAwsEcr { logr.FromContext(ctx).Info("Logging in to AWS ECR for " + imageRepo.Spec.Image) From 22f2317404ee0a8bde3d1ec449aef0266708c395 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Mon, 28 Jun 2021 12:29:50 +0100 Subject: [PATCH 04/10] Bump API version to v0.11.0 Signed-off-by: Michael Bridgen --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 35a4140b..2f4ccdf7 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/fluxcd/image-reflector-controller/api => ./api require ( github.com/Masterminds/semver/v3 v3.1.1 github.com/dgraph-io/badger/v3 v3.2103.0 - github.com/fluxcd/image-reflector-controller/api v0.10.0 + github.com/fluxcd/image-reflector-controller/api v0.11.0 github.com/fluxcd/pkg/apis/meta v0.10.0 github.com/fluxcd/pkg/runtime v0.12.0 github.com/fluxcd/pkg/version v0.1.0 From 4b48cf19aead2e9d9415aefbe87006bccf22e0d5 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Mon, 28 Jun 2021 12:46:18 +0100 Subject: [PATCH 05/10] Write changelog entry for v0.11.0 Signed-off-by: Michael Bridgen --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e88ea60..b0efdf5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.11.0 + +**Release date:** 2021-06-28 + +This prerelease promotes the API version from `v1alpha2` to `v1beta1`. + +:warning: With regard to the API version, no action is necessary at +present, as Kubernetes will automatically convert between `v1alpha2` +and `v1beta1` APIs. + +You may wish to migrate `v1alpha2` YAML files to `v1beta1`, in +preparation for `v1alpha2` being deprecated (eventually; there is no +date set at the time of writing). This is simply a case of setting the +`apiVersion` field value: + + `apiVersion: image.toolkit.fluxcd.io/v1beta1` + +Improvements: +* Let people set the number of controller workers with a flag + [#153](https://github.com/fluxcd/image-reflector-controller/pull/153) + ## 0.10.0 **Release date:** 2021-06-10 From 5c94f3d811f1f68512ac3c83f487ca2e1d81c5c0 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Mon, 28 Jun 2021 12:47:29 +0100 Subject: [PATCH 06/10] Bump controller version used in config Signed-off-by: Michael Bridgen --- config/manager/kustomization.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 25a0b739..6f2b79dd 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ resources: images: - name: fluxcd/image-reflector-controller newName: fluxcd/image-reflector-controller - newTag: v0.10.0 + newTag: v0.11.0 From 5da11523f83065d59fd68ff39040b131f72e05d5 Mon Sep 17 00:00:00 2001 From: Michael Bridgen Date: Wed, 14 Jul 2021 09:18:41 +0100 Subject: [PATCH 07/10] Correct apiVersion of v1beta1 samples Signed-off-by: Michael Bridgen --- config/samples/image_v1beta1_imagepolicy.yaml | 2 +- config/samples/image_v1beta1_imagerepository.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/samples/image_v1beta1_imagepolicy.yaml b/config/samples/image_v1beta1_imagepolicy.yaml index 0d1bdc8d..2c0a2521 100644 --- a/config/samples/image_v1beta1_imagepolicy.yaml +++ b/config/samples/image_v1beta1_imagepolicy.yaml @@ -1,4 +1,4 @@ -apiVersion: image.toolkit.fluxcd.io/v1alpha2 +apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImagePolicy metadata: name: podinfo diff --git a/config/samples/image_v1beta1_imagerepository.yaml b/config/samples/image_v1beta1_imagerepository.yaml index 850fb7a8..4694922d 100644 --- a/config/samples/image_v1beta1_imagerepository.yaml +++ b/config/samples/image_v1beta1_imagerepository.yaml @@ -1,4 +1,4 @@ -apiVersion: image.toolkit.fluxcd.io/v1alpha2 +apiVersion: image.toolkit.fluxcd.io/v1beta1 kind: ImageRepository metadata: name: podinfo From a6e1be84f8165787e3f0b081bfe3eb6489172e74 Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Tue, 1 Jun 2021 20:31:13 +0200 Subject: [PATCH 08/10] Log in to AWS ECR automatically using the cluster-provided credentials. As discussed in fluxcd/image-reflector-controller#139 if the environment variable USE_ECR is set, logs in to the AWS Elastic Container Registry in the image URL by fetching a temporary token from AWS. Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 77 +++++++++++++++++++++++ go.mod | 1 + go.sum | 6 ++ 3 files changed, 84 insertions(+) diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 8f209dfd..7ccec452 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" @@ -45,6 +48,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller" "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" @@ -182,6 +189,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) @@ -215,6 +270,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 2f4ccdf7..13157fe2 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.2103.0 github.com/fluxcd/image-reflector-controller/api v0.11.0 github.com/fluxcd/pkg/apis/meta v0.10.0 diff --git a/go.sum b/go.sum index b779a88f..e380a4e3 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,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= @@ -165,6 +167,8 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8 github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= 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/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -283,6 +287,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= From 801d106a42d16f529e4ed6794b5ef9a70f9c4d88 Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Thu, 24 Jun 2021 19:22:20 +0200 Subject: [PATCH 09/10] Enable AWS ECR login with flag instead of env variable (... and quote a dot in the ECR URL pattern regexp) Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 6 +++--- main.go | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 7ccec452..5356cb52 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -27,7 +27,6 @@ import ( "fmt" "net/http" "net/url" - "os" "regexp" "strings" "time" @@ -81,6 +80,7 @@ type ImageRepositoryReconciler struct { DatabaseWriter DatabaseReader } + UseAwsEcr bool } type ImageRepositoryReconcilerOptions struct { @@ -191,7 +191,7 @@ func (r *ImageRepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Requ func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion, awsPartition, ecrRepository, tag string, err error) { err = nil - registryUrlPartRe := regexp.MustCompile("([0-9+]*).dkr.ecr.([^/.]*).(amazonaws.com[.cn]*)/([^:]+):?(.*)") + 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") @@ -271,7 +271,7 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1 } if accountId, awsEcrRegion, _, _, _, err := parseAwsImageURL(imageRepo.Spec.Image); err == nil { - if _, present := os.LookupEnv("USE_ECR"); present { + if r.UseAwsEcr { logr.FromContext(ctx).Info("Logging in to AWS ECR for " + imageRepo.Spec.Image) authConfig, err := getAwsECRLoginAuth(accountId, awsEcrRegion) diff --git a/main.go b/main.go index 511d8eee..d0d9f2be 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func main() { storagePath string storageValueLogFileSize int64 concurrent int + useAwsEcr bool ) flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") @@ -79,6 +80,8 @@ func main() { flag.StringVar(&storagePath, "storage-path", "/data", "Where to store the persistent database of image metadata") flag.Int64Var(&storageValueLogFileSize, "storage-value-log-file-size", 1<<28, "Set the database's memory mapped value log file size in bytes. Effective memory usage is about two times this size.") flag.IntVar(&concurrent, "concurrent", 4, "The number of concurrent resource reconciles.") + flag.BoolVar(&useAwsEcr, "use-aws-ecr", false, "Log in to AWS Elastic Container Registry with IAM") + clientOptions.BindFlags(flag.CommandLine) logOptions.BindFlags(flag.CommandLine) leaderElectionOptions.BindFlags(flag.CommandLine) @@ -144,6 +147,7 @@ func main() { ExternalEventRecorder: eventRecorder, MetricsRecorder: metricsRecorder, Database: db, + UseAwsEcr: useAwsEcr, }).SetupWithManager(mgr, controllers.ImageRepositoryReconcilerOptions{ MaxConcurrentReconciles: concurrent, }); err != nil { From 782d111dddf6aab8bfe0e67b3e429e840a37b667 Mon Sep 17 00:00:00 2001 From: Martin Emrich Date: Thu, 24 Jun 2021 20:08:45 +0200 Subject: [PATCH 10/10] removed (as of now) unused return values Signed-off-by: Martin Emrich --- controllers/imagerepository_controller.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 5356cb52..33bb7193 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -189,7 +189,7 @@ 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) { +func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion string, err error) { err = nil registryUrlPartRe := regexp.MustCompile(`([0-9+]*).dkr.ecr.([^/.]*)\.(amazonaws\.com[.cn]*)/([^:]+):?(.*)`) registryUrlParts := registryUrlPartRe.FindAllStringSubmatch(imageUrl, -1) @@ -199,13 +199,6 @@ func parseAwsImageURL(imageUrl string) (accountId, awsEcrRegion, awsPartition, e } 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 } @@ -270,7 +263,7 @@ 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 accountId, awsEcrRegion, err := parseAwsImageURL(imageRepo.Spec.Image); err == nil { if r.UseAwsEcr { logr.FromContext(ctx).Info("Logging in to AWS ECR for " + imageRepo.Spec.Image)