diff --git a/pkg/apic/auth/apicauth.go b/pkg/apic/auth/apicauth.go index 10056b2e9..8e6f547f0 100644 --- a/pkg/apic/auth/apicauth.go +++ b/pkg/apic/auth/apicauth.go @@ -3,7 +3,6 @@ package auth import ( - "crypto/rsa" "fmt" "io" "net/http" @@ -13,7 +12,6 @@ import ( "github.com/Axway/agent-sdk/pkg/api" "github.com/Axway/agent-sdk/pkg/authz/oauth" "github.com/Axway/agent-sdk/pkg/config" - "github.com/Axway/agent-sdk/pkg/util" "github.com/Axway/agent-sdk/pkg/util/log" ) @@ -90,11 +88,10 @@ func NewPlatformTokenGetter(privKey, publicKey, password, url, aud, clientID str func NewPlatformTokenGetterWithCentralConfig(centralCfg config.CentralConfig) PlatformTokenGetter { return &platformTokenGetter{ cfg: centralCfg, - keyReader: &keyReader{ - privKey: centralCfg.GetAuthConfig().GetPrivateKey(), - publicKey: centralCfg.GetAuthConfig().GetPublicKey(), - password: centralCfg.GetAuthConfig().GetKeyPassword(), - }, + keyReader: oauth.NewKeyReader( + centralCfg.GetAuthConfig().GetPrivateKey(), + centralCfg.GetAuthConfig().GetPublicKey(), + centralCfg.GetAuthConfig().GetKeyPassword()), } } @@ -114,25 +111,10 @@ func staticTokenGetter(token string) funcTokenGetter { return funcTokenGetter(func() (string, error) { return token, nil }) } -type keyReader struct { - privKey string // path to rsa encoded private key, used to sign platform tokens - publicKey string // path to the rsa encoded public key - password string // path to password for private key -} - -func (kr *keyReader) getPrivateKey() (*rsa.PrivateKey, error) { - return util.ReadPrivateKeyFile(kr.privKey, kr.password) -} - -// getPublicKey from the path provided -func (kr *keyReader) getPublicKey() ([]byte, error) { - return util.ReadPublicKeyBytes(kr.publicKey) -} - // platformTokenGetter can get an access token from apic platform. type platformTokenGetter struct { - cfg config.CentralConfig - *keyReader + cfg config.CentralConfig + keyReader oauth.KeyReader axwayIDClient oauth.AuthClient } @@ -142,12 +124,12 @@ func (ptp *platformTokenGetter) Close() error { } func (ptp *platformTokenGetter) initAxwayIDPClient() error { - privateKey, err := ptp.getPrivateKey() + privateKey, err := ptp.keyReader.GetPrivateKey() if err != nil { return err } - publicKey, err := ptp.getPublicKey() + publicKey, err := ptp.keyReader.GetPublicKey() if err != nil { return err } @@ -162,9 +144,10 @@ func (ptp *platformTokenGetter) initAxwayIDPClient() error { oauth.WithServerName("AxwayId"), oauth.WithKeyPairAuth( ptp.cfg.GetAuthConfig().GetClientID(), + "", ptp.cfg.GetAuthConfig().GetAudience(), privateKey, - publicKey)) + publicKey, "")) return err } diff --git a/pkg/apic/auth/apicauth_test.go b/pkg/apic/auth/apicauth_test.go index d1563fd31..cb785f045 100644 --- a/pkg/apic/auth/apicauth_test.go +++ b/pkg/apic/auth/apicauth_test.go @@ -45,74 +45,6 @@ func TestChannelTokenGetterCloses(t *testing.T) { } } -func TestGetKey(t *testing.T) { - cases := []struct { - description string - kr *keyReader - }{ - { - "no password", - &keyReader{ - privKey: "testdata/private_key.pem", - }, - }, - { - "with empty password file", - &keyReader{ - privKey: "testdata/private_key.pem", - password: "testdata/password_empty", - }, - }, - { - "with password", - &keyReader{ - privKey: "testdata/private_key_with_pwd.pem", - password: "testdata/password", - }, - }, - } - - for _, testCase := range cases { - if _, err := testCase.kr.getPrivateKey(); err != nil { - t.Errorf("testcase: %s: failed to read rsa key %s", testCase.description, err) - } - } -} - -func TestGetPublicKey(t *testing.T) { - cases := []struct { - description string - kr *keyReader - }{ - { - "with public key", - &keyReader{ - publicKey: "testdata/public_key", - }, - }, - { - "with private and public key", - &keyReader{ - privKey: "testdata/private_key.pem", - publicKey: "testdata/public_key", - }, - }, - { - "with private, public key, and password", - &keyReader{ - privKey: "testdata/private_key_with_pwd.pem", - password: "testdata/password", - publicKey: "testdata/public_key", - }, - }, - } - for _, testCase := range cases { - if _, err := testCase.kr.getPublicKey(); err != nil { - t.Errorf("testcase: %s: failed to read public key %s", testCase.description, err) - } - } -} - func TestNetAuthenticate(t *testing.T) { aa := NewWithStatic("12345", "abcde") if aa.tenantID != "12345" { diff --git a/pkg/authz/oauth/authclient.go b/pkg/authz/oauth/authclient.go index f01b26d45..fe94fec14 100644 --- a/pkg/authz/oauth/authclient.go +++ b/pkg/authz/oauth/authclient.go @@ -39,7 +39,7 @@ type authClient struct { } type authenticator interface { - prepareRequest() (url.Values, error) + prepareRequest() (url.Values, map[string]string, error) } type tokenResponse struct { @@ -79,10 +79,10 @@ func WithServerName(serverName string) AuthClientOption { } } -// WithClientSecretAuth - sets up to use client secret authenticator -func WithClientSecretAuth(clientID, clientSecret, scope string) AuthClientOption { +// WithClientSecretBasicAuth - sets up to use client secret basic authenticator +func WithClientSecretBasicAuth(clientID, clientSecret, scope string) AuthClientOption { return func(opt *authClientOptions) { - opt.authenticator = &clientSecretAuthenticator{ + opt.authenticator = &clientSecretBasicAuthenticator{ clientID, clientSecret, scope, @@ -90,14 +90,50 @@ func WithClientSecretAuth(clientID, clientSecret, scope string) AuthClientOption } } +// WithClientSecretPostAuth - sets up to use client secret authenticator +func WithClientSecretPostAuth(clientID, clientSecret, scope string) AuthClientOption { + return func(opt *authClientOptions) { + opt.authenticator = &clientSecretPostAuthenticator{ + clientID, + clientSecret, + scope, + } + } +} + +// WithClientSecretJwtAuth - sets up to use client secret authenticator +func WithClientSecretJwtAuth(clientID, clientSecret, scope, issuer, aud string) AuthClientOption { + return func(opt *authClientOptions) { + opt.authenticator = &clientSecretJwtAuthenticator{ + clientID, + clientSecret, + scope, + issuer, + aud, + } + } +} + // WithKeyPairAuth - sets up to use public/private key pair authenticator -func WithKeyPairAuth(clientID, audience string, privKey *rsa.PrivateKey, publicKey []byte) AuthClientOption { +func WithKeyPairAuth(clientID, issuer, audience string, privKey *rsa.PrivateKey, publicKey []byte, scope string) AuthClientOption { return func(opt *authClientOptions) { opt.authenticator = &keyPairAuthenticator{ clientID, + issuer, audience, privKey, publicKey, + scope, + } + } +} + +// WithTLSClientAuth - sets up to use tls_client_auth and self_signed_tls_client_auth authenticator +func WithTLSClientAuth(clientID, scope string) AuthClientOption { + return func(opt *authClientOptions) { + opt.authenticator = &tlsClientAuthenticator{ + clientID: clientID, + scope: scope, } } } @@ -145,12 +181,12 @@ func (c *authClient) fetchNewToken() (string, error) { } func (c *authClient) getOAuthTokens() (*tokenResponse, error) { - req, err := c.options.authenticator.prepareRequest() + req, headers, err := c.options.authenticator.prepareRequest() if err != nil { return nil, err } - resp, err := c.postAuthForm(req) + resp, err := c.postAuthForm(req, headers) if err != nil { return nil, err } @@ -175,14 +211,18 @@ func (c *authClient) getOAuthTokens() (*tokenResponse, error) { return &tokens, nil } -func (c *authClient) postAuthForm(data url.Values) (resp *api.Response, err error) { +func (c *authClient) postAuthForm(data url.Values, headers map[string]string) (resp *api.Response, err error) { + reqHeaders := map[string]string{ + hdrContentType: mimeApplicationFormURLEncoded, + } + for name, value := range headers { + reqHeaders[name] = value + } req := api.Request{ - Method: api.POST, - URL: c.tokenURL, - Body: []byte(data.Encode()), - Headers: map[string]string{ - hdrContentType: mimeApplicationFormURLEncoded, - }, + Method: api.POST, + URL: c.tokenURL, + Body: []byte(data.Encode()), + Headers: reqHeaders, } return c.apiClient.Send(req) } diff --git a/pkg/authz/oauth/authclient_test.go b/pkg/authz/oauth/authclient_test.go index 5abc19358..fd414a2c4 100644 --- a/pkg/authz/oauth/authclient_test.go +++ b/pkg/authz/oauth/authclient_test.go @@ -66,7 +66,7 @@ func TestGetPlatformTokensHttpError(t *testing.T) { s.SetTokenResponse("", 0, http.StatusBadRequest) ac, err := NewAuthClient(s.GetTokenURL(), apiClient, WithServerName("testServer"), - WithClientSecretAuth("invalid_client", "invalid-secrt", "")) + WithClientSecretPostAuth("invalid_client", "invalid-secrt", "")) assert.Nil(t, err) assert.NotNil(t, ac) @@ -78,7 +78,7 @@ func TestGetPlatformTokensHttpError(t *testing.T) { s.SetTokenResponse("", 0, http.StatusBadRequest) ac, err = NewAuthClient(s.GetTokenURL(), apiClient, WithServerName("testServer"), - WithKeyPairAuth("invalid_client", "", privateKey, publicKey)) + WithKeyPairAuth("invalid_client", "", "", privateKey, publicKey, "")) assert.Nil(t, err) assert.NotNil(t, ac) @@ -88,7 +88,7 @@ func TestGetPlatformTokensHttpError(t *testing.T) { s.SetTokenResponse("token", 3*time.Second, http.StatusOK) ac, err = NewAuthClient(s.GetTokenURL(), apiClient, WithServerName("testServer"), - WithKeyPairAuth("invalid_client", "", privateKey, publicKey)) + WithKeyPairAuth("invalid_client", "", "", privateKey, publicKey, "")) assert.Nil(t, err) assert.NotNil(t, ac) @@ -106,7 +106,7 @@ func TestGetPlatformTokensTimeout(t *testing.T) { apiClient := api.NewClientWithTimeout(config.NewTLSConfig(), "", time.Second) ac, err := NewAuthClient(s.URL, apiClient, WithServerName("testServer"), - WithClientSecretAuth("invalid_client", "invalid-secrt", "")) + WithClientSecretPostAuth("invalid_client", "invalid-secrt", "")) assert.Nil(t, err) assert.NotNil(t, ac) diff --git a/pkg/authz/oauth/authservermetadata.go b/pkg/authz/oauth/authservermetadata.go index 882a7c93d..1e41c871b 100644 --- a/pkg/authz/oauth/authservermetadata.go +++ b/pkg/authz/oauth/authservermetadata.go @@ -1,5 +1,12 @@ package oauth +type MTLSEndPointAlias struct { + TokenEndpoint string `json:"token_endpoint,omitempty"` + RegistrationEndpoint string `json:"registration_endpoint,omitempty"` + IntrospectionEndpoint string `json:"introspection_endpoint,omitempty"` + RevocationEndpoint string `json:"revocation_endpoint,omitempty"` +} + // AuthorizationServerMetadata - OAuth metadata from IdP type AuthorizationServerMetadata struct { Issuer string `json:"issuer,omitempty"` @@ -27,4 +34,6 @@ type AuthorizationServerMetadata struct { RequestParameterSupported bool `json:"request_parameter_supported,omitempty"` RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported,omitempty"` + + MTLSEndPointAlias *MTLSEndPointAlias `json:"mtls_endpoint_aliases,omitempty"` } diff --git a/pkg/authz/oauth/clientsecretbasicauthenticator.go b/pkg/authz/oauth/clientsecretbasicauthenticator.go new file mode 100644 index 000000000..60e77d6f9 --- /dev/null +++ b/pkg/authz/oauth/clientsecretbasicauthenticator.go @@ -0,0 +1,28 @@ +package oauth + +import ( + "encoding/base64" + "net/url" +) + +type clientSecretBasicAuthenticator struct { + clientID string + clientSecret string + scope string +} + +func (p *clientSecretBasicAuthenticator) prepareRequest() (url.Values, map[string]string, error) { + v := url.Values{ + metaGrantType: []string{grantClientCredentials}, + } + + if p.scope != "" { + v.Add(metaScope, p.scope) + } + + token := base64.StdEncoding.EncodeToString([]byte(p.clientID + ":" + p.clientSecret)) + headers := map[string]string{ + "Authorization": "Basic " + token, + } + return v, headers, nil +} diff --git a/pkg/authz/oauth/clientsecretjwtauthenticator.go b/pkg/authz/oauth/clientsecretjwtauthenticator.go new file mode 100644 index 000000000..6d9057678 --- /dev/null +++ b/pkg/authz/oauth/clientsecretjwtauthenticator.go @@ -0,0 +1,56 @@ +package oauth + +import ( + "net/url" + "time" + + "github.com/golang-jwt/jwt" + "github.com/google/uuid" +) + +type clientSecretJwtAuthenticator struct { + clientID string + clientSecret string + scope string + issuer string + aud string +} + +// prepareInitialToken prepares a token for an access request +func (p *clientSecretJwtAuthenticator) prepareInitialToken() (string, error) { + now := time.Now() + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ + Issuer: p.issuer, + Subject: p.clientID, + Audience: p.aud, + ExpiresAt: now.Add(60*time.Second).UnixNano() / 1e9, + IssuedAt: now.UnixNano() / 1e9, + Id: uuid.New().String(), + }) + + requestToken, err := token.SignedString([]byte(p.clientSecret)) + if err != nil { + return "", err + } + + return requestToken, nil +} + +func (p *clientSecretJwtAuthenticator) prepareRequest() (url.Values, map[string]string, error) { + requestToken, err := p.prepareInitialToken() + if err != nil { + return nil, nil, err + } + + v := url.Values{ + metaGrantType: []string{grantClientCredentials}, + metaClientID: []string{p.clientID}, + metaClientAssertionType: []string{assertionTypeJWT}, + metaClientAssertion: []string{requestToken}, + } + + if p.scope != "" { + v.Add(metaScope, p.scope) + } + return v, nil, nil +} diff --git a/pkg/authz/oauth/clientsecretauthenticator.go b/pkg/authz/oauth/clientsecretpostauthenticator.go similarity index 63% rename from pkg/authz/oauth/clientsecretauthenticator.go rename to pkg/authz/oauth/clientsecretpostauthenticator.go index e406dbe06..bc68d2a82 100644 --- a/pkg/authz/oauth/clientsecretauthenticator.go +++ b/pkg/authz/oauth/clientsecretpostauthenticator.go @@ -1,14 +1,16 @@ package oauth -import "net/url" +import ( + "net/url" +) -type clientSecretAuthenticator struct { +type clientSecretPostAuthenticator struct { clientID string clientSecret string scope string } -func (p *clientSecretAuthenticator) prepareRequest() (url.Values, error) { +func (p *clientSecretPostAuthenticator) prepareRequest() (url.Values, map[string]string, error) { v := url.Values{ metaGrantType: []string{grantClientCredentials}, metaClientID: []string{p.clientID}, @@ -21,5 +23,5 @@ func (p *clientSecretAuthenticator) prepareRequest() (url.Values, error) { if p.scope != "" { v.Add(metaScope, p.scope) } - return v, nil + return v, nil, nil } diff --git a/pkg/authz/oauth/constants.go b/pkg/authz/oauth/constants.go index 9432144b0..28f84a566 100644 --- a/pkg/authz/oauth/constants.go +++ b/pkg/authz/oauth/constants.go @@ -14,12 +14,6 @@ const ( TypeKeycloak = "keycloak" ) -// IDP Auth type string const -const ( - IDPAuthTypeAccessToken = "accessToken" - IDPAuthTypeClient = "client" -) - const ( defaultServerName = "OAuth server" diff --git a/pkg/authz/oauth/keypairauthenticator.go b/pkg/authz/oauth/keypairauthenticator.go index a9515e8f6..b675bc747 100644 --- a/pkg/authz/oauth/keypairauthenticator.go +++ b/pkg/authz/oauth/keypairauthenticator.go @@ -13,18 +13,23 @@ import ( type keyPairAuthenticator struct { clientID string + issuer string aud string privateKey *rsa.PrivateKey publicKey []byte + scope string } // prepareInitialToken prepares a token for an access request -func (p *keyPairAuthenticator) prepareInitialToken(privateKey interface{}, kid, clientID, aud string) (string, error) { +func (p *keyPairAuthenticator) prepareInitialToken(kid string) (string, error) { now := time.Now() + if p.issuer == "" { + p.issuer = fmt.Sprintf("%s:%s", assertionTypeJWT, p.clientID) + } token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.StandardClaims{ - Issuer: fmt.Sprintf("%s:%s", assertionTypeJWT, clientID), - Subject: clientID, - Audience: aud, + Issuer: p.issuer, + Subject: p.clientID, + Audience: p.aud, ExpiresAt: now.Add(60*time.Second).UnixNano() / 1e9, IssuedAt: now.UnixNano() / 1e9, Id: uuid.New().String(), @@ -32,7 +37,7 @@ func (p *keyPairAuthenticator) prepareInitialToken(privateKey interface{}, kid, token.Header["kid"] = kid - requestToken, err := token.SignedString(privateKey) + requestToken, err := token.SignedString(p.privateKey) if err != nil { return "", err } @@ -40,20 +45,25 @@ func (p *keyPairAuthenticator) prepareInitialToken(privateKey interface{}, kid, return requestToken, nil } -func (p *keyPairAuthenticator) prepareRequest() (url.Values, error) { +func (p *keyPairAuthenticator) prepareRequest() (url.Values, map[string]string, error) { kid, err := util.ComputeKIDFromDER(p.publicKey) if err != nil { - return nil, err + return nil, nil, err } - requestToken, err := p.prepareInitialToken(p.privateKey, kid, p.clientID, p.aud) + requestToken, err := p.prepareInitialToken(kid) if err != nil { - return nil, err + return nil, nil, err } - return url.Values{ + v := url.Values{ metaGrantType: []string{grantClientCredentials}, metaClientAssertionType: []string{assertionTypeJWT}, metaClientAssertion: []string{requestToken}, - }, nil + } + + if p.scope != "" { + v.Add(metaScope, p.scope) + } + return v, nil, nil } diff --git a/pkg/authz/oauth/keyreader.go b/pkg/authz/oauth/keyreader.go new file mode 100644 index 000000000..0319dfb83 --- /dev/null +++ b/pkg/authz/oauth/keyreader.go @@ -0,0 +1,35 @@ +package oauth + +import ( + "crypto/rsa" + + "github.com/Axway/agent-sdk/pkg/util" +) + +type KeyReader interface { + GetPrivateKey() (*rsa.PrivateKey, error) + GetPublicKey() ([]byte, error) +} + +type keyReader struct { + privKey string // path to rsa encoded private key, used to sign platform tokens + publicKey string // path to the rsa encoded public key + password string // path to password for private key +} + +func NewKeyReader(privateKey, publicKey, password string) KeyReader { + return &keyReader{ + privKey: privateKey, + publicKey: publicKey, + password: password, + } +} + +func (kr *keyReader) GetPrivateKey() (*rsa.PrivateKey, error) { + return util.ReadPrivateKeyFile(kr.privKey, kr.password) +} + +// getPublicKey from the path provided +func (kr *keyReader) GetPublicKey() ([]byte, error) { + return util.ReadPublicKeyBytes(kr.publicKey) +} diff --git a/pkg/authz/oauth/keyreader_test.go b/pkg/authz/oauth/keyreader_test.go new file mode 100644 index 000000000..14d4145b3 --- /dev/null +++ b/pkg/authz/oauth/keyreader_test.go @@ -0,0 +1,71 @@ +package oauth + +import "testing" + +func TestGetKey(t *testing.T) { + cases := []struct { + description string + kr *keyReader + }{ + { + "no password", + &keyReader{ + privKey: "../../apic/auth/testdata/private_key.pem", + }, + }, + { + "with empty password file", + &keyReader{ + privKey: "../../apic/auth/testdata/private_key.pem", + password: "../../apic/auth/testdata/password_empty", + }, + }, + { + "with password", + &keyReader{ + privKey: "../../apic/auth/testdata/private_key_with_pwd.pem", + password: "../../apic/auth/testdata/password", + }, + }, + } + + for _, testCase := range cases { + if _, err := testCase.kr.GetPrivateKey(); err != nil { + t.Errorf("testcase: %s: failed to read rsa key %s", testCase.description, err) + } + } +} + +func TestGetPublicKey(t *testing.T) { + cases := []struct { + description string + kr *keyReader + }{ + { + "with public key", + &keyReader{ + publicKey: "../../apic/auth/testdata/public_key", + }, + }, + { + "with private and public key", + &keyReader{ + privKey: "../../apic/auth/testdata/private_key.pem", + publicKey: "../../apic/auth/testdata/public_key", + }, + }, + { + "with private, public key, and password", + &keyReader{ + privKey: "../../apic/auth/testdata/private_key_with_pwd.pem", + password: "../../apic/auth/testdata/password", + publicKey: "../../apic/auth/testdata/public_key", + }, + }, + } + for _, testCase := range cases { + if _, err := testCase.kr.GetPublicKey(); err != nil { + t.Errorf("testcase: %s: failed to read public key %s", testCase.description, err) + } + } +} diff --git a/pkg/authz/oauth/provider.go b/pkg/authz/oauth/provider.go index 2fa8dbb7b..30f1fcea7 100644 --- a/pkg/authz/oauth/provider.go +++ b/pkg/authz/oauth/provider.go @@ -82,10 +82,10 @@ func NewProvider(idp corecfg.IDPConfig, tlsCfg corecfg.TLSConfig, proxyURL strin } p.authServerMetadata = metadata - if p.cfg.GetAuthConfig() != nil && p.cfg.GetAuthConfig().GetType() == IDPAuthTypeClient { - p.authClient, err = NewAuthClient(p.authServerMetadata.TokenEndpoint, apiClient, - WithServerName(idp.GetIDPName()), - WithClientSecretAuth(p.cfg.GetAuthConfig().GetClientID(), p.cfg.GetAuthConfig().GetClientSecret(), p.cfg.GetAuthConfig().GetClientScope())) + + // No OAuth client is needed to request token for access token based authentication to IdP + if p.cfg.GetAuthConfig() != nil && p.cfg.GetAuthConfig().GetType() != corecfg.AccessToken { + p.authClient, err = p.createAuthClient() if err != nil { return nil, err } @@ -113,6 +113,85 @@ func (p *provider) fetchMetadata() (*AuthorizationServerMetadata, error) { } +func (p *provider) createAuthClient() (AuthClient, error) { + switch p.cfg.GetAuthConfig().GetType() { + case corecfg.Client: + fallthrough + case corecfg.ClientSecretPost: + return p.createClientSecretPostAuthClient() + case corecfg.ClientSecretBasic: + return p.createClientSecretBasicAuthClient() + case corecfg.ClientSecretJWT: + return p.createClientSecretJWTAuthClient() + case corecfg.PrivateKeyJWT: + return p.createPrivateKeyJWTAuthClient() + case corecfg.TLSClientAuth: + fallthrough + case corecfg.SelfSignedTLSClientAuth: + return p.createTLSAuthClient() + default: + return nil, fmt.Errorf("%s", "unknown IdP auth type") + } +} + +func (p *provider) createClientSecretPostAuthClient() (AuthClient, error) { + return NewAuthClient(p.GetTokenEndpoint(), p.apiClient, + WithServerName(p.cfg.GetIDPName()), + WithClientSecretPostAuth(p.cfg.GetAuthConfig().GetClientID(), p.cfg.GetAuthConfig().GetClientSecret(), p.cfg.GetAuthConfig().GetClientScope())) +} + +func (p *provider) createClientSecretBasicAuthClient() (AuthClient, error) { + return NewAuthClient(p.GetTokenEndpoint(), p.apiClient, + WithServerName(p.cfg.GetIDPName()), + WithClientSecretBasicAuth(p.cfg.GetAuthConfig().GetClientID(), p.cfg.GetAuthConfig().GetClientSecret(), p.cfg.GetAuthConfig().GetClientScope())) +} + +func (p *provider) createClientSecretJWTAuthClient() (AuthClient, error) { + return NewAuthClient(p.GetTokenEndpoint(), p.apiClient, + WithServerName(p.cfg.GetIDPName()), + WithClientSecretJwtAuth( + p.cfg.GetAuthConfig().GetClientID(), + p.cfg.GetAuthConfig().GetClientSecret(), + p.cfg.GetAuthConfig().GetClientScope(), + p.cfg.GetAuthConfig().GetClientID(), + p.authServerMetadata.Issuer, + )) +} + +func (p *provider) createPrivateKeyJWTAuthClient() (AuthClient, error) { + keyReader := NewKeyReader( + p.cfg.GetAuthConfig().GetPrivateKey(), + p.cfg.GetAuthConfig().GetPublicKey(), + p.cfg.GetAuthConfig().GetKeyPassword(), + ) + privateKey, keyErr := keyReader.GetPrivateKey() + if keyErr != nil { + return nil, keyErr + } + + publicKey, keyErr := keyReader.GetPublicKey() + if keyErr != nil { + return nil, keyErr + } + return NewAuthClient(p.GetTokenEndpoint(), p.apiClient, + WithServerName(p.cfg.GetIDPName()), + WithKeyPairAuth( + p.cfg.GetAuthConfig().GetClientID(), + p.cfg.GetAuthConfig().GetClientID(), + p.authServerMetadata.Issuer, + privateKey, + publicKey, + p.cfg.GetAuthConfig().GetClientScope(), + ), + ) +} + +func (p *provider) createTLSAuthClient() (AuthClient, error) { + return NewAuthClient(p.GetTokenEndpoint(), p.apiClient, + WithServerName(p.cfg.GetIDPName()), + WithTLSClientAuth(p.cfg.GetAuthConfig().GetClientID(), p.cfg.GetAuthConfig().GetClientScope())) +} + // GetName - returns the name of the provider func (p *provider) GetName() string { return p.cfg.GetIDPName() @@ -131,9 +210,19 @@ func (p *provider) GetIssuer() string { return "" } +func (p *provider) useTLSAuth() bool { + if p.cfg.GetAuthConfig() == nil { + return false + } + return p.cfg.GetAuthConfig().GetType() == corecfg.TLSClientAuth || p.cfg.GetAuthConfig().GetType() == corecfg.SelfSignedTLSClientAuth +} + // GetTokenEndpoint - return the token endpoint URL func (p *provider) GetTokenEndpoint() string { if p.authServerMetadata != nil { + if p.useTLSAuth() && p.authServerMetadata.MTLSEndPointAlias != nil && p.authServerMetadata.MTLSEndPointAlias.TokenEndpoint != "" { + return p.authServerMetadata.MTLSEndPointAlias.TokenEndpoint + } return p.authServerMetadata.TokenEndpoint } return "" diff --git a/pkg/authz/oauth/tlsclientauthenticator.go b/pkg/authz/oauth/tlsclientauthenticator.go new file mode 100644 index 000000000..68384313c --- /dev/null +++ b/pkg/authz/oauth/tlsclientauthenticator.go @@ -0,0 +1,20 @@ +package oauth + +import "net/url" + +type tlsClientAuthenticator struct { + clientID string + scope string +} + +func (p *tlsClientAuthenticator) prepareRequest() (url.Values, map[string]string, error) { + v := url.Values{ + metaGrantType: []string{grantClientCredentials}, + metaClientID: []string{p.clientID}, + } + + if p.scope != "" { + v.Add(metaScope, p.scope) + } + return v, nil, nil +} diff --git a/pkg/config/authconfig.go b/pkg/config/authconfig.go index 556b59ce2..fa36d51df 100644 --- a/pkg/config/authconfig.go +++ b/pkg/config/authconfig.go @@ -82,40 +82,30 @@ func (a *AuthConfiguration) validate() { a.validatePublicKey() } -func (a *AuthConfiguration) validatePrivateKey() { - if a.GetPrivateKey() == "" { - exception.Throw(ErrBadConfig.FormatError(pathAuthPrivateKey)) +func validateAuthFileConfig(configKeyName, authFile, dataEnvVar, errMsg string) { + if authFile == "" { + exception.Throw(ErrBadConfig.FormatError(configKeyName)) } else { - if !fileExists(a.GetPrivateKey()) { - privateKeyData := os.Getenv("CENTRAL_AUTH_PRIVATEKEY_DATA") - if privateKeyData == "" { - exception.Throw(ErrBadConfig.FormatError(pathAuthPrivateKey)) + if !fileExists(authFile) && dataEnvVar != "" { + data := os.Getenv(dataEnvVar) + if data == "" { + exception.Throw(ErrBadConfig.FormatError(configKeyName)) } - saveKeyData(a.GetPrivateKey(), privateKeyData) + saveKeyData(authFile, data) } // Validate that the file is readable - if _, err := os.Open(a.GetPrivateKey()); err != nil { - exception.Throw(ErrReadingKeyFile.FormatError("private key", a.GetPrivateKey())) + if _, err := os.Open(authFile); err != nil { + exception.Throw(ErrReadingKeyFile.FormatError(errMsg, authFile)) } } } +func (a *AuthConfiguration) validatePrivateKey() { + validateAuthFileConfig(pathAuthPrivateKey, a.GetPrivateKey(), "CENTRAL_AUTH_PRIVATEKEY_DATA", "private key") +} + func (a *AuthConfiguration) validatePublicKey() { - if a.GetPublicKey() == "" { - exception.Throw(ErrBadConfig.FormatError(pathAuthPublicKey)) - } else { - if !fileExists(a.GetPublicKey()) { - publicKeyData := os.Getenv("CENTRAL_AUTH_PUBLICKEY_DATA") - if publicKeyData == "" { - exception.Throw(ErrBadConfig.FormatError(pathAuthPublicKey)) - } - saveKeyData(a.GetPublicKey(), publicKeyData) - } - // Validate that the file is readable - if _, err := os.Open(a.GetPublicKey()); err != nil { - exception.Throw(ErrReadingKeyFile.FormatError("public key", a.GetPublicKey())) - } - } + validateAuthFileConfig(pathAuthPublicKey, a.GetPublicKey(), "CENTRAL_AUTH_PUBLICKEY_DATA", "public key") } // GetTokenURL - Returns the token URL diff --git a/pkg/config/externalidpconfig.go b/pkg/config/externalidpconfig.go index 154df547c..9d03694d7 100644 --- a/pkg/config/externalidpconfig.go +++ b/pkg/config/externalidpconfig.go @@ -9,8 +9,15 @@ import ( ) const ( - accessToken = "accessToken" - client = "client" + AccessToken = "accessToken" + Client = "client" + ClientSecretBasic = "client_secret_basic" + ClientSecretPost = "client_secret_post" + ClientSecretJWT = "client_secret_jwt" + PrivateKeyJWT = "private_key_jwt" + TLSClientAuth = "tls_client_auth" + SelfSignedTLSClientAuth = "self_signed_tls_client_auth" + propInsecureSkipVerify = "insecureSkipVerify" pathExternalIDP = "agentFeatures.idp" fldName = "name" @@ -27,6 +34,9 @@ const ( fldAuthClientID = "auth.clientId" fldAuthClientSecret = "auth.clientSecret" fldAuthClientScope = "auth.clientScope" + fldAuthPrivateKey = "auth.privateKey" + fldAuthPublicKey = "auth.publicKey" + fldAuthKeyPassword = "auth.keyPassword" fldSSLInsecureSkipVerify = "ssl." + propInsecureSkipVerify fldSSLRootCACertPath = "ssl.rootCACertPath" fldSSLClientCertPath = "ssl.clientCertPath" @@ -52,9 +62,21 @@ var configProperties = []string{ fldSSLRootCACertPath, fldSSLClientCertPath, fldSSLClientKeyPath, + fldAuthPrivateKey, + fldAuthPublicKey, + fldAuthKeyPassword, } -var validIDPAuthType = map[string]bool{accessToken: true, client: true} +var validIDPAuthType = map[string]bool{ + AccessToken: true, + Client: true, + ClientSecretBasic: true, + ClientSecretPost: true, + ClientSecretJWT: true, + PrivateKeyJWT: true, + SelfSignedTLSClientAuth: true, + TLSClientAuth: true, +} // ExternalIDPConfig - type ExternalIDPConfig interface { @@ -118,7 +140,13 @@ type IDPAuthConfig interface { // GetClientScope - Scopes used for requesting the token using the ID client GetClientScope() string // validate - Validates the IDP auth configuration - validate(isMTLS bool) + validate(tlsCfg TLSConfig) + // GetPrivateKey() - private key to be used for private_key_jwt authentication + GetPrivateKey() string + // GetPublicKey() - public key to be used for private_key_jwt authentication + GetPublicKey() string + // GetKeyPassword() - public key to be used for private_key_jwt authentication + GetKeyPassword() string } // IDPConfig - interface for IdP provider config @@ -156,6 +184,9 @@ type IDPAuthConfiguration struct { ClientID string `json:"clientId,omitempty"` ClientSecret string `json:"clientSecret,omitempty"` ClientScope string `json:"clientScope,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` + PublicKey string `json:"publicKey,omitempty"` + KeyPwd string `json:"keyPassword,omitempty"` } // IDPConfiguration - Structure to hold the IdP provider config @@ -241,11 +272,7 @@ func (i *IDPConfiguration) validate() { exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldMetadataURL)) } - isMTLS := false - if i.TLSConfig != nil { - isMTLS = i.TLSConfig.(*TLSConfiguration).ClientCertificatePath != "" && i.TLSConfig.(*TLSConfiguration).ClientKeyPath != "" - } - i.AuthConfig.validate(isMTLS) + i.AuthConfig.validate(i.TLSConfig) } // GetType - type of authentication mechanism to use "accessToken" or "client" @@ -273,26 +300,85 @@ func (i *IDPAuthConfiguration) GetClientScope() string { return i.ClientScope } +// GetPrivateKey - +func (i *IDPAuthConfiguration) GetPrivateKey() string { + return i.PrivateKey +} + +// GetPublicKey - +func (i *IDPAuthConfiguration) GetPublicKey() string { + return i.PublicKey +} + +// GetKeyPassword - +func (i *IDPAuthConfiguration) GetKeyPassword() string { + return i.KeyPwd +} + // validate - Validates the IDP auth configuration -func (i *IDPAuthConfiguration) validate(isMTLS bool) { +func (i *IDPAuthConfiguration) validate(tlsCfg TLSConfig) { if ok := validIDPAuthType[i.GetType()]; !ok { exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthType)) } - if i.GetType() == accessToken && i.GetAccessToken() == "" { + switch i.GetType() { + case AccessToken: + i.validateAccessTokenAuthConfig() + case Client: + fallthrough + case ClientSecretBasic: + fallthrough + case ClientSecretPost: + fallthrough + case ClientSecretJWT: + i.validateClientSecretAuthConfig() + case PrivateKeyJWT: + i.validatePrivateKeyJwtAuthConfig() + case TLSClientAuth: + fallthrough + case SelfSignedTLSClientAuth: + i.validateTLSClientAuthConfig(tlsCfg) + } +} + +func (i *IDPAuthConfiguration) validateAccessTokenAuthConfig() { + if i.GetAccessToken() == "" { exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthAccessToken)) } +} - if i.GetType() == client { - if i.GetClientID() == "" { - exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthClientID)) - } - if !isMTLS { - if i.GetClientSecret() == "" { - exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthClientSecret)) - } - } +func (i *IDPAuthConfiguration) validateClientIDConfig() { + if i.GetClientID() == "" { + exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthClientID)) + } +} + +func (i *IDPAuthConfiguration) validateClientSecretConfig() { + if i.GetClientSecret() == "" { + exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldAuthClientSecret)) + } +} + +func (i *IDPAuthConfiguration) validateClientSecretAuthConfig() { + i.validateClientIDConfig() + i.validateClientSecretConfig() +} + +func (i *IDPAuthConfiguration) validatePrivateKeyJwtAuthConfig() { + i.validateClientIDConfig() + + validateAuthFileConfig(pathExternalIDP+"."+fldAuthPrivateKey, i.PrivateKey, "", "private key") + validateAuthFileConfig(pathExternalIDP+"."+fldAuthPublicKey, i.PublicKey, "", "public key") +} + +func (i *IDPAuthConfiguration) validateTLSClientAuthConfig(tlsCfg TLSConfig) { + i.validateClientIDConfig() + + if tlsCfg == nil { + exception.Throw(ErrBadConfig.FormatError(pathExternalIDP + "." + fldSSLClientCertPath)) } + validateAuthFileConfig(pathExternalIDP+"."+fldSSLClientCertPath, tlsCfg.(*TLSConfiguration).ClientCertificatePath, "", "tls client certificate") + validateAuthFileConfig(pathExternalIDP+"."+fldSSLClientKeyPath, tlsCfg.(*TLSConfiguration).ClientKeyPath, "", "tls client key") } func addExternalIDPProperties(props properties.Properties) {