Skip to content

test: initial auth provider #3824

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

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .build-tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.24.1
require (
github.com/dapr/components-contrib v0.0.0
github.com/invopop/jsonschema v0.6.0
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.8.1
github.com/xeipuuv/gojsonschema v1.2.1-0.20201027075954-b076d39a02e5
gopkg.in/yaml.v3 v3.0.1
sigs.k8s.io/yaml v1.4.0
Expand All @@ -15,7 +15,7 @@ require (
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
Expand Down
14 changes: 7 additions & 7 deletions .build-tools/go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb h1:ahLO7pMmX6HAuT6/RxYWBY4AN2fXQJcYlU1msY6Kt7U=
github.com/dapr/kit v0.13.1-0.20240909215017-3823663aa4bb/go.mod h1:Hz1W2LmWfA4UX/12MdA+brsf+np6f/1dJt6C6F63cjI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand All @@ -9,14 +9,14 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hCbmY=
github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand All @@ -35,8 +35,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
Expand Down
44 changes: 44 additions & 0 deletions common/aws/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package auth

import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/dapr/components-contrib/common/aws/options"
)

const (
SESSION_DURATION = 3600
)

type AuthOptions struct {
awsConfig *aws.Config
}

type authOption func(*AuthOptions)

func WithAwsConfig(awsConfig *aws.Config) authOption {
return func(opts *AuthOptions) {
opts.awsConfig = awsConfig
}
}

func loadAuthOptions(opts ...authOption) AuthOptions {
options := AuthOptions{}
for _, opt := range opts {
opt(&options)
}
return options
}

type AuthProvider interface {
AuthTest() bool
GetAWSCredentialsProvider() aws.CredentialsProvider
Close() error
}

func NewAuthProvider(ctx context.Context, opts options.Options) (AuthProvider, error) {
if authTypeIsx509(opts) {
return newX509Auth(ctx, opts)
}
return newStaticAuth(opts), nil
}
74 changes: 74 additions & 0 deletions common/aws/auth/auth_static.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package auth

import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/dapr/components-contrib/common/aws/options"
"github.com/dapr/kit/logger"
)

type AuthStatic struct {
logger logger.Logger

region *string
endpoint *string // aka BaseEndpoint
accessKey *string
secretKey *string
sessionToken *string

assumeRoleArn *string
sessionName *string
}

func newStaticAuth(opts options.Options) AuthProvider {
provider := &AuthStatic{
logger: opts.Logger,
}

switch {
case opts.AccessKey != "":
provider.accessKey = &opts.AccessKey
case opts.AssumeRoleARN != "":
provider.assumeRoleArn = &opts.AssumeRoleARN
case opts.Endpoint != "":
provider.endpoint = &opts.Endpoint
case opts.Region != "":
provider.region = &opts.Region
case opts.SecretKey != "":
provider.secretKey = &opts.SecretKey
case opts.SessionName != "":
provider.sessionName = &opts.SessionName
case opts.SessionToken != "":
provider.sessionToken = &opts.SessionToken
}

return provider
}

func (a *AuthStatic) AuthTest() bool {
//TODO implement me
panic("implement me")
}

func (a *AuthStatic) GetAWSCredentialsProvider() aws.CredentialsProvider {
switch {
case a.accessKey == nil:
a.logger.Debug("accessKey is nil")
return nil
case a.secretKey == nil:
a.logger.Debug("secretKey is nil")
return nil
case a.sessionToken == nil:
a.logger.Debug("sessionToken is nil")
return nil
}

cpo := credentials.NewStaticCredentialsProvider(*a.accessKey, *a.secretKey, *a.sessionToken)

return aws.NewCredentialsCache(cpo) // cache the credentials
}

func (a *AuthStatic) Close() error {
//TODO implement me
panic("implement me")
}
1 change: 1 addition & 0 deletions common/aws/auth/auth_static_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package auth
210 changes: 210 additions & 0 deletions common/aws/auth/auth_x509.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package auth

import (
"context"
"crypto"
"crypto/x509"
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/dapr/components-contrib/common/aws/options"
spiffecontext "github.com/dapr/kit/crypto/spiffe/context"
"github.com/dapr/kit/logger"
kitmd "github.com/dapr/kit/metadata"
awssh "github.com/mikeee/aws_rolesanywhere-credential-helper/aws_signing_helper"
"sync"
)

const (
aws4_x509_ecdsa_sha256 = "AWS4-X509-ECDSA-SHA256"
)

func authTypeIsx509(opts options.Options) bool {
if opts.Properties != nil {
keys := []string{"trustProfileArn", "trustAnchorArn", "assumeRoleArn"}
for _, key := range keys {
if _, ok := opts.Properties[key]; !ok {
return false
}
}
return true
}
return false
}

