134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
/*
|
|
Copyright 2023 The Dapr Authors
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package jwks
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/lestrrat-go/jwx/v2/jwk"
|
|
|
|
contribCrypto "github.com/dapr/components-contrib/crypto"
|
|
contribMetadata "github.com/dapr/components-contrib/metadata"
|
|
"github.com/dapr/kit/jwkscache"
|
|
"github.com/dapr/kit/logger"
|
|
)
|
|
|
|
type jwksCrypto struct {
|
|
contribCrypto.LocalCryptoBaseComponent
|
|
|
|
md jwksMetadata
|
|
cache *jwkscache.JWKSCache
|
|
logger logger.Logger
|
|
closed atomic.Bool
|
|
closeCh chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// NewJWKSCrypto returns a new crypto provider based a JWKS, either passed as metadata, or read from a file or HTTP(S) URL.
|
|
// The key argument in methods is the ID of the key in the JWKS ("kid" property).
|
|
func NewJWKSCrypto(logger logger.Logger) contribCrypto.SubtleCrypto {
|
|
k := &jwksCrypto{
|
|
logger: logger,
|
|
closeCh: make(chan struct{}),
|
|
}
|
|
k.RetrieveKeyFn = k.retrieveKeyFromSecretFn
|
|
return k
|
|
}
|
|
|
|
// Init the crypto provider.
|
|
func (k *jwksCrypto) Init(ctx context.Context, metadata contribCrypto.Metadata) error {
|
|
if len(metadata.Properties) == 0 {
|
|
return errors.New("empty metadata properties")
|
|
}
|
|
|
|
// Parse the metadata
|
|
err := k.md.InitWithMetadata(metadata)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load metadata: %w", err)
|
|
}
|
|
|
|
// Init the JWKS cache
|
|
k.cache = jwkscache.NewJWKSCache(k.md.JWKS, k.logger)
|
|
k.cache.SetMinRefreshInterval(k.md.MinRefreshInterval)
|
|
k.cache.SetRequestTimeout(k.md.RequestTimeout)
|
|
|
|
// Start the JWKS cache in background
|
|
startErrCh := make(chan error)
|
|
go func() {
|
|
startErrCh <- k.cache.Start(k.getContext())
|
|
}()
|
|
|
|
// Wait for the cache to be ready
|
|
// Here we use the init context
|
|
err = k.cache.WaitForCacheReady(ctx)
|
|
if err != nil {
|
|
// If we have an initialization error, return
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Returns a context that is canceled when the component is closed.
|
|
func (k *jwksCrypto) getContext() context.Context {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
k.wg.Add(1)
|
|
go func() {
|
|
defer k.wg.Done()
|
|
defer cancel()
|
|
<-k.closeCh
|
|
}()
|
|
return ctx
|
|
}
|
|
|
|
// Close implements the io.Closer interface to close the component
|
|
func (k *jwksCrypto) Close() error {
|
|
if k.closed.CompareAndSwap(false, true) {
|
|
close(k.closeCh)
|
|
}
|
|
|
|
k.wg.Wait()
|
|
return nil
|
|
}
|
|
|
|
// Features returns the features available in this crypto provider.
|
|
func (k *jwksCrypto) Features() []contribCrypto.Feature {
|
|
return []contribCrypto.Feature{} // No Feature supported.
|
|
}
|
|
|
|
// Retrieves a key (public or private or symmetric) from the JWKS
|
|
func (k *jwksCrypto) retrieveKeyFromSecretFn(parentCtx context.Context, kid string) (jwk.Key, error) {
|
|
jwks := k.cache.KeySet()
|
|
if jwks == nil {
|
|
return nil, errors.New("no JWKS loaded")
|
|
}
|
|
|
|
key, found := jwks.LookupKeyID(kid)
|
|
if !found {
|
|
return nil, contribCrypto.ErrKeyNotFound
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
func (k *jwksCrypto) GetComponentMetadata() (metadataInfo contribMetadata.MetadataMap) {
|
|
metadataStruct := jwksMetadata{}
|
|
contribMetadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, contribMetadata.CryptoType)
|
|
return
|
|
}
|