Skip to content

Commit

Permalink
Merge pull request #41 from devopsfaith/alternative_jwk_sources
Browse files Browse the repository at this point in the history
support for local stored JWK files added
  • Loading branch information
kpacha committed Nov 19, 2020
2 parents fc3aaf7 + b636570 commit e9123d6
Show file tree
Hide file tree
Showing 10 changed files with 1,023 additions and 4 deletions.
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ require (
github.com/devopsfaith/krakend v1.1.2-0.20200826121428-f41e024bd50a
github.com/gin-contrib/sse v0.1.0
github.com/gin-gonic/gin v1.5.0
github.com/golang/protobuf v1.3.2
github.com/golang/protobuf v1.4.2
github.com/json-iterator/go v1.1.7
github.com/mattn/go-isatty v0.0.9
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742
github.com/ugorji/go v1.1.7
gocloud.dev v0.20.0
gocloud.dev/secrets/hashivault v0.20.0
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980
gopkg.in/go-playground/validator.v8 v8.18.2
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v2 v2.2.2
Expand Down
505 changes: 505 additions & 0 deletions go.sum

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions jose.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ func NewValidator(signatureConfig *SignatureConfig, ef ExtractorFactory) (*auth0
Fingerprints: decodedFs,
LocalCA: signatureConfig.LocalCA,
AllowInsecure: signatureConfig.DisableJWKSecurity,
LocalPath: signatureConfig.LocalPath,
SecretURL: signatureConfig.SecretURL,
CipherKey: signatureConfig.CipherKey,
}

sp, err := SecretProvider(cfg, te)
Expand Down
62 changes: 61 additions & 1 deletion jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -16,6 +17,9 @@ import (
"time"

auth0 "github.com/auth0-community/go-auth0"
jose "gopkg.in/square/go-jose.v2"

"github.com/devopsfaith/krakend-jose/secrets"
)

type SecretProviderConfig struct {
Expand All @@ -26,6 +30,9 @@ type SecretProviderConfig struct {
Cs []uint16
LocalCA string
AllowInsecure bool
LocalPath string
SecretURL string
CipherKey []byte
}

var (
Expand All @@ -40,7 +47,10 @@ func SecretProvider(cfg SecretProviderConfig, te auth0.RequestTokenExtractor) (*
}

if !cfg.CacheEnabled {
return auth0.NewJWKClientWithCache(opts, te, auth0.NewMemoryKeyCacher(0, 0)), nil
if cfg.LocalPath == "" {
return auth0.NewJWKClientWithCache(opts, te, auth0.NewMemoryKeyCacher(0, 0)), nil
}
return newLocalSecretProvider(opts, cfg, te)
}

var cacheDuration time.Duration
Expand All @@ -62,6 +72,56 @@ func SecretProvider(cfg SecretProviderConfig, te auth0.RequestTokenExtractor) (*
return client, nil
}

func newLocalSecretProvider(opts auth0.JWKClientOptions, cfg SecretProviderConfig, te auth0.RequestTokenExtractor) (*auth0.JWKClient, error) {
data, err := ioutil.ReadFile(cfg.LocalPath)
if err != nil {
return nil, err
}

if cfg.SecretURL != "" {
ctx := context.Background()
sk, err := secrets.New(ctx, cfg.SecretURL)
if err != nil {
return nil, err
}
data, err = sk.Decrypt(ctx, data, cfg.CipherKey)
if err != nil {
return nil, err
}
sk.Close()
}

keyCacher, err := NewFileKeyCacher(data)
if err != nil {
return nil, err
}
return auth0.NewJWKClientWithCache(opts, te, keyCacher), nil
}

func NewFileKeyCacher(data []byte) (*FileKeyCacher, error) {
keys := jose.JSONWebKeySet{}
if err := json.Unmarshal(data, &keys); err != nil {
return nil, err
}
keyMap := map[string]*jose.JSONWebKey{}
for _, k := range keys.Keys {
keyMap[k.KeyID] = &k
}
return &FileKeyCacher{keys: keyMap}, nil
}

type FileKeyCacher struct {
keys map[string]*jose.JSONWebKey
}

func (f *FileKeyCacher) Get(keyID string) (*jose.JSONWebKey, error) {
return f.keys[keyID], nil
}

func (f *FileKeyCacher) Add(keyID string, _ []jose.JSONWebKey) (*jose.JSONWebKey, error) {
return f.keys[keyID], nil
}

func newJWKClientOptions(cfg SecretProviderConfig) (auth0.JWKClientOptions, error) {
if len(cfg.Cs) == 0 {
cfg.Cs = DefaultEnabledCipherSuites
Expand Down
169 changes: 169 additions & 0 deletions jwk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@
package jose

import (
"context"
"crypto/rand"
"crypto/tls"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"sync/atomic"
"testing"

"github.com/devopsfaith/krakend-jose/secrets"
)

func TestJWK(t *testing.T) {
Expand Down Expand Up @@ -67,6 +72,126 @@ func TestJWK(t *testing.T) {
}
}

func TestJWK_file(t *testing.T) {
for _, tc := range []struct {
Name string
Alg string
ID string
}{
{
Name: "public",
ID: "2011-04-29",
Alg: "RS256",
},
{
Name: "public",
ID: "1",
Alg: "RS256",
},
{
Name: "private",
ID: "2011-04-29",
Alg: "RS256",
},
{
Name: "private",
ID: "1",
Alg: "RS256",
},
{
Name: "symmetric",
ID: "sim2",
Alg: "HS256",
},
} {
secretProvidr, err := SecretProvider(
SecretProviderConfig{
URI: "",
AllowInsecure: true,
LocalPath: "./fixtures/" + tc.Name + ".json",
},
nil,
)
if err != nil {
t.Error(err)
}
key, err := secretProvidr.GetKey(tc.ID)
if err != nil {
t.Errorf("[%s] extracting the key %s: %s", tc.Name, tc.ID, err.Error())
}
if key.Algorithm != tc.Alg {
t.Errorf("wrong alg. have: %s, want: %s", key.Algorithm, tc.Alg)
}
}
}

func TestJWK_cyperfile(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()

url := "base64key://smGbjm71Nxd1Ig5FS0wj9SlbzAIrnolCz9bQQ6uAhl4="

cypher, err := secrets.New(ctx, url)
if err != nil {
t.Error(err)
return
}
defer cypher.Close()

plainKey := make([]byte, 32)
rand.Read(plainKey)

cypherKey, err := cypher.EncryptKey(ctx, plainKey)
if err != nil {
t.Error(err)
return
}

b, _ := ioutil.ReadFile("./fixtures/private.json")
cypherText, err := cypher.Encrypt(ctx, b, cypherKey)
if err != nil {
t.Error(err)
return
}
ioutil.WriteFile("./fixtures/private.txt", cypherText, 0666)
defer os.Remove("./fixtures/private.txt")

for k, tc := range []struct {
Alg string
ID string
}{
{
ID: "2011-04-29",
Alg: "RS256",
},
{
ID: "1",
Alg: "RS256",
},
} {
secretProvidr, err := SecretProvider(
SecretProviderConfig{
URI: "",
AllowInsecure: true,
LocalPath: "./fixtures/private.txt",
CipherKey: cypherKey,
SecretURL: url,
},
nil,
)
if err != nil {
t.Error(err)
}
key, err := secretProvidr.GetKey(tc.ID)
if err != nil {
t.Errorf("[%d] extracting the key %s: %s", k, tc.ID, err.Error())
}
if key.Algorithm != tc.Alg {
t.Errorf("wrong alg. have: %s, want: %s", key.Algorithm, tc.Alg)
}
}
}

func TestJWK_cache(t *testing.T) {
cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
if err != nil {
Expand Down Expand Up @@ -160,6 +285,50 @@ func Test_decodeFingerprints(t *testing.T) {
}
}

func TestNewFileKeyCacher(t *testing.T) {
for _, tc := range []struct {
Name string
Alg string
ID string
}{
{
Name: "public",
ID: "2011-04-29",
Alg: "RS256",
},
{
Name: "public",
ID: "1",
},
{
Name: "private",
ID: "2011-04-29",
Alg: "RS256",
},
{
Name: "private",
ID: "1",
},
{
Name: "symmetric",
ID: "sim2",
Alg: "HS256",
},
} {
b, err := ioutil.ReadFile("./fixtures/" + tc.Name + ".json")
if err != nil {
t.Error(err)
}
kc, err := NewFileKeyCacher(b)
if err != nil {
t.Error(err)
}
if _, err := kc.Get(tc.ID); err != nil {
t.Error(err)
}
}
}

func jwkEndpoint(name string) http.HandlerFunc {
data, err := ioutil.ReadFile("./fixtures/" + name + ".json")
return func(rw http.ResponseWriter, _ *http.Request) {
Expand Down
9 changes: 9 additions & 0 deletions jws.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ type SignatureConfig struct {
DisableJWKSecurity bool `json:"disable_jwk_security"`
Fingerprints []string `json:"jwk_fingerprints,omitempty"`
LocalCA string `json:"jwk_local_ca,omitempty"`
LocalPath string `json:"jwk_local_path,omitempty"`
SecretURL string `json:"secret_url,omitempty"`
CipherKey []byte `json:"cypher_key,omitempty"`
}

type SignerConfig struct {
Expand All @@ -45,6 +48,9 @@ type SignerConfig struct {
DisableJWKSecurity bool `json:"disable_jwk_security"`
Fingerprints []string `json:"jwk_fingerprints,omitempty"`
LocalCA string `json:"jwk_local_ca,omitempty"`
LocalPath string `json:"jwk_local_path,omitempty"`
SecretURL string `json:"secret_url,omitempty"`
CipherKey []byte `json:"cypher_key,omitempty"`
}

var (
Expand Down Expand Up @@ -105,6 +111,9 @@ func NewSigner(cfg *config.EndpointConfig, te auth0.RequestTokenExtractor) (*Sig
Fingerprints: decodedFs,
LocalCA: signerCfg.LocalCA,
AllowInsecure: signerCfg.DisableJWKSecurity,
LocalPath: signerCfg.LocalPath,
SecretURL: signerCfg.SecretURL,
CipherKey: signerCfg.CipherKey,
}

sp, err := SecretProvider(spcfg, te)
Expand Down
2 changes: 1 addition & 1 deletion mux/jose_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func newVerifierEndpointCfg(alg, URL string, roles []string) *config.EndpointCon
"audience": []string{"http://api.example.com"},
"issuer": "http://example.com",
"roles": roles,
"propagate-claims": [][]string{[]string{"jti", "x-krakend-jti"}, []string{"sub", "x-krakend-sub"}, []string{"nonexistent", "x-krakend-ne"}},
"propagate-claims": [][]string{{"jti", "x-krakend-jti"}, {"sub", "x-krakend-sub"}, {"nonexistent", "x-krakend-ne"}},
"disable_jwk_security": true,
"cache": true,
},
Expand Down
Loading

0 comments on commit e9123d6

Please sign in to comment.