diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..471328d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +aws.lee diff --git a/example-project/cmd/web-api/main.go b/example-project/cmd/web-api/main.go index 6992515..8a5b4ea 100644 --- a/example-project/cmd/web-api/main.go +++ b/example-project/cmd/web-api/main.go @@ -2,10 +2,8 @@ package main import ( "context" - "crypto/rsa" "encoding/json" "expvar" - "io/ioutil" "log" "net/http" _ "net/http/pprof" @@ -19,9 +17,12 @@ import ( "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/db" "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/flag" itrace "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/trace" - jwt "github.com/dgrijalva/jwt-go" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" "github.com/kelseyhightower/envconfig" "go.opencensus.io/trace" + awstrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/aws/aws-sdk-go/aws" ) /* @@ -68,10 +69,19 @@ func main() { SendInterval time.Duration `default:"15s" envconfig:"SEND_INTERVAL"` SendTimeout time.Duration `default:"500ms" envconfig:"SEND_TIMEOUT"` } + AwsAccount struct { + AccessKeyID string `envconfig:"AWS_ACCESS_KEY_ID"` + SecretAccessKey string `envconfig:"AWS_SECRET_ACCESS_KEY"` + Region string `default:"us-east-1" envconfig:"AWS_REGION"` + + // Get an AWS session from an implicit source if no explicit + // configuration is provided. This is useful for taking advantage of + // EC2/ECS instance roles. + UseRole bool `envconfig:"AWS_USE_ROLE"` + } Auth struct { - KeyID string `envconfig:"KEY_ID"` - PrivateKeyFile string `default:"/app/private.pem" envconfig:"PRIVATE_KEY_FILE"` - Algorithm string `default:"RS256" envconfig:"ALGORITHM"` + AwsSecretID string `default:"auth-secret-key" envconfig:"AUTH_AWS_SECRET_ID"` + KeyExpiration time.Duration `default:"3600s" envconfig:"AUTH_KEY_EXPIRATION"` } } @@ -104,21 +114,22 @@ func main() { log.Printf("main : Config : %v\n", string(cfgJSON)) // ========================================================================= - // Find auth keys - - keyContents, err := ioutil.ReadFile(cfg.Auth.PrivateKeyFile) - if err != nil { - log.Fatalf("main : Reading auth private key : %v", err) + // Init AWS Session + var awsSession *session.Session + if cfg.AwsAccount.UseRole { + // Get an AWS session from an implicit source if no explicit + // configuration is provided. This is useful for taking advantage of + // EC2/ECS instance roles. + awsSession = session.Must(session.NewSession()) + } else { + creds := credentials.NewStaticCredentials(cfg.AwsAccount.AccessKeyID, cfg.AwsAccount.SecretAccessKey, "") + awsSession = session.New(&aws.Config{Region: aws.String(cfg.AwsAccount.Region), Credentials: creds}) } + awsSession = awstrace.WrapSession(awsSession) - key, err := jwt.ParseRSAPrivateKeyFromPEM(keyContents) - if err != nil { - log.Fatalf("main : Parsing auth private key : %v", err) - } - - publicKeyLookup := auth.NewSingleKeyFunc(cfg.Auth.KeyID, key.Public().(*rsa.PublicKey)) - - authenticator, err := auth.NewAuthenticator(key, cfg.Auth.KeyID, cfg.Auth.Algorithm, publicKeyLookup) + // ========================================================================= + // Load auth keys from AWS and init new Authenticator + authenticator, err := auth.NewAuthenticator(awsSession, cfg.Auth.AwsSecretID, time.Now().UTC(), cfg.Auth.KeyExpiration) if err != nil { log.Fatalf("main : Constructing authenticator : %v", err) } diff --git a/example-project/go.mod b/example-project/go.mod index 0fb6c6e..aea2173 100644 --- a/example-project/go.mod +++ b/example-project/go.mod @@ -1,6 +1,7 @@ module geeks-accelerator/oss/saas-starter-kit/example-project require ( + github.com/aws/aws-sdk-go v1.19.32 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dimfeld/httptreemux v5.0.1+incompatible github.com/go-playground/locales v0.12.1 @@ -11,12 +12,15 @@ require ( github.com/leodido/go-urn v1.1.0 // indirect github.com/openzipkin/zipkin-go v0.1.1 github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3 + github.com/philhofer/fwd v1.0.0 // indirect github.com/pkg/errors v0.8.0 github.com/stretchr/testify v1.3.0 // indirect + github.com/tinylib/msgp v1.1.0 // indirect go.opencensus.io v0.14.0 golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b golang.org/x/net v0.0.0-20180724234803-3673e40ba225 // indirect golang.org/x/text v0.3.0 // indirect + gopkg.in/DataDog/dd-trace-go.v1 v1.13.0 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v9 v9.28.0 diff --git a/example-project/go.sum b/example-project/go.sum index 470ae03..0c85773 100644 --- a/example-project/go.sum +++ b/example-project/go.sum @@ -1,3 +1,5 @@ +github.com/aws/aws-sdk-go v1.19.32 h1:/usjSR6qsKfOKzk4tDNvZq7LqmP5+J0Cq/Uwsr2XVG8= +github.com/aws/aws-sdk-go v1.19.32/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -10,6 +12,8 @@ github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rm github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -23,6 +27,8 @@ github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOI github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3 h1:9J0mOv1rXIBlRjQCiAGyx9C3dZZh5uIa3HU0oTV8v1E= github.com/pborman/uuid v0.0.0-20180122190007-c65b2f87fee3/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= +github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -30,6 +36,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU= +github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= go.opencensus.io v0.14.0 h1:1eTLxqxSIAylcKoxnNkdhvvBNZDA8JwkKNXxgyma0IA= go.opencensus.io v0.14.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= @@ -38,6 +46,8 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmy golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/DataDog/dd-trace-go.v1 v1.13.0 h1:8e4PNOH1Iq9ZwaY/IUjMIpzpsCweNNakKTiDsWc3ZJE= +gopkg.in/DataDog/dd-trace-go.v1 v1.13.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/example-project/internal/platform/auth/auth.go b/example-project/internal/platform/auth/auth.go index 249f4f5..67a99a6 100644 --- a/example-project/internal/platform/auth/auth.go +++ b/example-project/internal/platform/auth/auth.go @@ -1,10 +1,19 @@ package auth import ( + "bytes" + "crypto/rand" "crypto/rsa" + "crypto/x509" + "encoding/pem" "fmt" + "github.com/aws/aws-sdk-go/aws/awserr" + "time" - jwt "github.com/dgrijalva/jwt-go" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/dgrijalva/jwt-go" "github.com/pkg/errors" ) @@ -19,15 +28,15 @@ import ( // endpoint. See https://auth0.com/docs/jwks for more details. type KeyFunc func(keyID string) (*rsa.PublicKey, error) -// NewSingleKeyFunc is a simple implementation of KeyFunc that only ever -// supports one key. This is easy for development but in projection should be -// replaced with a caching layer that calls a JWKS endpoint. -func NewSingleKeyFunc(id string, key *rsa.PublicKey) KeyFunc { +// NewKeyFunc is a multiple implementation of KeyFunc that +// supports a map of keys. +func NewKeyFunc(keys map[string]*rsa.PrivateKey) KeyFunc { return func(kid string) (*rsa.PublicKey, error) { - if id != kid { + key, ok := keys[kid] + if !ok { return nil, fmt.Errorf("unrecognized kid %q", kid) } - return key, nil + return key.Public().(*rsa.PublicKey), nil } } @@ -41,21 +50,193 @@ type Authenticator struct { parser *jwt.Parser } -// NewAuthenticator creates an *Authenticator for use. It will error if: -// - The private key is nil. -// - The public key func is nil. -// - The key ID is blank. +// NewAuthenticator creates an *Authenticator for use. +// key expiration is optional to filter out old keys +// It will error if: +// - The aws session is nil. +// - The aws secret id is blank. // - The specified algorithm is unsupported. -func NewAuthenticator(key *rsa.PrivateKey, keyID, algorithm string, publicKeyFunc KeyFunc) (*Authenticator, error) { - if key == nil { - return nil, errors.New("private key cannot be nil") +func NewAuthenticator(awsSession *session.Session, awsSecretID string, now time.Time, keyExpiration time.Duration) (*Authenticator, error) { + if awsSession == nil { + return nil, errors.New("aws session cannot be nil") } - if publicKeyFunc == nil { - return nil, errors.New("public key function cannot be nil") + + if awsSecretID == "" { + return nil, errors.New("aws secret id cannot be empty") } - if keyID == "" { - return nil, errors.New("keyID cannot be blank") + + if now.IsZero() { + now = time.Now().UTC() } + + // Time threshold to stop loading keys, any key with a created date + // before this value will not be loaded. + var disabledCreatedDate time.Time + + // Time threshold to create a new key. If a current key exists and the + // created date of the key is before this value, a new key will be created. + var activeCreatedDate time.Time + + // If an expiration duration is included, convert to past time from now. + if keyExpiration.Seconds() != 0 { + // Ensure the expiration is a time in the past for comparison below. + if keyExpiration.Seconds() > 0 { + keyExpiration = keyExpiration * -1 + } + // Stop loading keys when the created date exceeds two times the key expiration + disabledCreatedDate = now.UTC().Add(keyExpiration * 2) + + // Time used to determine when a new key should be created. + activeCreatedDate = now.UTC().Add(keyExpiration) + } + + // Init new AWS Secret Manager using provided AWS session. + secretManager := secretsmanager.New(awsSession) + + // A List of version ids for the stored secret. All keys will be stored under + // the same name in AWS secret manager. We still want to load old keys for a + // short period of time to ensure any requests in flight have the opportunity + // to be completed. + var versionIds []string + + // Exec call to AWS secret manager to return a list of version ids for the + // provided secret ID. + listParams := &secretsmanager.ListSecretVersionIdsInput{ + SecretId: aws.String(awsSecretID), + } + err := secretManager.ListSecretVersionIdsPages(listParams, + func(page *secretsmanager.ListSecretVersionIdsOutput, lastPage bool) bool { + for _, v := range page.Versions { + // When disabled CreatedDate is not empty, compare the created date + // for each key version to the disabled cut off time. + if !disabledCreatedDate.IsZero() && v.CreatedDate != nil && !v.CreatedDate.IsZero() { + // Skip any version ids that are less than the expiration time. + if v.CreatedDate.UTC().Unix() < disabledCreatedDate.UTC().Unix() { + continue + } + } + + if v.VersionId != nil { + versionIds = append(versionIds, *v.VersionId) + } + } + return !lastPage + }, + ) + + // Flag whether the secret exists and update needs to be used + // instead of create. + var awsSecretIDNotFound bool + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case secretsmanager.ErrCodeResourceNotFoundException: + awsSecretIDNotFound = true + } + } + + if !awsSecretIDNotFound { + return nil, errors.Wrapf(err, "aws list secret version ids for secret ID %s failed", awsSecretID) + } + } + + // Map of keys stored by version id. version id is kid. + keyContents := make(map[string][]byte) + + // The current key id if there is an active one. + var curKeyId string + + // If the list of version ids is not empty, load the keys from secret manager. + if len(versionIds) > 0 { + // The max created data to determine the most recent key. + var lastCreatedDate time.Time + + for _, id := range versionIds { + res, err := secretManager.GetSecretValue(&secretsmanager.GetSecretValueInput{ + SecretId: aws.String(awsSecretID), + VersionId: aws.String(id), + }) + if err != nil { + return nil, errors.Wrapf(err, "aws secret id %s, version id %s value failed", awsSecretID, id) + } + + if len(res.SecretBinary) == 0 { + continue + } + + keyContents[*res.VersionId] = res.SecretBinary + + if lastCreatedDate.IsZero() || res.CreatedDate.UTC().Unix() > lastCreatedDate.UTC().Unix() { + curKeyId = *res.VersionId + lastCreatedDate = res.CreatedDate.UTC() + } + } + + // + if !activeCreatedDate.IsZero() && lastCreatedDate.UTC().Unix() < activeCreatedDate.UTC().Unix() { + curKeyId = "" + } + } + + // If there are no keys stored in secret manager, create a new one or + // if the current key needs to be rotated, generate a new key and update the secret. + // @TODO: When a new key is generated and there are multiple instances of the service running + // its possible based on the key expiration set that requests fail because keys are only + // refreshed on instance launch. Could store keys in a kv store and update that value + // when new keys are generated + if len(keyContents) == 0 || curKeyId == "" { + privateKey, err := keygen() + if err != nil { + return nil, errors.Wrap(err, "failed to generate new private key") + } + + if awsSecretIDNotFound { + res, err := secretManager.CreateSecret(&secretsmanager.CreateSecretInput{ + Name: aws.String(awsSecretID), + SecretBinary: privateKey, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to create new secret with private key") + } + curKeyId = *res.VersionId + } else { + res, err := secretManager.UpdateSecret(&secretsmanager.UpdateSecretInput{ + SecretId: aws.String(awsSecretID), + SecretBinary: privateKey, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to create new secret with private key") + } + curKeyId = *res.VersionId + } + + keyContents[curKeyId] = privateKey + } + + // Map of keys by kid (version id). + keys := make(map[string]*rsa.PrivateKey) + + // The current active key to be used. + var curPrivateKey *rsa.PrivateKey + + // Loop through all the key bytes and load the private key. + for kid, keyContent := range keyContents { + key, err := jwt.ParseRSAPrivateKeyFromPEM(keyContent) + if err != nil { + return nil, errors.Wrap(err, "parsing auth private key") + } + keys[kid] = key + if kid == curKeyId { + curPrivateKey = key + } + } + + // Lookup function to be used by the middleware to validate the kid and + // Return the associated public key. + publicKeyLookup := NewKeyFunc(keys) + + // Algorithm to be used to for the private key. + algorithm := "RS256" if jwt.GetSigningMethod(algorithm) == nil { return nil, errors.Errorf("unknown algorithm %v", algorithm) } @@ -68,10 +249,10 @@ func NewAuthenticator(key *rsa.PrivateKey, keyID, algorithm string, publicKeyFun } a := Authenticator{ - privateKey: key, - keyID: keyID, + privateKey: curPrivateKey, + keyID: curKeyId, algorithm: algorithm, - kf: publicKeyFunc, + kf: publicKeyLookup, parser: &parser, } @@ -125,3 +306,23 @@ func (a *Authenticator) ParseClaims(tknStr string) (Claims, error) { return claims, nil } + +// keygen creates an x509 private key for signing auth tokens. +func keygen() ([]byte, error) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return []byte{}, errors.Wrap(err, "generating keys") + } + + block := pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(key), + } + + buf := new(bytes.Buffer) + if err := pem.Encode(buf, &block); err != nil { + return []byte{}, errors.Wrap(err, "encoding to private file") + } + + return buf.Bytes(), nil +} diff --git a/example-project/internal/platform/auth/auth_test.go b/example-project/internal/platform/auth/auth_test.go index 1d4e3f3..7c8acb2 100644 --- a/example-project/internal/platform/auth/auth_test.go +++ b/example-project/internal/platform/auth/auth_test.go @@ -1,29 +1,43 @@ package auth_test import ( + "os" "testing" + "time" "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/auth" - jwt "github.com/dgrijalva/jwt-go" + "geeks-accelerator/oss/saas-starter-kit/example-project/internal/platform/tests" + "github.com/pborman/uuid" ) +var test *tests.Test + +// TestMain is the entry point for testing. +func TestMain(m *testing.M) { + os.Exit(testMain(m)) +} + +func testMain(m *testing.M) int { + test = tests.New() + defer test.TearDown() + + return m.Run() +} + func TestAuthenticator(t *testing.T) { - // Parse the private key used to generate the token. - prvKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(privateRSAKey)) - if err != nil { - t.Fatal(err) - } + awsSecretID := "jwt-key" + uuid.NewRandom().String() - // Parse the public key used to validate the token. - pubKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicRSAKey)) - if err != nil { - t.Fatal(err) - } - - a, err := auth.NewAuthenticator(prvKey, privateRSAKeyID, "RS256", auth.NewSingleKeyFunc(privateRSAKeyID, pubKey)) - if err != nil { - t.Fatal(err) + var authTests = []struct { + name string + awsSecretID string + now time.Time + keyExpiration time.Duration + error error + }{ + {"NoKeyExpiration", awsSecretID, time.Now(), time.Duration(0), nil}, + {"KeyExpirationOk", awsSecretID, time.Now(), time.Duration(time.Second * 3600), nil}, + {"KeyExpirationDisabled", awsSecretID, time.Now().Add(time.Second * 3600 * 3), time.Duration(time.Second * 3600), nil}, } // Generate the token. @@ -31,67 +45,44 @@ func TestAuthenticator(t *testing.T) { Roles: []string{auth.RoleAdmin}, } - tknStr, err := a.GenerateToken(signedClaims) - if err != nil { - t.Fatal(err) - } + t.Log("Given the need to validate initiating a new Authenticator by key expiration.") + { + for i, tt := range authTests { + t.Logf("\tTest: %d\tWhen running test: %s", i, tt.name) + { + a, err := auth.NewAuthenticator(test.AwsSession, tt.awsSecretID, tt.now, tt.keyExpiration) + if err != tt.error { + t.Log("\t\tGot :", err) + t.Log("\t\tWant:", tt.error) + t.Fatalf("\t%s\tNewAuthenticator failed.", tests.Failed) + } - parsedClaims, err := a.ParseClaims(tknStr) - if err != nil { - t.Fatal(err) - } + tknStr, err := a.GenerateToken(signedClaims) + if err != nil { + t.Log("\t\tGot :", err) + t.Fatalf("\t%s\tGenerateToken failed.", tests.Failed) + } - // Assert expected claims. - if exp, got := len(signedClaims.Roles), len(parsedClaims.Roles); exp != got { - t.Fatalf("expected %v roles, got %v", exp, got) - } - if exp, got := signedClaims.Roles[0], parsedClaims.Roles[0]; exp != got { - t.Fatalf("expected roles[0] == %v, got %v", exp, got) + parsedClaims, err := a.ParseClaims(tknStr) + if err != nil { + t.Log("\t\tGot :", err) + t.Fatalf("\t%s\tParseClaims failed.", tests.Failed) + } + + // Assert expected claims. + if exp, got := len(signedClaims.Roles), len(parsedClaims.Roles); exp != got { + t.Log("\t\tGot :", got) + t.Log("\t\tWant:", exp) + t.Fatalf("\t%s\tShould got the same number of roles.", tests.Failed) + } + if exp, got := signedClaims.Roles[0], parsedClaims.Roles[0]; exp != got { + t.Log("\t\tGot :", got) + t.Log("\t\tWant:", exp) + t.Fatalf("\t%s\tShould got the same role name.", tests.Failed) + } + + t.Logf("\t%s\tNewAuthenticator ok.", tests.Success) + } + } } } - -// The key id we would have generated for the private below key -const privateRSAKeyID = "54bb2165-71e1-41a6-af3e-7da4a0e1e2c1" - -// Output of: -// openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048 -const privateRSAKey = `-----BEGIN PRIVATE KEY----- -MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQDdiBDU4jqRYuHl -yBmo5dWB1j9aeDrXzUTJbRKlgo+DWDQzIzJQvackvRu8/f7B5cseoqmeJcmBu6pc -4DmQ+puGNHxzCyYVFSMwRtHBZvfWS3P+UqIXCKRAX/NZbLkUEeqPnn5WXjA+YXKk -sfniE0xDH8W22o0OXHOzRhDWORjNTulpMpLv8tKnnLKh2Y/kCL/4vo0SZ+RWh8F9 -4+JTZx/47RHWb6fkxkikyTO3zO3efIkrKjfRx2CwFwO2rQ/3T04GQB/Lgr5lfJQU -iofvvVYuj2xBJao+3t9Ir0OeSbw1T5Rz03VLtN8SZhvaxWaBfwkUuUNL1glJO+Yd -LkMxGS0zAgMBAAECggEBAKM6m7RQUPlJE8u8qfOCDdSSKbIefrT9wZ5tKN0dG2Oa -/TNkzrEhXOO8F5Ek0a7LA+Q51KL7ksNtpLS0XpZNoYS8bapS36ePIJN0yx8nIJwc -koYlGtu/+U6ZpHQSoTiBjwRtswcudXuxT8i8frOupnWbFpKJ7H9Vbcb9bHB8N6Mm -D63wSBR08ZMrZXheKHQCQcxSQ2ZQZ+X3LBIOdXZH1aaptU2KpMEU5oyxXPShTVMg -0f748yU2njXCF0ZABEanXgp13egr/MPqHwnS/h0PH45bNy3IgFtMEHEouQFsAzoS -qNe8/9WnrpY87UdSZMnzF/IAXV0bmollDnqfM8/EqxkCgYEA96ThXYGzAK5RKNqp -RqVdRVA0UTT48sJvrxLMuHpyUzg6cl8FZE5rrNxFbouxvyN192Ctv1q8yfv4/HfM -KpmtEjt3fYtITHVXII6O3qNaRoIEPwKT4eK/ar+JO59vI0YvweXvDH5TkS9aiFr+ -pPGf3a7EbE24BKhgiI8eT6K0VuUCgYEA5QGg11ZVoUut4ERAPouwuSdWwNe0HYqJ -A1m5vTvF5ghUHAb023lrr7Psq9DPJQQe7GzPfXafsat9hGenyqiyxo1gwClIyoEH -fOg753kdHcy60VVzumsPXece3OOSnd0rRMgfsSsclgYO7z0g9YZPAjt2w9NVw6uN -UDqX3eO2WjcCgYEA015eoNHv99fRG96udsbz+hI/5UQibAl7C+Iu7BJO/CrU8AOc -dYXdr5f+hyEioDLjIDbbdaU71+aCGPMjRwUNzK8HCRfVqLTKndYvqWWhyuZ0O1e2 -4ykHGlTLDCHD2Uaxwny/8VjteNEDI7kO+bfmLG9b5djcBNW2Nzh4tZ348OUCgYEA -vIrTppbhF1QciqkGj7govrgBt/GfzDaTyZtkzcTZkSNIRG8Bx3S3UUh8UZUwBpTW -9OY9ClnQ7tF3HLzOq46q6cfaYTtcP8Vtqcv2DgRsEW3OXazSBChC1ZgEk+4Vdz1x -c0akuRP6jBXe099rNFno0LiudlmXoeqrBOPIxxnEt48CgYEAxNZBc/GKiHXz/ZRi -IZtRT5rRRof7TEiDxSKOXHSG7HhIRDCrpwn4Dfi+GWNHIwsIlom8FzZTSHAN6pqP -E8Imrlt3vuxnUE1UMkhDXrlhrxslRXU9enynVghAcSrg6ijs8KuN/9RB/I7H03cT -77mx9eHMcYcRUciY5C8AOaArmMA= ------END PRIVATE KEY-----` - -// Output of: -// openssl rsa -pubout -in private.pem -out public.pem -const publicRSAKey = `-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3YgQ1OI6kWLh5cgZqOXV -gdY/Wng6181EyW0SpYKPg1g0MyMyUL2nJL0bvP3+weXLHqKpniXJgbuqXOA5kPqb -hjR8cwsmFRUjMEbRwWb31ktz/lKiFwikQF/zWWy5FBHqj55+Vl4wPmFypLH54hNM -Qx/FttqNDlxzs0YQ1jkYzU7paTKS7/LSp5yyodmP5Ai/+L6NEmfkVofBfePiU2cf -+O0R1m+n5MZIpMkzt8zt3nyJKyo30cdgsBcDtq0P909OBkAfy4K+ZXyUFIqH771W -Lo9sQSWqPt7fSK9Dnkm8NU+Uc9N1S7TfEmYb2sVmgX8JFLlDS9YJSTvmHS5DMRkt -MwIDAQAB ------END PUBLIC KEY-----` diff --git a/example-project/internal/platform/tests/main.go b/example-project/internal/platform/tests/main.go index 22663c8..af5d3f3 100644 --- a/example-project/internal/platform/tests/main.go +++ b/example-project/internal/platform/tests/main.go @@ -3,6 +3,7 @@ package tests import ( "context" "fmt" + "github.com/aws/aws-sdk-go/aws/session" "log" "os" "runtime/debug" @@ -23,9 +24,10 @@ const ( // Test owns state for running/shutting down tests. type Test struct { - Log *log.Logger - MasterDB *db.DB - container *docker.Container + Log *log.Logger + MasterDB *db.DB + container *docker.Container + AwsSession *session.Session } // New is the entry point for tests. @@ -36,6 +38,10 @@ func New() *Test { log := log.New(os.Stdout, "TEST : ", log.LstdFlags|log.Lmicroseconds|log.Lshortfile) + // ============================================================ + // Init AWS Session + awsSession := session.Must(session.NewSession()) + // ============================================================ // Startup Mongo container @@ -59,7 +65,7 @@ func New() *Test { log.Fatalf("startup : Register DB : %v", err) } - return &Test{log, masterDB, container} + return &Test{log, masterDB, container, awsSession} } // TearDown is used for shutting down tests. Calling this should be diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0f9915c --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module gitlab.com/geeks-accelerator/oss/saas-starter-kit + +go 1.12