-
Notifications
You must be signed in to change notification settings - Fork 288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor registry tester to support aws config #6761
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,8 +5,10 @@ | |
"fmt" | ||
"io" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/aws/aws-sdk-go-v2/aws" | ||
"github.com/aws/aws-sdk-go-v2/config" | ||
"github.com/aws/aws-sdk-go-v2/credentials" | ||
"github.com/aws/aws-sdk-go-v2/service/ecr" | ||
|
@@ -22,28 +24,33 @@ | |
"us-east-2": "TODO.dkr.ecr.us-east-2.amazonaws.com", | ||
} | ||
|
||
// TestRegistryAccess test if the packageControllerClient has valid credential to access registry. | ||
func TestRegistryAccess(ctx context.Context, accessKey, secret, registry, region string) error { | ||
cfg, err := config.LoadDefaultConfig(ctx, | ||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secret, "")), | ||
config.WithRegion(region), | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
// RegistryAccessTester test if AWS credentials has valid permission to access an ECR registry. | ||
type RegistryAccessTester interface { | ||
Test(ctx context.Context, accessKey, secret, region, awsConfig, registry string) error | ||
} | ||
|
||
ecrClient := ecr.NewFromConfig(cfg) | ||
out, err := ecrClient.GetAuthorizationToken(context.Background(), &ecr.GetAuthorizationTokenInput{}) | ||
// DefaultRegistryAccessTester the default implementation of RegistryAccessTester. | ||
type DefaultRegistryAccessTester struct{} | ||
|
||
// Test if the AWS static credential or sharedConfig has valid permission to access an ECR registry. | ||
func (r *DefaultRegistryAccessTester) Test(ctx context.Context, accessKey, secret, region, awsConfig, registry string) (err error) { | ||
authTokenProvider := &DefaultRegistryAuthTokenProvider{} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what do you think about making this part of the struct? then it's a little easier to do dependency injection for testing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The disadvantage is that the field has to be specified clearly when creating the struct. Maybe the advantage doesn't worth the effort |
||
|
||
var authToken string | ||
if len(awsConfig) > 0 { | ||
authToken, err = authTokenProvider.GetTokenByAWSConfig(ctx, awsConfig) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to write the order of precedence here in our docs? Or have we already established that somewhere? Not a blocker, maybe just something to write a ticket for. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This order is somehow determined by credentialproviderpackages https://github.com/aws/eks-anywhere-packages/blob/main/credentialproviderpackage/pkg/awscred/awscred.go#L48C25-L48C25 |
||
} else { | ||
authToken, err = authTokenProvider.GetTokenByAWSKeySecret(ctx, accessKey, secret, region) | ||
} | ||
if err != nil { | ||
return err | ||
} | ||
authToken := out.AuthorizationData[0].AuthorizationToken | ||
|
||
return TestRegistryWithAuthToken(*authToken, registry, http.DefaultClient.Do) | ||
return TestRegistryWithAuthToken(authToken, registry, http.DefaultClient.Do) | ||
} | ||
|
||
// TestRegistryWithAuthToken test if the registry can be acccessed with auth token. | ||
func TestRegistryWithAuthToken(authToken, registry string, getResponse func(req *http.Request) (*http.Response, error)) error { | ||
func TestRegistryWithAuthToken(authToken, registry string, do Do) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is "Do" a golang convention or just a convenient interface name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's a method of http.Client, but I used it here for alias of the function type |
||
manifestPath := "/v2/eks-anywhere-packages/manifests/latest" | ||
|
||
req, err := http.NewRequest("GET", "https://"+registry+manifestPath, nil) | ||
|
@@ -52,7 +59,7 @@ | |
} | ||
req.Header.Add("Authorization", "Basic "+authToken) | ||
|
||
resp2, err := getResponse(req) | ||
resp2, err := do(req) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -76,3 +83,97 @@ | |
} | ||
return prodRegionalECRMap[region] | ||
} | ||
|
||
// RegistryAuthTokenProvider provides auth token for registry access. | ||
type RegistryAuthTokenProvider interface { | ||
GetTokenByAWSConfig(ctx context.Context, awsConfig string) (string, error) | ||
GetTokenByAWSKeySecret(ctx context.Context, key, secret, region string) (string, error) | ||
} | ||
|
||
// DefaultRegistryAuthTokenProvider provides auth token for AWS ECR registry access. | ||
type DefaultRegistryAuthTokenProvider struct{} | ||
|
||
// GetTokenByAWSConfig get auth token by AWS config. | ||
func (d *DefaultRegistryAuthTokenProvider) GetTokenByAWSConfig(ctx context.Context, awsConfig string) (string, error) { | ||
cfg, err := ParseAWSConfig(ctx, awsConfig) | ||
if err != nil { | ||
return "", err | ||
} | ||
return getAuthorizationToken(*cfg) | ||
} | ||
|
||
// ParseAWSConfig parse AWS config from string. | ||
func ParseAWSConfig(ctx context.Context, awsConfig string) (*aws.Config, error) { | ||
file, err := os.CreateTemp("", "eksa-temp-aws-config-*") | ||
if err != nil { | ||
return nil, err | ||
} | ||
if _, err := file.Write([]byte(awsConfig)); err != nil { | ||
return nil, err | ||
} | ||
defer os.Remove(file.Name()) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cfg, err := config.LoadDefaultConfig(ctx, | ||
config.WithSharedConfigFiles([]string{file.Name()}), | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &cfg, nil | ||
} | ||
|
||
// GetAWSConfigFromKeySecret get AWS config from key, secret and region. | ||
func GetAWSConfigFromKeySecret(ctx context.Context, key, secret, region string) (*aws.Config, error) { | ||
cfg, err := config.LoadDefaultConfig(ctx, | ||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(key, secret, "")), | ||
config.WithRegion(region), | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &cfg, nil | ||
} | ||
|
||
// GetTokenByAWSKeySecret get auth token by AWS key and secret. | ||
func (d *DefaultRegistryAuthTokenProvider) GetTokenByAWSKeySecret(ctx context.Context, key, secret, region string) (string, error) { | ||
cfg, err := GetAWSConfigFromKeySecret(ctx, key, secret, region) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return getAuthorizationToken(*cfg) | ||
} | ||
|
||
func getAuthorizationToken(cfg aws.Config) (string, error) { | ||
ecrClient := ecr.NewFromConfig(cfg) | ||
out, err := ecrClient.GetAuthorizationToken(context.Background(), &ecr.GetAuthorizationTokenInput{}) | ||
if err != nil { | ||
return "", fmt.Errorf("ecrClient cannot get authorization token: %w", err) | ||
} | ||
authToken := out.AuthorizationData[0].AuthorizationToken | ||
return *authToken, nil | ||
} | ||
|
||
// Do is a function type that takes a http request and returns a http response. | ||
type Do func(req *http.Request) (*http.Response, error) | ||
|
||
// TestRegistryAccessWithAWSConfig test if the AWS config has valid permission to access container registry. | ||
func TestRegistryAccessWithAWSConfig(ctx context.Context, awsConfig, registry string, tokenProvider RegistryAuthTokenProvider, do Do) error { | ||
token, err := tokenProvider.GetTokenByAWSConfig(ctx, awsConfig) | ||
if err != nil { | ||
return err | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I missed out codecov was quite low. Could we add some tests to get up to 80% coverage? |
||
return TestRegistryWithAuthToken(token, registry, do) | ||
} | ||
|
||
// TestRegistryAccessWithAWSKeySecret test if the AWS key and secret has valid permission to access container registry. | ||
func TestRegistryAccessWithAWSKeySecret(ctx context.Context, key, secret, region, registry string, tokenProvider RegistryAuthTokenProvider, do Do) error { | ||
token, err := tokenProvider.GetTokenByAWSKeySecret(ctx, key, secret, region) | ||
if err != nil { | ||
return err | ||
} | ||
return TestRegistryWithAuthToken(token, registry, do) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just confirming, this is only used in tests?