From cdcaf421917514d30797d10084d831afe44dd2e3 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Tue, 19 Apr 2022 07:51:59 +0200 Subject: [PATCH] feat: provide jwt profile token source for clients (#83) * feat: provide jwt profile token source for clients * let deprecated function implement replacement * let deprecated function implement replacement --- example/admin/admin.go | 7 +++--- example/auth/auth.go | 8 +++---- example/mgmt/mgmt.go | 4 ++-- pkg/client/middleware/auth.go | 35 ++++++++++++++++++++++++--- pkg/client/zitadel/client.go | 45 +++++++++++++++++++++++------------ 5 files changed, 70 insertions(+), 29 deletions(-) diff --git a/example/admin/admin.go b/example/admin/admin.go index fa33a41..ffc2536 100644 --- a/example/admin/admin.go +++ b/example/admin/admin.go @@ -21,11 +21,10 @@ func main() { //create a client for the admin api providing: //- scopes (including the ZITADEL project ID), - //- path to your key json (if not provided by environment variable) + //- a JWT Profile source token (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH client, err := admin.NewClient( - []string{oidc.ScopeOpenID, zitadel.ScopeProjectID("124406231057084973")}, - zitadel.WithCustomURL("http://localhost:50002/oauth/v2", "localhost:50001"), - zitadel.WithInsecure(), + []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, + //zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")), ) if err != nil { log.Fatalln("could not create client", err) diff --git a/example/auth/auth.go b/example/auth/auth.go index f832217..87a5088 100644 --- a/example/auth/auth.go +++ b/example/auth/auth.go @@ -14,12 +14,10 @@ import ( func main() { //create a client for the auth api providing: //- scopes (including the ZITADEL project ID), - //- path to your key json (if not provided by environment variable) + //- a JWT Profile source token (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH client, err := auth.NewClient( - []string{oidc.ScopeOpenID, zitadel.ScopeProjectID("124406231057084973")}, - zitadel.WithCustomURL("http://localhost:50002/oauth/v2", "localhost:50001"), - zitadel.WithInsecure(), - //zitadel.WithKeyPath("key.json"), + []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, + //zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")), ) if err != nil { log.Fatalln("could not create client", err) diff --git a/example/mgmt/mgmt.go b/example/mgmt/mgmt.go index 4d8f566..d0524ac 100644 --- a/example/mgmt/mgmt.go +++ b/example/mgmt/mgmt.go @@ -22,12 +22,12 @@ func main() { //create a client for the management api providing: //- scopes (including the ZITADEL project ID), - //- path to your key json (if not provided by environment variable) + //- a JWT Profile token source (e.g. path to your key json), if not provided, the file will be read from the path set in env var ZITADEL_KEY_PATH //- id of the organisation where your calls will be executed //(default is the resource owner / organisation of the calling user, can also be provided for every call separately) client, err := management.NewClient( []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, - //zitadel.WithKeyPath("key.json"), + //zitadel.WithJWTProfileTokenSource(middleware.JWTProfileFromPath("key.json")), //zitadel.WithOrgID(*orgID), ) if err != nil { diff --git a/pkg/client/middleware/auth.go b/pkg/client/middleware/auth.go index becf16a..17f2e40 100644 --- a/pkg/client/middleware/auth.go +++ b/pkg/client/middleware/auth.go @@ -22,11 +22,31 @@ type AuthInterceptor struct { oauth2.TokenSource } -//NewAuthInterceptor creates an interceptor which authenticates a service account with JWT Profile using a key.json. +type JWTProfileTokenSource func(issuer string, scopes []string) (oauth2.TokenSource, error) + +func JWTProfileFromPath(keyPath string) JWTProfileTokenSource { + return func(issuer string, scopes []string) (oauth2.TokenSource, error) { + return profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes) + } +} + +func JWTProfileFromFileData(fileData []byte) JWTProfileTokenSource { + return func(issuer string, scopes []string) (oauth2.TokenSource, error) { + return profile.NewJWTProfileTokenSourceFromKeyFileData(issuer, fileData, scopes) + } +} + +func JWTProfileFromKeyAndUserID(key []byte, keyID, userID string) JWTProfileTokenSource { + return func(issuer string, scopes []string) (oauth2.TokenSource, error) { + return profile.NewJWTProfileTokenSource(issuer, userID, keyID, key, scopes) + } +} + +//NewAuthenticator creates an interceptor which authenticates a service account with a provided JWT Profile (using a key.json either as file or data). //There returned token will be used for authorization in all calls //if expired, the token will be automatically refreshed -func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthInterceptor, error) { - ts, err := profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes) +func NewAuthenticator(issuer string, jwtProfileTokenSource JWTProfileTokenSource, scopes ...string) (*AuthInterceptor, error) { + ts, err := jwtProfileTokenSource(issuer, scopes) if err != nil { return nil, err } @@ -35,6 +55,15 @@ func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthIntercep }, nil } +//NewAuthInterceptor creates an interceptor which authenticates a service account with JWT Profile using a key.json. +//There returned token will be used for authorization in all calls +//if expired, the token will be automatically refreshed +// +// Deprecated: use NewAuthenticator(issuer, JWTProfileFromPath(keyPath), scopes...) instead +func NewAuthInterceptor(issuer, keyPath string, scopes ...string) (*AuthInterceptor, error) { + return NewAuthenticator(issuer, JWTProfileFromPath(keyPath), scopes...) +} + func (interceptor *AuthInterceptor) Unary() grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { authCtx, err := interceptor.setToken(ctx) diff --git a/pkg/client/zitadel/client.go b/pkg/client/zitadel/client.go index 3d0fd28..3fce3d5 100644 --- a/pkg/client/zitadel/client.go +++ b/pkg/client/zitadel/client.go @@ -4,6 +4,8 @@ import ( "crypto/x509" "strings" + "github.com/caos/oidc/pkg/client/profile" + "golang.org/x/oauth2" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -12,21 +14,21 @@ import ( ) type Connection struct { - issuer string - api string - keyPath string - scopes []string - orgID string - insecure bool + issuer string + api string + jwtProfileTokenSource middleware.JWTProfileTokenSource + scopes []string + orgID string + insecure bool *grpc.ClientConn } func NewConnection(scopes []string, options ...Option) (*Connection, error) { c := &Connection{ - issuer: client.Issuer, - api: client.API, - keyPath: middleware.OSKeyPath(), - scopes: scopes, + issuer: client.Issuer, + api: client.API, + jwtProfileTokenSource: middleware.JWTProfileFromPath(middleware.OSKeyPath()), + scopes: scopes, } for _, option := range options { @@ -35,7 +37,7 @@ func NewConnection(scopes []string, options ...Option) (*Connection, error) { } } - unaryInterceptors, streamInterceptors, err := interceptors(c.issuer, c.keyPath, c.orgID, c.scopes) + unaryInterceptors, streamInterceptors, err := interceptors(c.issuer, c.orgID, c.scopes, c.jwtProfileTokenSource) if err != nil { return nil, err } @@ -61,8 +63,8 @@ func NewConnection(scopes []string, options ...Option) (*Connection, error) { return c, nil } -func interceptors(issuer, keyPath, orgID string, scopes []string) ([]grpc.UnaryClientInterceptor, []grpc.StreamClientInterceptor, error) { - auth, err := middleware.NewAuthInterceptor(issuer, keyPath, scopes...) +func interceptors(issuer, orgID string, scopes []string, jwtProfileTokenSource middleware.JWTProfileTokenSource) ([]grpc.UnaryClientInterceptor, []grpc.StreamClientInterceptor, error) { + auth, err := middleware.NewAuthenticator(issuer, jwtProfileTokenSource, scopes...) if err != nil { return nil, nil, err } @@ -111,11 +113,24 @@ func WithCustomURL(issuer, api string) func(*Connection) error { } } -//WithKeyPath sets the path to the key.json used for authentication +//WithKeyPath sets the path to the key.json used for the authentication //if not set env var ZITADEL_KEY_PATH will be used +// +//Deprecated: use WithJWTProfileTokenSource(middleware.JWTProfileFromPath(keyPath)) instead func WithKeyPath(keyPath string) func(*Connection) error { return func(client *Connection) error { - client.keyPath = keyPath + client.jwtProfileTokenSource = func(issuer string, scopes []string) (oauth2.TokenSource, error) { + return profile.NewJWTProfileTokenSourceFromKeyFile(issuer, keyPath, scopes) + } + return nil + } +} + +//WithJWTProfileTokenSource sets the provider used for the authentication +//if not set, the key file will be read from the path set in env var ZITADEL_KEY_PATH +func WithJWTProfileTokenSource(provider middleware.JWTProfileTokenSource) func(*Connection) error { + return func(client *Connection) error { + client.jwtProfileTokenSource = provider return nil } }