mirror of
https://github.com/zitadel/oidc.git
synced 2026-05-01 07:29:43 -05:00
refactor: deprecate proprietary key file use for JWT Profile (#801)
While reviewing #750, we noticed that the `KeyFile` struct and corresponding methods are proprietary to Zitadel and should have never been part of the pure OIDC library. This PR deprecates the corresponding parts. For users of Zitadel, the corresponding code is moved to zitadel/zitadel-go#516 ### Definition of Ready - [x] I am happy with the code - [x] Short description of the feature/issue is added in the pr description - [x] PR is linked to the corresponding user story - [x] Acceptance criteria are met - [ ] All open todos and follow ups are defined in a new ticket and justified - [ ] Deviations from the acceptance criteria and design are agreed with the PO and documented. - [x] No debug or dead code - [x] My code has no repetitions - [x] Critical parts are tested automatically - [ ] Where possible E2E tests are implemented - [x] Documentation/examples are up-to-date - [x] All non-functional requirements are met - [x] Functionality of the acceptance criteria is checked manually on the dev system.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client/rp"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
@@ -30,6 +31,7 @@ func main() {
|
||||
clientID := os.Getenv("CLIENT_ID")
|
||||
clientSecret := os.Getenv("CLIENT_SECRET")
|
||||
keyPath := os.Getenv("KEY_PATH")
|
||||
keyID := os.Getenv("KEY_ID")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
port := os.Getenv("PORT")
|
||||
scopes := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
@@ -71,7 +73,11 @@ func main() {
|
||||
options = append(options, rp.WithPKCE(cookieHandler))
|
||||
}
|
||||
if keyPath != "" {
|
||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(keyPath)))
|
||||
signingKey, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error reading key file %s", err.Error())
|
||||
}
|
||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyAndKeyID(signingKey, keyID)))
|
||||
}
|
||||
if pkce {
|
||||
options = append(options, rp.WithPKCE(cookieHandler))
|
||||
|
||||
@@ -60,6 +60,7 @@ func main() {
|
||||
clientID := os.Getenv("CLIENT_ID")
|
||||
clientSecret := os.Getenv("CLIENT_SECRET")
|
||||
keyPath := os.Getenv("KEY_PATH")
|
||||
keyID := os.Getenv("KEY_ID")
|
||||
issuer := os.Getenv("ISSUER")
|
||||
scopes := strings.Split(os.Getenv("SCOPES"), " ")
|
||||
|
||||
@@ -70,7 +71,11 @@ func main() {
|
||||
options = append(options, rp.WithPKCE(cookieHandler))
|
||||
}
|
||||
if keyPath != "" {
|
||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyPath(keyPath)))
|
||||
signingKey, err := os.ReadFile(keyPath)
|
||||
if err != nil {
|
||||
logrus.Fatalf("error reading key file %s", err.Error())
|
||||
}
|
||||
options = append(options, rp.WithJWTProfile(rp.SignerFromKeyAndKeyID(signingKey, keyID)))
|
||||
}
|
||||
|
||||
provider, err := rp.NewRelyingPartyOIDC(ctx, issuer, clientID, clientSecret, "", scopes, options...)
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"type":"serviceaccount","keyId":"key1","key":"-----BEGIN RSA PRIVATE KEY-----\nMIICXgIBAAKBgQD21E+180rCAzp15zy2X/JOYYHtxYhF51pWCsITeChJd7sFWxp1\ntxSHTiomQYBiBWgcCavsdu/VLPQJhO3PTIyglxc1XRGsM48oDT5MkFsAVDvbjuWk\nF0lstQyw4pr8Wg0Ecf1aL6YlvVKB9h5rAgZ9T+elNJ7q5takMAvNhu7zMQIDAQAB\nAoGAeLRw2qjEaUZM43WWchVPmFcEw/MyZgTyX1tZd03uXacolUDtGp3ScyydXiHw\nF39PX063fabYOCaInNMdvJ9RsQz2OcZuS/K6NOmWhzBfLgs4Y1tU6ijoY/gBjHgu\nCV0KjvoWIfEtKl/On/wTrAnUStFzrc7U4dpKFP1fy2ZTTnECQQD8aP2QOxmKUyfg\nBAjfonpkrNeaTRNwTULTvEHFiLyaeFd1PAvsDiKZtpk6iHLb99mQZkVVtAK5qgQ4\n1OI72jkVAkEA+lcAamuZAM+gIiUhbHA7BfX9OVgyGDD2tx5g/kxhMUmK6hIiO6Ul\n0nw5KfrCEUU3AzrM7HejUg3q61SYcXTgrQJBALhrzbhwNf0HPP9Ec2dSw7KDRxSK\ndEV9bfJefn/hpEwI2X3i3aMfwNAmxlYqFCH8OY5z6vzvhX46ZtNPV+z7SPECQQDq\nApXi5P27YlpgULEzup2R7uZsymLZdjvJ5V3pmOBpwENYlublNnVqkrCk60CqADdy\nj26rxRIoS9ZDcWqm9AhpAkEAyrNXBMJh08ghBMb3NYPFfr/bftRJSrGjhBPuJ5qr\nXzWaXhYVMMh3OSAwzHBJbA1ffdQJuH2ebL99Ur5fpBcbVw==\n-----END RSA PRIVATE KEY-----\n","userId":"service"}
|
||||
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQD21E+180rCAzp15zy2X/JOYYHtxYhF51pWCsITeChJd7sFWxp1
|
||||
txSHTiomQYBiBWgcCavsdu/VLPQJhO3PTIyglxc1XRGsM48oDT5MkFsAVDvbjuWk
|
||||
F0lstQyw4pr8Wg0Ecf1aL6YlvVKB9h5rAgZ9T+elNJ7q5takMAvNhu7zMQIDAQAB
|
||||
AoGAeLRw2qjEaUZM43WWchVPmFcEw/MyZgTyX1tZd03uXacolUDtGp3ScyydXiHw
|
||||
F39PX063fabYOCaInNMdvJ9RsQz2OcZuS/K6NOmWhzBfLgs4Y1tU6ijoY/gBjHgu
|
||||
CV0KjvoWIfEtKl/On/wTrAnUStFzrc7U4dpKFP1fy2ZTTnECQQD8aP2QOxmKUyfg
|
||||
BAjfonpkrNeaTRNwTULTvEHFiLyaeFd1PAvsDiKZtpk6iHLb99mQZkVVtAK5qgQ4
|
||||
1OI72jkVAkEA+lcAamuZAM+gIiUhbHA7BfX9OVgyGDD2tx5g/kxhMUmK6hIiO6Ul
|
||||
0nw5KfrCEUU3AzrM7HejUg3q61SYcXTgrQJBALhrzbhwNf0HPP9Ec2dSw7KDRxSK
|
||||
dEV9bfJefn/hpEwI2X3i3aMfwNAmxlYqFCH8OY5z6vzvhX46ZtNPV+z7SPECQQDq
|
||||
ApXi5P27YlpgULEzup2R7uZsymLZdjvJ5V3pmOBpwENYlublNnVqkrCk60CqADdy
|
||||
j26rxRIoS9ZDcWqm9AhpAkEAyrNXBMJh08ghBMb3NYPFfr/bftRJSrGjhBPuJ5qr
|
||||
XzWaXhYVMMh3OSAwzHBJbA1ffdQJuH2ebL99Ur5fpBcbVw==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// serviceKey1 is a public key which will be used for the JWT Profile Authorization Grant
|
||||
// the corresponding private key is in the service-key1.json (for demonstration purposes)
|
||||
// the corresponding private key is in the service-key1.pem (for demonstration purposes)
|
||||
var serviceKey1 = &rsa.PublicKey{
|
||||
N: func() *big.Int {
|
||||
n, _ := new(big.Int).SetString("00f6d44fb5f34ac2033a75e73cb65ff24e6181edc58845e75a560ac21378284977bb055b1a75b714874e2a2641806205681c09abec76efd52cf40984edcf4c8ca09717355d11ac338f280d3e4c905b00543bdb8ee5a417496cb50cb0e29afc5a0d0471fd5a2fa625bd5281f61e6b02067d4fe7a5349eeae6d6a4300bcd86eef331", 16)
|
||||
@@ -105,7 +105,7 @@ func NewStorageWithClients(userStore UserStore, clients map[string]*Client) *Sto
|
||||
services: map[string]Service{
|
||||
userStore.ExampleClientID(): {
|
||||
keys: map[string]*rsa.PublicKey{
|
||||
"key1": serviceKey1,
|
||||
ServiceUserKeyID: serviceKey1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,6 +9,13 @@ import (
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
const (
|
||||
// ServiceUserID is the ID of the service user.
|
||||
ServiceUserID = "service"
|
||||
// ServiceUserKeyID is the key ID of the service user.
|
||||
ServiceUserKeyID = "key1"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string
|
||||
Username string
|
||||
@@ -85,7 +92,7 @@ func NewUserStore(issuer string) UserStore {
|
||||
|
||||
// ExampleClientID is only used in the example server
|
||||
func (u userStore) ExampleClientID() string {
|
||||
return "service"
|
||||
return ServiceUserID
|
||||
}
|
||||
|
||||
func (u userStore) GetUserByID(id string) *User {
|
||||
|
||||
@@ -10,6 +10,8 @@ const (
|
||||
applicationKey = "application"
|
||||
)
|
||||
|
||||
// Deprecated: use [github.com/zitadel/zitadel-go/v3/pkg/client.KeyFile] instead.
|
||||
// The type will be removed in the next major release.
|
||||
type KeyFile struct {
|
||||
Type string `json:"type"` // serviceaccount or application
|
||||
KeyID string `json:"keyId"`
|
||||
@@ -23,6 +25,8 @@ type KeyFile struct {
|
||||
ClientID string `json:"clientId"`
|
||||
}
|
||||
|
||||
// Deprecated: use [github.com/zitadel/zitadel-go/v3/pkg/client.ConfigFromKeyFile] instead.
|
||||
// The type will be removed in the next major release.
|
||||
func ConfigFromKeyFile(path string) (*KeyFile, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
@@ -31,6 +35,8 @@ func ConfigFromKeyFile(path string) (*KeyFile, error) {
|
||||
return ConfigFromKeyFileData(data)
|
||||
}
|
||||
|
||||
// Deprecated: use [github.com/zitadel/zitadel-go/v3/pkg/client.ConfigFromKeyFileData] instead.
|
||||
// The type will be removed in the next major release.
|
||||
func ConfigFromKeyFileData(data []byte) (*KeyFile, error) {
|
||||
var f KeyFile
|
||||
if err := json.Unmarshal(data, &f); err != nil {
|
||||
|
||||
@@ -34,6 +34,9 @@ type jwtProfileTokenSource struct {
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key from jsonFile.
|
||||
//
|
||||
// The passed context is only used for the call to the Discover endpoint.
|
||||
//
|
||||
// Deprecated: use [github.com/zitadel/zitadel-go/v3/pkg/client.ConfigFromKeyFileData] instead.
|
||||
// The function will be removed in the next major release.
|
||||
func NewJWTProfileTokenSourceFromKeyFile(ctx context.Context, issuer, jsonFile string, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFile(jsonFile)
|
||||
if err != nil {
|
||||
@@ -47,6 +50,9 @@ func NewJWTProfileTokenSourceFromKeyFile(ctx context.Context, issuer, jsonFile s
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key in jsonData.
|
||||
//
|
||||
// The passed context is only used for the call to the Discover endpoint.
|
||||
//
|
||||
// Deprecated: use [github.com/zitadel/zitadel-go/v3/pkg/client.ConfigFromKeyFileData] instead.
|
||||
// The function will be removed in the next major release.
|
||||
func NewJWTProfileTokenSourceFromKeyFileData(ctx context.Context, issuer string, jsonData []byte, scopes []string, options ...func(source *jwtProfileTokenSource)) (TokenSource, error) {
|
||||
keyData, err := client.ConfigFromKeyFileData(jsonData)
|
||||
if err != nil {
|
||||
@@ -55,7 +61,7 @@ func NewJWTProfileTokenSourceFromKeyFileData(ctx context.Context, issuer string,
|
||||
return NewJWTProfileTokenSource(ctx, issuer, keyData.UserID, keyData.KeyID, []byte(keyData.Key), scopes, options...)
|
||||
}
|
||||
|
||||
// NewJWTProfileSource returns an implementation of oauth2.TokenSource
|
||||
// NewJWTProfileTokenSource returns an implementation of oauth2.TokenSource
|
||||
// It will request a token using the OAuth2 JWT Profile Grant,
|
||||
// therefore sending an `assertion` by singing a JWT with the provided private key.
|
||||
//
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
httphelper "github.com/zitadel/oidc/v3/pkg/http"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
@@ -324,7 +325,7 @@ func WithVerifierOpts(opts ...VerifierOption) Option {
|
||||
|
||||
// WithClientKey specifies the path to the key.json to be used for the JWT Profile Client Authentication on the token endpoint
|
||||
//
|
||||
// deprecated: use WithJWTProfile(SignerFromKeyPath(path)) instead
|
||||
// Deprecated: use WithJWTProfile(SignerFromKeyPath(path)), resp. WithJWTProfile(SignerFromKeyAndKeyID(key, keyID) instead.
|
||||
func WithClientKey(path string) Option {
|
||||
return WithJWTProfile(SignerFromKeyPath(path))
|
||||
}
|
||||
@@ -363,6 +364,8 @@ func WithSigningAlgsFromDiscovery() Option {
|
||||
|
||||
type SignerFromKey func() (jose.Signer, error)
|
||||
|
||||
// Deprecated: use [SignerFromKeyAndKeyID] instead.
|
||||
// The function will be removed in the next major release.
|
||||
func SignerFromKeyPath(path string) SignerFromKey {
|
||||
return func() (jose.Signer, error) {
|
||||
config, err := client.ConfigFromKeyFile(path)
|
||||
@@ -373,6 +376,8 @@ func SignerFromKeyPath(path string) SignerFromKey {
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: use [SignerFromKeyAndKeyID] instead.
|
||||
// The function will be removed in the next major release.
|
||||
func SignerFromKeyFile(fileData []byte) SignerFromKey {
|
||||
return func() (jose.Signer, error) {
|
||||
config, err := client.ConfigFromKeyFileData(fileData)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -14,21 +15,22 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/oidc/v3/example/server/storage"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
)
|
||||
|
||||
func jwtProfile() (string, error) {
|
||||
keyData, err := client.ConfigFromKeyFile("../../example/server/service-key1.json")
|
||||
data, err := os.ReadFile("../../example/server/service-key1.pem")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signer, err := client.NewSignerFromPrivateKeyByte([]byte(keyData.Key), keyData.KeyID)
|
||||
signer, err := client.NewSignerFromPrivateKeyByte(data, storage.ServiceUserKeyID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return client.SignedJWTProfileAssertion(keyData.UserID, []string{testIssuer}, time.Hour, signer)
|
||||
return client.SignedJWTProfileAssertion(storage.ServiceUserID, []string{testIssuer}, time.Hour, signer)
|
||||
}
|
||||
|
||||
func TestServerRoutes(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user