-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Sovietaced/initial
Initial implementation
- Loading branch information
Showing
18 changed files
with
1,202 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# To get started with Dependabot version updates, you'll need to specify which | ||
# package ecosystems to update and where the package manifests are located. | ||
# Please see the documentation for all configuration options: | ||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates | ||
|
||
version: 2 | ||
updates: | ||
- package-ecosystem: "gomod" # See documentation for possible values | ||
directory: "/" # Location of package manifests | ||
schedule: | ||
interval: "daily" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
name-template: v$NEXT_PATCH_VERSION | ||
tag-template: v$NEXT_PATCH_VERSION | ||
template: | | ||
# What's Changed | ||
$CHANGES | ||
categories: | ||
- title: ⚠️ Breaking Changes | ||
labels: | ||
- 'breaking change' | ||
- title: 🔒 Security | ||
labels: | ||
- 'security' | ||
- title: 🚀 Features | ||
labels: | ||
- 'enhancement' | ||
- 'feature' | ||
- title: 🐛 Bug Fixes | ||
labels: | ||
- 'bug' | ||
- title: 📖 Documentation | ||
labels: | ||
- 'documentation' | ||
- title: 🧹 Housekeeping | ||
labels: | ||
- 'chore' | ||
- 'test flakiness' | ||
- title: 📦 Dependency updates | ||
label: 'dependencies' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
on: [push, pull_request] | ||
name: ci | ||
jobs: | ||
test: | ||
strategy: | ||
matrix: | ||
go-version: [1.21.x] | ||
os: [ubuntu-latest] | ||
runs-on: ${{ matrix.os }} | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- uses: actions/setup-go@v4 | ||
with: | ||
go-version: ${{ matrix.go-version }} | ||
- run: go test ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
name: Release Drafter | ||
|
||
on: | ||
push: | ||
# branches to consider in the event; optional, defaults to all | ||
branches: | ||
- main | ||
# pull_request event is required only for autolabeler | ||
pull_request: | ||
# Only following types are handled by the action, but one can default to all as well | ||
types: [opened, reopened, synchronize] | ||
# pull_request_target event is required for autolabeler to support PRs from forks | ||
# pull_request_target: | ||
# types: [opened, reopened, synchronize] | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
update_release_draft: | ||
permissions: | ||
# write permission is required to create a github release | ||
contents: write | ||
# write permission is required for autolabeler | ||
# otherwise, read permission is required at least | ||
pull-requests: write | ||
runs-on: ubuntu-latest | ||
steps: | ||
# (Optional) GitHub Enterprise requires GHE_HOST variable set | ||
#- name: Set GHE_HOST | ||
# run: | | ||
# echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV | ||
|
||
# Drafts your next Release notes as Pull Requests are merged into "master" | ||
- uses: release-drafter/release-drafter@v5 | ||
# (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml | ||
# with: | ||
# config-name: my-config.yml | ||
# disable-autolabeler: true | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,3 +19,6 @@ | |
|
||
# Go workspace file | ||
go.work | ||
|
||
# IntelliJ | ||
.idea/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,8 @@ | ||
# okta-jwt-verifier | ||
# okta-jwt-verifier | ||
|
||
[![Test](https://github.com/sovietaced/okta-jwt-verifier/actions/workflows/ci.yml/badge.svg)](https://github.com/sovietaced/okta-jwt-verifier/actions/workflows/ci.yml) | ||
[![GoDoc](https://godoc.org/github.com/sovietaced/okta-jwt-verifier?status.png)](http://godoc.org/github.com/sovietaced/okta-jwt-verifier) | ||
[![Go Report](https://goreportcard.com/badge/github.com/sovietaced/okta-jwt-verifier)](https://goreportcard.com/report/github.com/sovietaced/okta-jwt-verifier) | ||
|
||
Alternative implementation to the official [okta-jwt-verifier](https://github.com/okta/okta-jwt-verifier-golang) that | ||
includes support for telemetry (ie. OpenTelemetry), minimizing operational latency, and testability. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module github.com/sovietaced/okta-jwt-verifier | ||
|
||
go 1.21.5 | ||
|
||
require ( | ||
github.com/MicahParks/jwkset v0.5.4 | ||
github.com/MicahParks/keyfunc/v3 v3.1.1 | ||
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
github.com/MicahParks/jwkset v0.5.4 h1:59s9OUNIKF3g+IXYm3pa4vPXXEudRNetyy3+H6KpKdw= | ||
github.com/MicahParks/jwkset v0.5.4/go.mod h1:fOx7dCX+XgPDzcRbZzi9DMY3vyebWXmsz7XPqstr3ms= | ||
github.com/MicahParks/keyfunc/v3 v3.1.1 h1:ghC5jcuU4/TTQQ9Ns7TEVuhnscQOH+WL4//Jmsy5/DA= | ||
github.com/MicahParks/keyfunc/v3 v3.1.1/go.mod h1:Qmrhb9tkHX1i/kCiLAPDOCWIEfN9yq7u/tkP16lmLL8= | ||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= | ||
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= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package keyfunc | ||
|
||
import ( | ||
"context" | ||
"github.com/golang-jwt/jwt/v5" | ||
) | ||
|
||
// Provider is a pluggable provider of JWT verifying key functions. | ||
type Provider interface { | ||
// GetKeyfunc gets the JWT verifying key function for an issuer. | ||
GetKeyfunc(ctx context.Context) (jwt.Keyfunc, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
package okta | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/MicahParks/keyfunc/v3" | ||
"github.com/benbjohnson/clock" | ||
"github.com/golang-jwt/jwt/v5" | ||
"github.com/sovietaced/okta-jwt-verifier/metadata" | ||
"io" | ||
"net/http" | ||
"sync" | ||
"time" | ||
) | ||
|
||
// Options are configurable options for the KeyfuncProvider. | ||
type Options struct { | ||
httpClient *http.Client | ||
clock clock.Clock | ||
cacheTtl time.Duration | ||
} | ||
|
||
// WithHttpClient allows for a configurable http client. | ||
func WithHttpClient(httpClient *http.Client) Option { | ||
return func(mo *Options) { | ||
mo.httpClient = httpClient | ||
} | ||
} | ||
|
||
func withClock(clock clock.Clock) Option { | ||
return func(mo *Options) { | ||
mo.clock = clock | ||
} | ||
} | ||
|
||
// WithCacheTtl specifies the TTL on the Okta JWK set. | ||
func WithCacheTtl(ttl time.Duration) Option { | ||
return func(mo *Options) { | ||
mo.cacheTtl = ttl | ||
} | ||
} | ||
|
||
func defaultOptions() *Options { | ||
opts := &Options{} | ||
WithHttpClient(http.DefaultClient)(opts) | ||
withClock(clock.New())(opts) | ||
WithCacheTtl(5 * time.Minute)(opts) | ||
return opts | ||
} | ||
|
||
// Option for the KeyfuncProvider | ||
type Option func(*Options) | ||
|
||
type cachedKeyfunc struct { | ||
expiration time.Time | ||
keyfunc jwt.Keyfunc | ||
} | ||
|
||
func newCachedKeyfunc(expiration time.Time, keyfunc jwt.Keyfunc) *cachedKeyfunc { | ||
return &cachedKeyfunc{expiration: expiration, keyfunc: keyfunc} | ||
} | ||
|
||
// KeyfuncProvider implements the keyfunc.KeyfuncProvider and generates JWT validating functions for Okta tokens. | ||
type KeyfuncProvider struct { | ||
mp metadata.Provider | ||
httpClient *http.Client | ||
clock clock.Clock | ||
|
||
keyfuncMutex sync.Mutex | ||
cacheTtl time.Duration | ||
cachedKeyfunc *cachedKeyfunc | ||
} | ||
|
||
// NewKeyfuncProvider creates a new KeyfuncProvider. | ||
func NewKeyfuncProvider(mp metadata.Provider, options ...Option) *KeyfuncProvider { | ||
opts := defaultOptions() | ||
for _, option := range options { | ||
option(opts) | ||
} | ||
|
||
return &KeyfuncProvider{mp: mp, httpClient: opts.httpClient, clock: opts.clock, cacheTtl: opts.cacheTtl} | ||
} | ||
|
||
// GetKeyfunc gets a jwt.Keyfunc based on the OIDC metadata. | ||
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) | ||
} | ||
|
||
keyfunc, err := kp.getOrFetchKeyfunc(ctx, md.JwksUri) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting or fetching keyfunc: %w", err) | ||
} | ||
|
||
return keyfunc, nil | ||
} | ||
|
||
func (kp *KeyfuncProvider) getOrFetchKeyfunc(ctx context.Context, jwksUri string) (jwt.Keyfunc, error) { | ||
cachedKeyfuncCopy := kp.cachedKeyfunc | ||
|
||
if cachedKeyfuncCopy != nil && kp.clock.Now().Before(cachedKeyfuncCopy.expiration) { | ||
return cachedKeyfuncCopy.keyfunc, nil | ||
} | ||
|
||
// Acquire a lock | ||
kp.keyfuncMutex.Lock() | ||
defer kp.keyfuncMutex.Unlock() | ||
|
||
// Check again to protect against races | ||
cachedKeyfuncCopy = kp.cachedKeyfunc | ||
|
||
if cachedKeyfuncCopy != nil && kp.clock.Now().Before(cachedKeyfuncCopy.expiration) { | ||
return cachedKeyfuncCopy.keyfunc, nil | ||
} | ||
|
||
keyfunc, err := kp.fetchKeyfunc(ctx, jwksUri) | ||
if err != nil { | ||
return nil, fmt.Errorf("fetching keyfunc: %w", err) | ||
} | ||
|
||
expiration := kp.clock.Now().Add(kp.cacheTtl) | ||
kp.cachedKeyfunc = newCachedKeyfunc(expiration, keyfunc) | ||
|
||
return keyfunc, nil | ||
} | ||
|
||
func (kp *KeyfuncProvider) fetchKeyfunc(ctx context.Context, jwksUri string) (jwt.Keyfunc, error) { | ||
|
||
httpRequest, err := http.NewRequestWithContext(ctx, http.MethodGet, jwksUri, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("creating new http request: %w", err) | ||
} | ||
resp, err := kp.httpClient.Do(httpRequest) | ||
if err != nil { | ||
return nil, fmt.Errorf("making http request for jwks: %w", err) | ||
} | ||
defer resp.Body.Close() | ||
|
||
ok := resp.StatusCode >= 200 && resp.StatusCode < 300 | ||
if !ok { | ||
return nil, fmt.Errorf("request for jwks %q was not HTTP 2xx OK, it was: %d", jwksUri, resp.StatusCode) | ||
} | ||
|
||
jwkJson, err := io.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read jwks response body: %w", err) | ||
} | ||
|
||
kf, err := keyfunc.NewJWKSetJSON(jwkJson) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create keyfunc from jwk json: %w", err) | ||
} | ||
|
||
return kf.Keyfunc, nil | ||
|
||
} |
Oops, something went wrong.