140 lines
3.3 KiB
Go
140 lines
3.3 KiB
Go
package v4a
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/aws/aws-sdk-go-v2/aws"
|
|
)
|
|
|
|
// Credentials is Context, ECDSA, and Optional Session Token that can be used
|
|
// to sign requests using SigV4a
|
|
type Credentials struct {
|
|
Context string
|
|
PrivateKey *ecdsa.PrivateKey
|
|
SessionToken string
|
|
|
|
// Time the credentials will expire.
|
|
CanExpire bool
|
|
Expires time.Time
|
|
}
|
|
|
|
// Expired returns if the credentials have expired.
|
|
func (v Credentials) Expired() bool {
|
|
if v.CanExpire {
|
|
return !v.Expires.After(time.Now())
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// HasKeys returns if the credentials keys are set.
|
|
func (v Credentials) HasKeys() bool {
|
|
return len(v.Context) > 0 && v.PrivateKey != nil
|
|
}
|
|
|
|
// SymmetricCredentialAdaptor wraps a SigV4 AccessKey/SecretKey provider and adapts the credentials
|
|
// to a ECDSA PrivateKey for signing with SiV4a
|
|
type SymmetricCredentialAdaptor struct {
|
|
SymmetricProvider aws.CredentialsProvider
|
|
|
|
asymmetric atomic.Value
|
|
m sync.Mutex
|
|
}
|
|
|
|
// Retrieve retrieves symmetric credentials from the underlying provider.
|
|
func (s *SymmetricCredentialAdaptor) Retrieve(ctx context.Context) (aws.Credentials, error) {
|
|
symCreds, err := s.retrieveFromSymmetricProvider(ctx)
|
|
if err != nil {
|
|
return aws.Credentials{}, err
|
|
}
|
|
|
|
if asymCreds := s.getCreds(); asymCreds == nil {
|
|
return symCreds, nil
|
|
}
|
|
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
|
|
asymCreds := s.getCreds()
|
|
if asymCreds == nil {
|
|
return symCreds, nil
|
|
}
|
|
|
|
// if the context does not match the access key id clear it
|
|
if asymCreds.Context != symCreds.AccessKeyID {
|
|
s.asymmetric.Store((*Credentials)(nil))
|
|
}
|
|
|
|
return symCreds, nil
|
|
}
|
|
|
|
// RetrievePrivateKey returns credentials suitable for SigV4a signing
|
|
func (s *SymmetricCredentialAdaptor) RetrievePrivateKey(ctx context.Context) (Credentials, error) {
|
|
if asymCreds := s.getCreds(); asymCreds != nil {
|
|
return *asymCreds, nil
|
|
}
|
|
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
|
|
if asymCreds := s.getCreds(); asymCreds != nil {
|
|
return *asymCreds, nil
|
|
}
|
|
|
|
symmetricCreds, err := s.retrieveFromSymmetricProvider(ctx)
|
|
if err != nil {
|
|
return Credentials{}, fmt.Errorf("failed to retrieve symmetric credentials: %v", err)
|
|
}
|
|
|
|
privateKey, err := deriveKeyFromAccessKeyPair(symmetricCreds.AccessKeyID, symmetricCreds.SecretAccessKey)
|
|
if err != nil {
|
|
return Credentials{}, fmt.Errorf("failed to derive assymetric key from credentials")
|
|
}
|
|
|
|
creds := Credentials{
|
|
Context: symmetricCreds.AccessKeyID,
|
|
PrivateKey: privateKey,
|
|
SessionToken: symmetricCreds.SessionToken,
|
|
CanExpire: symmetricCreds.CanExpire,
|
|
Expires: symmetricCreds.Expires,
|
|
}
|
|
|
|
s.asymmetric.Store(&creds)
|
|
|
|
return creds, nil
|
|
}
|
|
|
|
func (s *SymmetricCredentialAdaptor) getCreds() *Credentials {
|
|
v := s.asymmetric.Load()
|
|
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
c := v.(*Credentials)
|
|
if c != nil && c.HasKeys() && !c.Expired() {
|
|
return c
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SymmetricCredentialAdaptor) retrieveFromSymmetricProvider(ctx context.Context) (aws.Credentials, error) {
|
|
credentials, err := s.SymmetricProvider.Retrieve(ctx)
|
|
if err != nil {
|
|
return aws.Credentials{}, err
|
|
}
|
|
|
|
return credentials, nil
|
|
}
|
|
|
|
// CredentialsProvider is the interface for a provider to retrieve credentials
|
|
// to sign requests with.
|
|
type CredentialsProvider interface {
|
|
RetrievePrivateKey(context.Context) (Credentials, error)
|
|
}
|