/
ecr.go
139 lines (119 loc) · 3.57 KB
/
ecr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package ecr
import (
"context"
"encoding/base64"
"errors"
"fmt"
"regexp"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/smithy-go/logging"
"github.com/docker/docker/api/types/registry"
"github.com/dominodatalab/hephaestus/pkg/controller/support/credentials/cloudauth"
"github.com/go-logr/logr"
)
type ecrClient interface {
GetAuthorizationToken(
ctx context.Context,
params *ecr.GetAuthorizationTokenInput,
optFns ...func(*ecr.Options),
) (*ecr.GetAuthorizationTokenOutput, error)
}
type awsLogger struct {
logr.Logger
}
func (l awsLogger) Logf(classification logging.Classification, format string, v ...interface{}) {
var level int
if classification == logging.Debug {
level = 1
}
l.V(level).Info(fmt.Sprintf(format, v...))
}
var (
awsConfig aws.Config
newClient = newECRClient
urlRegex = regexp.MustCompile(
//nolint:lll
`^(?P<aws_account_id>[a-zA-Z\d][a-zA-Z\d-_]*)\.dkr\.ecr(-fips)?\.(?P<region>[a-zA-Z\d][a-zA-Z\d-_]*)\.amazonaws\.com(\.cn)?`,
)
urlRegexRegionIndex = urlRegex.SubexpIndex("region")
)
func Register(ctx context.Context, logger logr.Logger, registry *cloudauth.Registry) error {
clientMode := aws.LogRequest | aws.LogResponse | aws.LogRetries
clientLogger := &awsLogger{logger}
var err error
awsConfig, err = config.LoadDefaultConfig(
ctx,
config.WithEC2IMDSRegion(func(o *config.UseEC2IMDSRegion) {
o.Client = imds.New(imds.Options{
ClientLogMode: clientMode,
Logger: clientLogger,
})
}),
config.WithLogger(clientLogger),
config.WithClientLogMode(clientMode),
)
if err != nil {
logger.Info("ECR not registered", "error", err)
return nil
}
registry.Register(urlRegex, authenticate)
logger.Info("ECR registered")
return nil
}
func newECRClient(region string) ecrClient {
c := awsConfig
c.Region = region
return ecr.NewFromConfig(c)
}
func authenticate(ctx context.Context, logger logr.Logger, url string) (*registry.AuthConfig, error) {
logger.WithName("ecr-auth-provider")
match := urlRegex.FindStringSubmatch(url)
if len(match) == 0 {
err := fmt.Errorf("ECR URL is invalid: %q should match pattern %v", url, urlRegex)
logger.Info(err.Error())
return nil, err
}
client := newClient(match[urlRegexRegionIndex])
input := &ecr.GetAuthorizationTokenInput{}
resp, err := client.GetAuthorizationToken(ctx, input)
if err != nil {
err = fmt.Errorf("failed to access ECR auth token: %w", err)
logger.Info(err.Error())
return nil, err
}
if len(resp.AuthorizationData) != 1 {
err = fmt.Errorf("expected a single ECR authorization token: %v", resp.AuthorizationData)
logger.Info(err.Error())
return nil, err
}
authToken := aws.ToString(resp.AuthorizationData[0].AuthorizationToken)
username, password, err := decodeAuth(authToken)
if err != nil {
err = fmt.Errorf("invalid ECR authorization token: %w", err)
logger.Info(err.Error())
return nil, err
}
logger.Info("Successfully authenticated with ECR")
return ®istry.AuthConfig{
Username: username,
Password: password,
}, nil
}
func decodeAuth(auth string) (string, string, error) {
if auth == "" {
return "", "", errors.New("docker auth token cannot be blank")
}
decoded, err := base64.StdEncoding.DecodeString(auth)
if err != nil {
return "", "", fmt.Errorf("failed to decode docker auth token: %w", err)
}
creds := strings.SplitN(string(decoded), ":", 2)
if len(creds) != 2 {
return "", "", fmt.Errorf("invalid docker auth token: %q", creds)
}
return creds[0], creds[1], nil
}