Skip to content

Commit c18c511

Browse files
committed
test: initial auth provider
Signed-off-by: Mike Nguyen <[email protected]>
1 parent 294dd75 commit c18c511

File tree

18 files changed

+1293
-672
lines changed

18 files changed

+1293
-672
lines changed

.build-tools/go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.24.1
55
require (
66
github.com/dapr/components-contrib v0.0.0
77
github.com/invopop/jsonschema v0.6.0
8-
github.com/spf13/cobra v1.6.1
8+
github.com/spf13/cobra v1.8.1
99
github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5
1010
gopkg.in/yaml.v3 v3.0.1
1111
sigs.k8s.io/yaml v1.4.0
@@ -15,7 +15,7 @@ require (
1515
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb // indirect
1616
github.com/gogo/protobuf v1.3.2 // indirect
1717
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
18-
github.com/inconshreveable/mousetrap v1.0.1 // indirect
18+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1919
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
2020
github.com/spf13/cast v1.8.0 // indirect
2121
github.com/spf13/pflag v1.0.6 // indirect
@@ -26,3 +26,8 @@ require (
2626
)
2727

2828
replace github.com/dapr/components-contrib => ../
29+
30+
// AWS v2 Migration Hack
31+
// This allows for the existing library to remain in place whilst components are upgraded incrementally.
32+
// TODO: @mikeee - remove
33+
replace github.com/aws/rolesanywhere-credential-helper_v160 => github.com/aws/rolesanywhere-credential-helper v1.6.0

.build-tools/go.sum

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
1+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
22
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb h1:ahLO7pMmX6HAuT6/RxYWBY4AN2fXQJcYlU1msY6Kt7U=
33
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb/go.mod h1:Hz1W2LmWfA4UX/12MdA+brsf+np6f/1dJt6C6F63cjI=
44
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -9,14 +9,14 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
99
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
1010
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
1111
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
12-
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
13-
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
12+
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
13+
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1414
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
1515
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
1616
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
1717
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
18-
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
19-
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
18+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
19+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
2020
github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY=
2121
github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
2222
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -35,8 +35,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
3535
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
3636
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
3737
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
38-
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
39-
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
38+
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
39+
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
4040
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
4141
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
4242
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=

common/aws/auth/auth.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go-v2/aws"
6+
"github.com/dapr/components-contrib/common/aws/options"
7+
)
8+
9+
const (
10+
SESSION_DURATION = 3600
11+
)
12+
13+
type AuthOptions struct {
14+
awsConfig *aws.Config
15+
}
16+
17+
type authOption func(*AuthOptions)
18+
19+
func WithAwsConfig(awsConfig *aws.Config) authOption {
20+
return func(opts *AuthOptions) {
21+
opts.awsConfig = awsConfig
22+
}
23+
}
24+
25+
func loadAuthOptions(opts ...authOption) AuthOptions {
26+
options := AuthOptions{}
27+
for _, opt := range opts {
28+
opt(&options)
29+
}
30+
return options
31+
}
32+
33+
type AuthProvider interface {
34+
AuthTest() bool
35+
GetAWSCredentialsProvider() aws.CredentialsProvider
36+
Close() error
37+
}
38+
39+
func NewAuthProvider(ctx context.Context, opts options.Options) (AuthProvider, error) {
40+
if authTypeIsx509(opts) {
41+
return newX509Auth(ctx, opts)
42+
}
43+
return newStaticAuth(opts), nil
44+
}

common/aws/auth/auth_static.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package auth
2+
3+
import (
4+
"github.com/aws/aws-sdk-go-v2/aws"
5+
"github.com/aws/aws-sdk-go-v2/credentials"
6+
"github.com/dapr/components-contrib/common/aws/options"
7+
"github.com/dapr/kit/logger"
8+
)
9+
10+
type AuthStatic struct {
11+
logger logger.Logger
12+
13+
region *string
14+
endpoint *string // aka BaseEndpoint
15+
accessKey *string
16+
secretKey *string
17+
sessionToken *string
18+
19+
assumeRoleArn *string
20+
sessionName *string
21+
}
22+
23+
func newStaticAuth(opts options.Options) AuthProvider {
24+
provider := &AuthStatic{
25+
logger: opts.Logger,
26+
}
27+
28+
switch {
29+
case opts.AccessKey != "":
30+
provider.accessKey = &opts.AccessKey
31+
case opts.AssumeRoleARN != "":
32+
provider.assumeRoleArn = &opts.AssumeRoleARN
33+
case opts.Endpoint != "":
34+
provider.endpoint = &opts.Endpoint
35+
case opts.Region != "":
36+
provider.region = &opts.Region
37+
case opts.SecretKey != "":
38+
provider.secretKey = &opts.SecretKey
39+
case opts.SessionName != "":
40+
provider.sessionName = &opts.SessionName
41+
case opts.SessionToken != "":
42+
provider.sessionToken = &opts.SessionToken
43+
}
44+
45+
return provider
46+
}
47+
48+
func (a *AuthStatic) AuthTest() bool {
49+
//TODO implement me
50+
panic("implement me")
51+
}
52+
53+
func (a *AuthStatic) GetAWSCredentialsProvider() aws.CredentialsProvider {
54+
switch {
55+
case a.accessKey == nil:
56+
a.logger.Error("accessKey is nil")
57+
return nil
58+
case a.secretKey == nil:
59+
a.logger.Error("secretKey is nil")
60+
return nil
61+
case a.sessionToken == nil:
62+
a.logger.Error("sessionToken is nil")
63+
return nil
64+
}
65+
66+
cpo := credentials.NewStaticCredentialsProvider(*a.accessKey, *a.secretKey, *a.sessionToken)
67+
68+
return aws.NewCredentialsCache(cpo) // cache the credentials
69+
}
70+
71+
func (a *AuthStatic) Close() error {
72+
//TODO implement me
73+
panic("implement me")
74+
}

common/aws/auth/auth_static_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package auth

common/aws/auth/auth_x509.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package auth
2+
3+
import (
4+
"context"
5+
"crypto"
6+
"crypto/x509"
7+
"errors"
8+
"fmt"
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/config"
11+
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
12+
"github.com/aws/aws-sdk-go-v2/service/sts"
13+
awssh "github.com/aws/rolesanywhere-credential-helper_v160/aws_signing_helper"
14+
"github.com/dapr/components-contrib/common/aws/options"
15+
spiffecontext "github.com/dapr/kit/crypto/spiffe/context"
16+
"github.com/dapr/kit/logger"
17+
kitmd "github.com/dapr/kit/metadata"
18+
"sync"
19+
)
20+
21+
const (
22+
aws4_x509_ecdsa_sha256 = "AWS4-X509-ECDSA-SHA256"
23+
)
24+
25+
func authTypeIsx509(opts options.Options) bool {
26+
if opts.Properties != nil {
27+
keys := []string{"trustProfileArn", "trustAnchorArn", "assumeRoleArn"}
28+
for _, key := range keys {
29+
if _, ok := opts.Properties[key]; !ok {
30+
return false
31+
}
32+
}
33+
return true
34+
}
35+
return false
36+
}
37+
38+
type AuthX509Options struct {
39+
TrustProfileArn *string `json:"trustProfileArn" mapstructure:"trustProfileArn"`
40+
TrustAnchorArn *string `json:"trustAnchorArn" mapstructure:"trustAnchorArn"`
41+
AssumeRoleArn *string `json:"assumeRoleArn" mapstructure:"assumeRoleArn"`
42+
}
43+
44+
type AuthX509 struct {
45+
logger logger.Logger
46+
mutex sync.RWMutex
47+
48+
signer crypto.Signer
49+
chainPEM []byte
50+
privateKeyPEM []byte
51+
awsSigner awsSigner
52+
53+
assumeRoleArn *string
54+
trustProfileArn *string
55+
trustAnchorArn *string
56+
region *string
57+
}
58+
59+
func newX509Auth(ctx context.Context, opts options.Options) (AuthProvider, error) {
60+
var x509Opts AuthX509Options
61+
if err := kitmd.DecodeMetadata(opts.Properties, &x509Opts); err != nil {
62+
return nil, err
63+
}
64+
switch {
65+
case x509Opts.TrustProfileArn == nil:
66+
return nil, errors.New("trustProfileArn is required")
67+
case x509Opts.TrustAnchorArn == nil:
68+
return nil, errors.New("trustAnchorArn is required")
69+
case x509Opts.AssumeRoleArn == nil:
70+
return nil, errors.New("assumeRoleArn is required")
71+
}
72+
73+
authX509 := &AuthX509{
74+
logger: opts.Logger,
75+
76+
assumeRoleArn: x509Opts.AssumeRoleArn,
77+
trustAnchorArn: x509Opts.TrustAnchorArn,
78+
trustProfileArn: x509Opts.TrustProfileArn,
79+
}
80+
81+
if err := authX509.updateCertsFromContext(ctx); err != nil {
82+
return nil, err
83+
}
84+
85+
return authX509, nil
86+
}
87+
88+
func (x *AuthX509) updateCertsFromContext(ctx context.Context) error {
89+
x.mutex.Lock()
90+
defer x.mutex.Unlock()
91+
92+
svidSrc, ok := spiffecontext.From(ctx)
93+
if !ok {
94+
return errors.New("failed to get SVID from context")
95+
}
96+
97+
svid, err := svidSrc.GetX509SVID()
98+
if err != nil {
99+
return fmt.Errorf("failed to get SVID: %w", err)
100+
}
101+
102+
chainPEM, privateKeyPEM, err := svid.Marshal()
103+
if err != nil {
104+
return fmt.Errorf("failed to marshal SVID: %w", err)
105+
}
106+
107+
x.chainPEM = chainPEM
108+
x.privateKeyPEM = privateKeyPEM
109+
110+
signer, err := newX509Signer(privateKeyPEM)
111+
if err != nil {
112+
return fmt.Errorf("failed to create signer: %w", err)
113+
}
114+
x.signer = signer
115+
116+
x.awsSigner = awsSigner{
117+
Signer: x.signer,
118+
chain: x.chainPEM,
119+
}
120+
121+
return nil
122+
}
123+
124+
func parseX509Certificate(chainPEM []byte) (*x509.Certificate, error) {
125+
return x509.ParseCertificate(chainPEM)
126+
}
127+
128+
func newX509Signer(privateKeyPEM []byte) (crypto.Signer, error) {
129+
privateKey, err := x509.ParseECPrivateKey(privateKeyPEM)
130+
if err != nil {
131+
return nil, fmt.Errorf("failed to parse private key: %w", err)
132+
}
133+
return privateKey, nil
134+
}
135+
136+
type awsSigner struct {
137+
crypto.Signer
138+
chain []byte
139+
}
140+
141+
func (a *awsSigner) Certificate() (*x509.Certificate, error) {
142+
cert, err := x509.ParseCertificate(a.chain)
143+
if err != nil {
144+
return nil, fmt.Errorf("failed to parse certificate: %w", err)
145+
}
146+
return cert, nil
147+
}
148+
149+
func (a *awsSigner) CertificateChain() ([]*x509.Certificate, error) {
150+
cert, err := a.Certificate()
151+
if err != nil {
152+
return nil, fmt.Errorf("failed to get certificate: %w", err)
153+
}
154+
return []*x509.Certificate{cert}, nil
155+
}
156+
157+
func (a *awsSigner) Close() {
158+
panic("implement me")
159+
}
160+
161+
func (x *AuthX509) getCredentials() (*awssh.CredentialProcessOutput, error) {
162+
credentialsOpts := &awssh.CredentialsOpts{
163+
RoleArn: *x.assumeRoleArn,
164+
ProfileArnStr: *x.trustProfileArn,
165+
TrustAnchorArnStr: *x.trustAnchorArn,
166+
SessionDuration: SESSION_DURATION,
167+
Region: *x.region,
168+
Debug: true, // TODO: @mikeee remove debug
169+
}
170+
171+
cpo, err := awssh.GenerateCredentials(credentialsOpts, &x.awsSigner, aws4_x509_ecdsa_sha256)
172+
if err != nil {
173+
return nil, fmt.Errorf("failed to generate credentials: %w", err)
174+
}
175+
176+
return &cpo, nil
177+
}
178+
179+
func (x *AuthX509) GetCredentials() (aws.Credentials, error) {
180+
panic("implement me")
181+
}
182+
183+
func (x *AuthX509) getOrRefreshCredentials(ctx context.Context) (aws.Credentials, error) {
184+
panic("implement me")
185+
}
186+
187+
func (x *AuthX509) AuthTest() bool {
188+
return true
189+
}
190+
191+
func (x *AuthX509) GetAWSCredentialsProvider() aws.CredentialsProvider {
192+
awsConfig, err := config.LoadDefaultConfig(
193+
context.TODO(),
194+
)
195+
if err != nil {
196+
x.logger.Errorf("failed to load default config: %v", err)
197+
return nil
198+
}
199+
200+
// create a new sts client
201+
stsClient := sts.NewFromConfig(awsConfig)
202+
203+
assumeRoleProvider := stscreds.NewAssumeRoleProvider(stsClient, *x.assumeRoleArn)
204+
205+
return aws.NewCredentialsCache(assumeRoleProvider)
206+
}
207+
208+
func (x *AuthX509) Close() error {
209+
panic("implement me")
210+
}

0 commit comments

Comments
 (0)