Skip to content

Commit

Permalink
fix(aws): handle ECR repositories in different regions
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin Conner <kev.conner@getupcloud.com>
  • Loading branch information
knrc committed Feb 28, 2024
1 parent e1ea02c commit 712265c
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 6 deletions.
34 changes: 31 additions & 3 deletions pkg/fanal/image/registry/ecr/ecr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ecr
import (
"context"
"encoding/base64"
"regexp"
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
Expand All @@ -14,10 +15,9 @@ import (
"github.com/aquasecurity/trivy/pkg/fanal/types"
)

const ecrURL = "amazonaws.com"

type ecrAPI interface {
GetAuthorizationToken(ctx context.Context, params *ecr.GetAuthorizationTokenInput, optFns ...func(*ecr.Options)) (*ecr.GetAuthorizationTokenOutput, error)
Options() ecr.Options
}

type ECR struct {
Expand All @@ -37,7 +37,8 @@ func getSession(option types.RegistryOptions) (aws.Config, error) {
}

func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error {
if !strings.HasSuffix(domain, ecrURL) {
region := determineRegion(domain)
if region == nil {
return xerrors.Errorf("ECR : %w", types.InvalidURLPattern)
}

Expand All @@ -46,11 +47,34 @@ func (e *ECR) CheckOptions(domain string, option types.RegistryOptions) error {
return err
}

// override region with the value from the repository domain
cfg.Region = *region

svc := ecr.NewFromConfig(cfg)
e.Client = svc
return nil
}

// Endpoints take the form
// <registry-id>.dkr.ecr.<region>.amazonaws.com
// <registry-id>.dkr.ecr-fips.<region>.amazonaws.com
// <registry-id>.dkr.ecr.<region>.amazonaws.com.cn
// <registry-id>.dkr.ecr.<region>.sc2s.sgov.gov
// <registry-id>.dkr.ecr.<region>.c2s.ic.gov
// see
// - https://docs.aws.amazon.com/general/latest/gr/ecr.html
// - https://docs.amazonaws.cn/en_us/aws/latest/userguide/endpoints-arns.html
// - /usr/local/aws-cli/awscli/botocore/data/endpoints.json
var ecrEndpointMatch = regexp.MustCompile(`^[^.]+\.dkr\.ecr(?:-fips)?\.([^.]+)\.(?:amazonaws\.com(?:\.cn)?|sc2s\.sgov\.gov|c2s\.ic\.gov)$`)

func determineRegion(domain string) *string {
matches := ecrEndpointMatch.FindStringSubmatch(domain)
if matches != nil {
return &matches[1]
}
return nil
}

func (e *ECR) GetCredential(ctx context.Context) (username, password string, err error) {
input := &ecr.GetAuthorizationTokenInput{}
result, err := e.Client.GetAuthorizationToken(ctx, input)
Expand All @@ -70,3 +94,7 @@ func (e *ECR) GetCredential(ctx context.Context) (username, password string, err
}
return "", "", nil
}

func (e *ECR) Options() ecr.Options {
return e.Client.Options()
}
64 changes: 61 additions & 3 deletions pkg/fanal/image/registry/ecr/ecr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,65 @@ import (

func TestCheckOptions(t *testing.T) {
var tests = map[string]struct {
domain string
wantErr error
domain string
expectedRegion string
wantErr error
}{
"InvalidURL": {
domain: "alpine:3.9",
wantErr: types.InvalidURLPattern,
},
"NoOption": {
domain: "xxx.ecr.ap-northeast-1.amazonaws.com",
domain: "xxx.dkr.ecr.ap-northeast-1.amazonaws.com",
expectedRegion: "ap-northeast-1",
},
"region-1": {
domain: "xxx.dkr.ecr.region-1.amazonaws.com",
expectedRegion: "region-1",
},
"region-2": {
domain: "xxx.dkr.ecr.region-2.amazonaws.com",
expectedRegion: "region-2",
},
"fips-region-1": {
domain: "xxx.dkr.ecr-fips.fips-region.amazonaws.com",
expectedRegion: "fips-region",
},
"cn-region-1": {
domain: "xxx.dkr.ecr.region-1.amazonaws.com.cn",
expectedRegion: "region-1",
},
"cn-region-2": {
domain: "xxx.dkr.ecr.region-2.amazonaws.com.cn",
expectedRegion: "region-2",
},
"sc2s-region-1": {
domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov.gov",
expectedRegion: "sc2s-region",
},
"c2s-region-1": {
domain: "xxx.dkr.ecr.c2s-region.c2s.ic.gov",
expectedRegion: "c2s-region",
},
"invalid-ecr": {
domain: "xxx.dkrecr.region-1.amazonaws.com",
wantErr: types.InvalidURLPattern,
},
"invalid-fips": {
domain: "xxx.dkr.ecrfips.fips-region.amazonaws.com",
wantErr: types.InvalidURLPattern,
},
"invalid-cn": {
domain: "xxx.dkr.ecr.region-2.amazonaws.cn",
wantErr: types.InvalidURLPattern,
},
"invalid-sc2s": {
domain: "xxx.dkr.ecr.sc2s-region.sc2s.sgov",
wantErr: types.InvalidURLPattern,
},
"invalid-cs2": {
domain: "xxx.dkr.ecr.c2s-region.c2s.ic",
wantErr: types.InvalidURLPattern,
},
}

Expand All @@ -35,6 +85,10 @@ func TestCheckOptions(t *testing.T) {
}
continue
}

if a.Options().Region != v.expectedRegion {
t.Errorf("[%s]\nexpected region %v\nactual : %v", testname, v.expectedRegion, a.Options().Region)
}
}
}

Expand All @@ -46,6 +100,10 @@ func (m mockedECR) GetAuthorizationToken(ctx context.Context, params *ecr.GetAut
return &m.Resp, nil
}

func (m mockedECR) Options() ecr.Options {
return ecr.Options{}
}

func TestECRGetCredential(t *testing.T) {
cases := []struct {
Resp ecr.GetAuthorizationTokenOutput
Expand Down

0 comments on commit 712265c

Please sign in to comment.