type AuthX509Options struct {
TrustProfileArn *string `json:"trustProfileArn" mapstructure:"trustProfileArn"`
TrustAnchorArn *string `json:"trustAnchorArn" mapstructure:"trustAnchorArn"`
AssumeRoleArn *string `json:"assumeRoleArn" mapstructure:"assumeRoleArn"`
}

type AuthX509 struct {
logger logger.Logger
mutex sync.RWMutex

signer crypto.Signer
chainPEM []byte
privateKeyPEM []byte
awsSigner awsSigner

assumeRoleArn *string
trustProfileArn *string
trustAnchorArn *string
region *string
}

func newX509Auth(ctx context.Context, opts options.Options) (AuthProvider, error) {
var x509Opts AuthX509Options
if err := kitmd.DecodeMetadata(opts.Properties, &x509Opts); err != nil {
return nil, err
}
switch {
case x509Opts.TrustProfileArn == nil:
return nil, errors.New("trustProfileArn is required")
case x509Opts.TrustAnchorArn == nil:
return nil, errors.New("trustAnchorArn is required")
case x509Opts.AssumeRoleArn == nil:
return nil, errors.New("assumeRoleArn is required")
}

authX509 := &AuthX509{
logger: opts.Logger,

assumeRoleArn: x509Opts.AssumeRoleArn,
trustAnchorArn: x509Opts.TrustAnchorArn,
trustProfileArn: x509Opts.TrustProfileArn,
}

if err := authX509.updateCertsFromContext(ctx); err != nil {
return nil, err
}

return authX509, nil
}

func (x *AuthX509) updateCertsFromContext(ctx context.Context) error {
x.mutex.Lock()
defer x.mutex.Unlock()

svidSrc, ok := spiffecontext.From(ctx)
if !ok {
return errors.New("failed to get SVID from context")
}

svid, err := svidSrc.GetX509SVID()
if err != nil {
return fmt.Errorf("failed to get SVID: %w", err)
}

chainPEM, privateKeyPEM, err := svid.Marshal()
if err != nil {
return fmt.Errorf("failed to marshal SVID: %w", err)
}

x.chainPEM = chainPEM
x.privateKeyPEM = privateKeyPEM

signer, err := newX509Signer(privateKeyPEM)
if err != nil {
return fmt.Errorf("failed to create signer: %w", err)
}
x.signer = signer

x.awsSigner = awsSigner{
Signer: x.signer,
chain: x.chainPEM,
}

return nil
}

func parseX509Certificate(chainPEM []byte) (*x509.Certificate, error) {
return x509.ParseCertificate(chainPEM)
}

func newX509Signer(privateKeyPEM []byte) (crypto.Signer, error) {
privateKey, err := x509.ParseECPrivateKey(privateKeyPEM)
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return privateKey, nil
}

type awsSigner struct {
crypto.Signer
chain []byte
}

func (a *awsSigner) Certificate() (*x509.Certificate, error) {
cert, err := x509.ParseCertificate(a.chain)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
return cert, nil
}

func (a *awsSigner) CertificateChain() ([]*x509.Certificate, error) {
cert, err := a.Certificate()
if err != nil {
return nil, fmt.Errorf("failed to get certificate: %w", err)
}
return []*x509.Certificate{cert}, nil
}

func (a *awsSigner) Close() {
panic("implement me")
}

func (x *AuthX509) getCredentials() (*awssh.CredentialProcessOutput, error) {
credentialsOpts := &awssh.CredentialsOpts{
RoleArn: *x.assumeRoleArn,
ProfileArnStr: *x.trustProfileArn,
TrustAnchorArnStr: *x.trustAnchorArn,
SessionDuration: SESSION_DURATION,
Region: *x.region,
Debug: true, // TODO: @mikeee remove debug
}

cpo, err := awssh.GenerateCredentials(credentialsOpts, &x.awsSigner, aws4_x509_ecdsa_sha256)
if err != nil {
return nil, fmt.Errorf("failed to generate credentials: %w", err)
}

return &cpo, nil
}

func (x *AuthX509) GetCredentials() (aws.Credentials, error) {
panic("implement me")
}

func (x *AuthX509) getOrRefreshCredentials(ctx context.Context) (aws.Credentials, error) {
panic("implement me")
}

func (x *AuthX509) AuthTest() bool {
return true
}

func (x *AuthX509) GetAWSCredentialsProvider() aws.CredentialsProvider {
awsConfig, err := config.LoadDefaultConfig(
context.TODO(),
)
if err != nil {
x.logger.Errorf("failed to load default config: %v", err)
return nil
}

// create a new sts client
stsClient := sts.NewFromConfig(awsConfig)

assumeRoleProvider := stscreds.NewAssumeRoleProvider(stsClient, *x.assumeRoleArn)

return aws.NewCredentialsCache(assumeRoleProvider)
}

func (x *AuthX509) Close() error {
panic("implement me")
}
Loading