Skip to content

Commit

Permalink
Merge branch 'master' of github.com:gogatekeeper/gatekeeper into leve…
Browse files Browse the repository at this point in the history
…l-of-authentication
  • Loading branch information
p53 committed Oct 22, 2024
2 parents 5e5d878 + 6a5b023 commit 59e9375
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 236 deletions.
27 changes: 16 additions & 11 deletions pkg/apperrors/apperrors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ var (
ErrPKCECookieEmpty = errors.New("seems that pkce code verifier cookie value is empty string")
ErrQueryParamValueMismatch = errors.New("query param value is not allowed")
ErrMissingAuthCode = errors.New("missing auth code")

ErrSessionExpiredVerifyOff = errors.New("the session has expired and verification switch off")
ErrSessionExpiredRefreshOff = errors.New("session expired and access token refreshing is disabled")
ErrRefreshTokenNotFound = errors.New("unable to find refresh token for user")
ErrAccTokenRefreshFailure = errors.New("failed to refresh the access token")
ErrEncryptAccToken = errors.New("unable to encrypt access token")
ErrEncryptRefreshToken = errors.New("failed to encrypt refresh token")
ErrEncryptIDToken = errors.New("unable to encrypt idToken token")
ErrInvalidGrantType = errors.New("invalid grant type is not supported")
ErrSessionExpiredVerifyOff = errors.New("the session has expired and verification switch off")
ErrSessionExpiredRefreshOff = errors.New("session expired and access token refreshing is disabled")
ErrRefreshTokenNotFound = errors.New("unable to find refresh token for user")
ErrAccTokenRefreshFailure = errors.New("failed to refresh the access token")
ErrEncryptAccToken = errors.New("unable to encrypt access token")
ErrEncryptRefreshToken = errors.New("failed to encrypt refresh token")
ErrEncryptIDToken = errors.New("unable to encrypt idToken token")

ErrDelTokFromStore = errors.New("failed to remove old token")
ErrSaveTokToStore = errors.New("failed to store refresh token")
Expand All @@ -58,9 +58,10 @@ var (
ErrParseRefreshToken = errors.New("failed to parse refresh token")
ErrParseIDToken = errors.New("failed to parse id token")
ErrParseAccessToken = errors.New("failed to parse access token")
ErrParseIDTokenClaims = errors.New("faled to parse id token claims")
ErrParseAccessTokenClaims = errors.New("faled to parse access token claims")
ErrParseRefreshTokenClaims = errors.New("faled to parse refresh token claims")
ErrParseIDTokenClaims = errors.New("failed to parse id token claims")
ErrParseAccessTokenClaims = errors.New("failed to parse access token claims")
ErrParseRefreshTokenClaims = errors.New("failed to parse refresh token claims")
ErrPATTokenFetch = errors.New("failed to get PAT token")

ErrAccTokenVerifyFailure = errors.New("access token failed verification")
ErrTokenSignature = errors.New("invalid token signature")
Expand All @@ -78,6 +79,10 @@ var (
ErrHmacHeaderEmpty = errors.New("request HMAC header empty")
ErrHmacMismatch = errors.New("received HMAC header and calculated HMAC does not match")

ErrStartMainHTTP = errors.New("failed to start main http service")
ErrStartRedirectHTTP = errors.New("failed to start http redirect service")
ErrStartAdminHTTP = errors.New("failed to start admin service")

// config errors

ErrNoRedirectsWithEnableRefreshTokensInvalid = errors.New("no-redirects true cannot be enabled with refresh tokens")
Expand Down
165 changes: 99 additions & 66 deletions pkg/keycloak/proxy/misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import (
"context"
"fmt"
"net/http"
"os"
"strings"
"time"

"github.com/Nerzal/gocloak/v12"
"github.com/cenkalti/backoff/v4"
oidc3 "github.com/coreos/go-oidc/v3/oidc"
"github.com/go-jose/go-jose/v4/jwt"
"github.com/gogatekeeper/gatekeeper/pkg/apperrors"
Expand All @@ -36,8 +36,67 @@ import (
"go.uber.org/zap"
)

//nolint:cyclop
func getPAT(
ctx context.Context,
clientID string,
clientSecret string,
realm string,
openIDProviderTimeout time.Duration,
grantType string,
idpClient *gocloak.GoCloak,
forwardingUsername string,
forwardingPassword string,
) (*gocloak.JWT, *jwt.Claims, error) {
cntx, cancel := context.WithTimeout(
ctx,
openIDProviderTimeout,
)
defer cancel()

var token *gocloak.JWT
var err error

switch grantType {
case configcore.GrantTypeClientCreds:
token, err = idpClient.LoginClient(
cntx,
clientID,
clientSecret,
realm,
)
case configcore.GrantTypeUserCreds:
token, err = idpClient.Login(
cntx,
clientID,
clientSecret,
realm,
forwardingUsername,
forwardingPassword,
)
default:
return nil, nil, apperrors.ErrInvalidGrantType
}

if err != nil {
return nil, nil, err
}

parsedToken, err := jwt.ParseSigned(token.AccessToken, constant.SignatureAlgs[:])
if err != nil {
return nil, nil, err
}

stdClaims := &jwt.Claims{}
err = parsedToken.UnsafeClaimsWithoutVerification(stdClaims)
if err != nil {
return nil, nil, err
}

return token, stdClaims, err
}

func refreshPAT(
ctx context.Context,
logger *zap.Logger,
pat *PAT,
clientID string,
Expand All @@ -52,8 +111,7 @@ func getPAT(
forwardingUsername string,
forwardingPassword string,
done chan bool,
) {
retry := 0
) error {
initialized := false
grantType := configcore.GrantTypeClientCreds

Expand All @@ -62,57 +120,44 @@ func getPAT(
}

for {
if retry > 0 {
logger.Info(
"retrying fetching PAT token",
zap.Int("retry", retry),
)
}

ctx, cancel := context.WithTimeout(
context.Background(),
openIDProviderTimeout,
)

var token *gocloak.JWT
var err error

switch grantType {
case configcore.GrantTypeClientCreds:
token, err = idpClient.LoginClient(
ctx,
clientID,
clientSecret,
realm,
)
case configcore.GrantTypeUserCreds:
token, err = idpClient.Login(
ctx,
var claims *jwt.Claims
operation := func() error {
var err error
pCtx, cancel := context.WithCancel(ctx)
defer cancel()
token, claims, err = getPAT(
pCtx,
clientID,
clientSecret,
realm,
openIDProviderTimeout,
grantType,
idpClient,
forwardingUsername,
forwardingPassword,
)
default:
return err
}

notify := func(err error, delay time.Duration) {
logger.Error(
"Chosen grant type is not supported",
zap.String("grant_type", grantType),
err.Error(),
zap.Duration("retry after", delay),
)
os.Exit(1)
}

if err != nil {
retry++
logger.Error("problem getting PAT token", zap.Error(err))

if retry >= patRetryCount {
cancel()
os.Exit(1)
}
bom := backoff.WithMaxRetries(
backoff.NewConstantBackOff(patRetryInterval),
uint64(patRetryCount),
)
boCtx, cancel := context.WithCancel(ctx)
defer cancel()
box := backoff.WithContext(bom, boCtx)
err := backoff.RetryNotify(operation, box, notify)

<-time.After(patRetryInterval)
continue
if err != nil {
return err
}

pat.m.Lock()
Expand All @@ -121,37 +166,25 @@ func getPAT(

if !initialized {
done <- true
initialized = true
}

initialized = true

parsedToken, err := jwt.ParseSigned(token.AccessToken, constant.SignatureAlgs[:])
if err != nil {
retry++
logger.Error("failed to parse the access token", zap.Error(err))
<-time.After(patRetryInterval)
continue
}

stdClaims := &jwt.Claims{}
err = parsedToken.UnsafeClaimsWithoutVerification(stdClaims)
if err != nil {
retry++
logger.Error("unable to parse access token for claims", zap.Error(err))
<-time.After(patRetryInterval)
continue
}

retry = 0
expiration := stdClaims.Expiry.Time()
expiration := claims.Expiry.Time()
refreshIn := utils.GetWithin(expiration, constant.PATRefreshInPercent)

logger.Info(
"waiting for expiration of access token",
"waiting for access token expiration",
zap.Float64("refresh_in", refreshIn.Seconds()),
)

<-time.After(refreshIn)
refreshTimer := time.NewTimer(refreshIn)
select {
case <-ctx.Done():
logger.Info("shutdown PAT refresh routine")
refreshTimer.Stop()
return nil
case <-refreshTimer.C:
}
}
}

Expand Down
43 changes: 19 additions & 24 deletions pkg/keycloak/proxy/oauth_proxy.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package proxy

import (
"context"
"net"
"net/http"
"net/url"
Expand All @@ -12,10 +11,9 @@ import (
"github.com/gogatekeeper/gatekeeper/pkg/keycloak/config"
"github.com/gogatekeeper/gatekeeper/pkg/proxy/cookie"
"github.com/gogatekeeper/gatekeeper/pkg/proxy/core"
"github.com/gogatekeeper/gatekeeper/pkg/proxy/models"
"github.com/gogatekeeper/gatekeeper/pkg/storage"
"go.uber.org/zap"
"golang.org/x/oauth2"
"golang.org/x/sync/errgroup"
)

type PAT struct {
Expand All @@ -29,25 +27,22 @@ type RPT struct {
}

type OauthProxy struct {
Provider *oidc3.Provider
Config *config.Config
Endpoint *url.URL
IdpClient *gocloak.GoCloak
Listener net.Listener
Log *zap.Logger
metricsHandler http.Handler
Router http.Handler
adminRouter http.Handler
Server *http.Server
Store storage.Storage
Upstream core.ReverseProxy
pat *PAT
rpt *RPT
accessForbidden func(wrt http.ResponseWriter, req *http.Request) context.Context
accessError func(wrt http.ResponseWriter, req *http.Request) context.Context
customSignInPage func(wrt http.ResponseWriter, authURL string)
GetIdentity func(req *http.Request, tokenCookie string, tokenHeader string) (*models.UserContext, error)
Cm *cookie.Manager
getRedirectionURL func(wrt http.ResponseWriter, req *http.Request) string
newOAuth2Config func(redirectionURL string) *oauth2.Config
Provider *oidc3.Provider
Config *config.Config
Endpoint *url.URL
IdpClient *gocloak.GoCloak
Listener net.Listener
Log *zap.Logger
metricsHandler http.Handler
Router http.Handler
adminRouter http.Handler
Server *http.Server
HTTPServer *http.Server
AdminServer *http.Server
Store storage.Storage
Upstream core.ReverseProxy
pat *PAT
rpt *RPT
Cm *cookie.Manager
ErrGroup *errgroup.Group
}
Loading

0 comments on commit 59e9375

Please sign in to comment.