-
Notifications
You must be signed in to change notification settings - Fork 9
/
keychain.go
125 lines (101 loc) · 3.63 KB
/
keychain.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
// Copyright 2022 Namespace Labs Inc; All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
package ecr
import (
"context"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/ecr/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
dockertypes "github.com/docker/cli/cli/config/types"
"github.com/google/go-containerregistry/pkg/authn"
"namespacelabs.dev/foundation/internal/artifacts/oci"
"namespacelabs.dev/foundation/internal/compute"
"namespacelabs.dev/foundation/internal/fnerrors"
awsprovider "namespacelabs.dev/foundation/internal/providers/aws"
"namespacelabs.dev/foundation/internal/providers/aws/auth"
"namespacelabs.dev/foundation/std/tasks"
)
var DefaultKeychain oci.Keychain = defaultKeychain{}
type defaultKeychain struct{}
func (dk defaultKeychain) Resolve(ctx context.Context, r authn.Resource) (authn.Authenticator, error) {
// XXX rethink this; we need more context in order to pick the right credentials.
session, err := awsprovider.ConfiguredSession(ctx, nil)
if err != nil {
return nil, err
}
config, err := keychainSession{sesh: session}.refreshPrivateAuth(ctx)
if err != nil {
return nil, err
}
if config.ServerAddress == r.RegistryStr() {
return authn.FromConfig(authn.AuthConfig{
Username: config.Username,
Password: config.Password,
}), nil
}
// Nothing available.
return authn.FromConfig(authn.AuthConfig{}), nil
}
type keychainSession struct {
sesh *awsprovider.Session
}
var _ oci.Keychain = keychainSession{}
func (em keychainSession) Resolve(ctx context.Context, r authn.Resource) (authn.Authenticator, error) {
config, err := em.refreshPrivateAuth(ctx)
if err != nil {
return nil, err
}
if config.ServerAddress == r.RegistryStr() {
return authn.FromConfig(authn.AuthConfig{
Username: config.Username,
Password: config.Password,
}), nil
}
return nil, nil
}
func (em keychainSession) refreshPrivateAuth(ctx context.Context) (*dockertypes.AuthConfig, error) {
if em.sesh == nil {
return nil, fnerrors.New("aws/ecr: no credentials available")
}
return tasks.Return(ctx, tasks.Action("aws.ecr.auth"),
func(ctx context.Context) (*dockertypes.AuthConfig, error) {
return refreshAuth(ctx,
func(ctx context.Context) ([]types.AuthorizationData, error) {
resp, err := compute.GetValue[*ecr.GetAuthorizationTokenOutput](ctx, &cachedAuthToken{sesh: em.sesh})
if err != nil {
return nil, err
}
return resp.AuthorizationData, nil
},
func(ctx context.Context) (string, error) {
res, err := compute.Get(ctx, em.resolveAccount())
if err != nil {
return "", err
}
return repoURL(em.sesh.Config(), res.Value), nil
})
})
}
func (em keychainSession) resolveAccount() compute.Computable[*sts.GetCallerIdentityOutput] {
return auth.ResolveWithConfig(em.sesh)
}
type cachedAuthToken struct {
sesh *awsprovider.Session
compute.DoScoped[*ecr.GetAuthorizationTokenOutput]
}
func (cat cachedAuthToken) Action() *tasks.ActionEvent {
return tasks.Action("ecr.get-auth-token").Category("aws")
}
func (cat cachedAuthToken) Inputs() *compute.In {
return compute.Inputs().Str("cacheKey", cat.sesh.CacheKey())
}
func (cat cachedAuthToken) Compute(ctx context.Context, _ compute.Resolved) (*ecr.GetAuthorizationTokenOutput, error) {
token, err := ecr.NewFromConfig(cat.sesh.Config()).GetAuthorizationToken(ctx, &ecr.GetAuthorizationTokenInput{})
if err != nil {
return nil, auth.CheckNeedsLoginOr(cat.sesh, err, func(err error) error {
return fnerrors.New("ecr: get auth token failed: %w", err)
})
}
return token, nil
}