Skip to content

Commit

Permalink
stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Sovietaced committed Dec 31, 2023
1 parent 2e13d2d commit 8367fb0
Show file tree
Hide file tree
Showing 15 changed files with 720 additions and 137 deletions.
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ require (
github.com/benbjohnson/clock v1.3.5
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1
go.opentelemetry.io/otel v1.21.0
go.opentelemetry.io/otel/sdk v1.21.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
golang.org/x/sys v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
21 changes: 21 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,33 @@ github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
12 changes: 12 additions & 0 deletions jwt/keyfunc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package jwt

import (
"context"
"github.com/golang-jwt/jwt/v5"
)

// KeyfuncProvider is a pluggable provider of JWT verifying key functions.
type KeyfuncProvider interface {
// GetKeyfunc gets the JWT verifying key function for an issuer.
GetKeyfunc(ctx context.Context) (jwt.Keyfunc, error)
}
97 changes: 97 additions & 0 deletions jwt/okta/okta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package okta

import (
"context"
"fmt"
"github.com/MicahParks/jwkset"
"github.com/MicahParks/keyfunc/v3"
"github.com/golang-jwt/jwt/v5"
"github.com/sovietaced/okta-jwt-verifier/metadata"
"net/http"
"net/url"
"sync"
)

type Options struct {
httpClient *http.Client
}

func WithHttpClient(httpClient *http.Client) Option {
return func(mo *Options) {
mo.httpClient = httpClient
}
}

func defaultOptions() *Options {
opts := &Options{}
return opts
}

// Option for the OktaMetadataProvider
type Option func(*Options)

type KeyfuncProvider struct {
mp metadata.Provider
httpClient *http.Client
storageMap map[string]jwkset.Storage

storageMutex sync.Mutex
}

func NewKeyfuncProvider(mp metadata.Provider, options ...Option) *KeyfuncProvider {
opts := defaultOptions()
for _, option := range options {
option(opts)
}

return &KeyfuncProvider{mp: mp, httpClient: opts.httpClient, storageMap: map[string]jwkset.Storage{}}
}

func (kp *KeyfuncProvider) GetKeyfunc(ctx context.Context) (jwt.Keyfunc, error) {
md, err := kp.mp.GetMetadata(ctx)
if err != nil {
return nil, fmt.Errorf("getting metadata: %w", err)
}

storage, err := kp.getOrCreateStorage(md.JwksUri)
if err != nil {
return nil, fmt.Errorf("getting storage: %w", err)
}

k, err := keyfunc.New(keyfunc.Options{Storage: storage})
if err != nil {
return nil, fmt.Errorf("creating keyfunc: %w", err)
}

return k.Keyfunc, nil
}

func (kp *KeyfuncProvider) getOrCreateStorage(jwksUri string) (jwkset.Storage, error) {
storage, exists := kp.storageMap[jwksUri]
if exists {
return storage, nil
}

// Acquire a lock
kp.storageMutex.Lock()
defer kp.storageMutex.Unlock()

// Check again to protect against races
storage, exists = kp.storageMap[jwksUri]
if exists {
return storage, nil
}

u, err := url.Parse(jwksUri)
if err != nil {
return nil, fmt.Errorf("parsing jwks uri: %w", err)
}

storage, err = jwkset.NewStorageFromHTTP(u, jwkset.HTTPClientStorageOptions{Client: kp.httpClient})
if err != nil {
return nil, fmt.Errorf("creating http storage: %w", err)
}

kp.storageMap[jwksUri] = storage
return storage, nil
}
63 changes: 63 additions & 0 deletions jwt/okta/okta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package okta

import (
"context"
"crypto/rand"
"crypto/rsa"
"github.com/golang-jwt/jwt/v5"
"github.com/sovietaced/okta-jwt-verifier/jwt/okta/oktatest"
"github.com/sovietaced/okta-jwt-verifier/metadata"
"github.com/stretchr/testify/require"
"testing"
)

func TestKeyfuncProvider(t *testing.T) {

// Generate RSA key.
pk, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

ctx := context.Background()

t.Run("get keyfunc", func(t *testing.T) {
uri := oktatest.ServeJwks(t, ctx, pk)

mp := &oktatest.StaticMetadataProvider{
Md: metadata.Metadata{
JwksUri: uri,
},
}

kp := NewKeyfuncProvider(mp)

keyfunc, err := kp.GetKeyfunc(ctx)
require.NoError(t, err)
validateKeyfunc(t, keyfunc, pk)
})

t.Run("get keyfunc and verify cached", func(t *testing.T) {

})

t.Run("get keyfunc and validate tracing", func(t *testing.T) {

})

t.Run("get keyfunc and metadata provider errors", func(t *testing.T) {

})

t.Run("get keyfunc and jwks uri is invalid", func(t *testing.T) {

})
}

func validateKeyfunc(t *testing.T, keyfunc jwt.Keyfunc, pk *rsa.PrivateKey) {
token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{})
token.Header["kid"] = oktatest.KID
tokenString, err := token.SignedString(pk)
require.NoError(t, err)

_, err = jwt.Parse(tokenString, keyfunc)
require.NoError(t, err)
}
49 changes: 49 additions & 0 deletions jwt/okta/oktatest/test_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package oktatest

import (
"context"
"crypto/rsa"
"github.com/MicahParks/jwkset"
"github.com/sovietaced/okta-jwt-verifier/metadata"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"testing"
)

const (
KID = "test"
)

type StaticMetadataProvider struct {
Md metadata.Metadata
}

func (smp *StaticMetadataProvider) GetMetadata(ctx context.Context) (metadata.Metadata, error) {
return smp.Md, nil
}

func ServeJwks(t *testing.T, ctx context.Context, priv *rsa.PrivateKey) string {
serverStore := jwkset.NewMemoryStorage()
md := jwkset.JWKMetadataOptions{
KID: KID,
}
jwkOptions := jwkset.JWKOptions{
Metadata: md,
}
jwk, err := jwkset.NewJWKFromKey(priv, jwkOptions)
require.NoError(t, err)

err = serverStore.KeyWrite(ctx, jwk)
require.NoError(t, err)

rawJWKS, err := serverStore.JSONPrivate(ctx)
require.NoError(t, err)

svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(rawJWKS)
}))
t.Cleanup(svr.Close)

return svr.URL
}
66 changes: 0 additions & 66 deletions keyfunc.go

This file was deleted.

2 changes: 1 addition & 1 deletion metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type Provider interface {
GetMetadata(ctx context.Context) (Metadata, error)
}

// Metadata represents the OIDC metadata response from Okta.
// Metadata represents the OIDC metadata response from Okta. We only care about the JWKS URI.
// See: https://developer.okta.com/docs/reference/api/oidc/#well-known-openid-configuration
type Metadata struct {
JwksUri string `json:"jwks_uri"`
Expand Down
1 change: 1 addition & 0 deletions metadata/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func WithClock(clock clock.Clock) Option {
mo.clock = clock
}
}

func defaultOptions() *Options {
opts := &Options{}
WithHttpClient(http.DefaultClient)(opts)
Expand Down
Loading

0 comments on commit 8367fb0

Please sign in to comment.