Skip to content

Commit

Permalink
Merge pull request #97 from tupyy/auth
Browse files Browse the repository at this point in the history
auth: Add a simple RHSSO authentication
  • Loading branch information
tupyy authored Dec 10, 2024
2 parents 45a8fa1 + 5763480 commit 75c5a8e
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 0 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ go 1.22.2

require (
github.com/IBM/sarama v1.43.3
github.com/MicahParks/keyfunc/v3 v3.3.5
github.com/cloudevents/sdk-go/v2 v2.15.2
github.com/coreos/butane v0.22.0
github.com/getkin/kin-openapi v0.126.0
github.com/go-chi/chi v1.5.5
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/google/uuid v1.6.0
github.com/konveyor/forklift-controller v0.0.0-20221102112227-e73b65a01cda
github.com/kubev2v/migration-event-streamer v0.0.0-20241125102656-9cdf9e64a16b
Expand Down Expand Up @@ -38,6 +40,7 @@ require (
)

require (
github.com/MicahParks/jwkset v0.5.19 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/aws/aws-sdk-go v1.50.25 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA=
github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ=
github.com/MicahParks/jwkset v0.5.19 h1:XZCsgJv05DBCvxEHYEHlSafqiuVn5ESG0VRB331Fxhw=
github.com/MicahParks/jwkset v0.5.19/go.mod h1:q8ptTGn/Z9c4MwbcfeCDssADeVQb3Pk7PnVxrvi+2QY=
github.com/MicahParks/keyfunc/v3 v3.3.5 h1:7ceAJLUAldnoueHDNzF8Bx06oVcQ5CfJnYwNt1U3YYo=
github.com/MicahParks/keyfunc/v3 v3.3.5/go.mod h1:SdCCyMJn/bYqWDvARspC6nCT8Sk74MjuAY22C7dCST8=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
Expand Down Expand Up @@ -153,6 +157,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down
7 changes: 7 additions & 0 deletions internal/api_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-chi/chi/v5/middleware"
api "github.com/kubev2v/migration-planner/api/v1alpha1"
"github.com/kubev2v/migration-planner/internal/api/server"
"github.com/kubev2v/migration-planner/internal/auth"
"github.com/kubev2v/migration-planner/internal/config"
"github.com/kubev2v/migration-planner/internal/events"
"github.com/kubev2v/migration-planner/internal/image"
Expand Down Expand Up @@ -75,8 +76,14 @@ func (s *Server) Run(ctx context.Context) error {
ErrorHandler: oapiErrorHandler,
}

authenticator, err := auth.NewAuthenticator(s.cfg.Service.Auth)
if err != nil {
return fmt.Errorf("failed to create authenticator: %w", err)
}

router := chi.NewRouter()
router.Use(
authenticator.Authenticator,
middleware.RequestID,
zapchi.Logger(zap.S(), "router_api"),
middleware.Recoverer,
Expand Down
25 changes: 25 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package auth

import (
"net/http"

"github.com/kubev2v/migration-planner/internal/config"
)

type Authenticator interface {
Authenticator(next http.Handler) http.Handler
}

const (
RHSSOAuthentication string = "rhsso"
NoneAuthentication string = "none"
)

func NewAuthenticator(authConfig config.Auth) (Authenticator, error) {
switch authConfig.AuthenticationType {
case RHSSOAuthentication:
return NewRHSSOAuthenticator(authConfig.JwtCertUrl)
default:
return NewNoneAuthenticator()
}
}
13 changes: 13 additions & 0 deletions internal/auth/auth_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package auth_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestAuth(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Auth Suite")
}
26 changes: 26 additions & 0 deletions internal/auth/none_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package auth

import (
"net/http"

"go.uber.org/zap"
)

type NoneAuthenticator struct{}

func NewNoneAuthenticator() (*NoneAuthenticator, error) {
return &NoneAuthenticator{}, nil
}

func (n *NoneAuthenticator) Authenticator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
zap.S().Named("auth").Info("authentication disabled")

user := User{
Username: "admin",
Organization: "internal",
}
ctx := newContext(r.Context(), user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
82 changes: 82 additions & 0 deletions internal/auth/rhsso_authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package auth

import (
"context"
"errors"
"fmt"
"net/http"
"time"

keyfunc "github.com/MicahParks/keyfunc/v3"
"github.com/golang-jwt/jwt/v5"
"go.uber.org/zap"
)

type RHSSOAuthenticator struct {
keyFn func(t *jwt.Token) (any, error)
}

func NewRHSSOAuthenticatorWithKeyFn(keyFn func(t *jwt.Token) (any, error)) (*RHSSOAuthenticator, error) {
return &RHSSOAuthenticator{keyFn: keyFn}, nil
}

func NewRHSSOAuthenticator(jwkCertUrl string) (*RHSSOAuthenticator, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

k, err := keyfunc.NewDefaultCtx(ctx, []string{jwkCertUrl})
if err != nil {
return nil, fmt.Errorf("failed to get sso public keys: %w", err)
}

return &RHSSOAuthenticator{keyFn: k.Keyfunc}, nil
}

func (rh *RHSSOAuthenticator) Authenticate(token string) (User, error) {
parser := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodRS256.Name}), jwt.WithIssuedAt(), jwt.WithExpirationRequired())
t, err := parser.Parse(token, rh.keyFn)
if err != nil {
zap.S().Errorw("failed to parse or the token is invalid", "token", token)
return User{}, fmt.Errorf("failed to authenticate token: %w", err)
}

if !t.Valid {
zap.S().Errorw("failed to parse or the token is invalid", "token", token)
return User{}, fmt.Errorf("failed to parse or validate token")
}

return rh.parseToken(t)
}

func (rh *RHSSOAuthenticator) parseToken(userToken *jwt.Token) (User, error) {
claims, ok := userToken.Claims.(jwt.MapClaims)
if !ok {
return User{}, errors.New("failed to parse jwt token claims")
}

return User{
Username: claims["username"].(string),
Organization: claims["org_id"].(string),
ClientID: claims["client_id"].(string),
}, nil
}

func (rh *RHSSOAuthenticator) Authenticator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
accessToken := r.Header.Get("Authorization")
if accessToken == "" || len(accessToken) < len("Bearer ") {
http.Error(w, "No token provided", http.StatusUnauthorized)
return
}

accessToken = accessToken[len("Bearer "):]
user, err := rh.Authenticate(accessToken)
if err != nil {
http.Error(w, "authentication failed", http.StatusUnauthorized)
return
}

ctx := newContext(r.Context(), user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Loading

0 comments on commit 75c5a8e

Please sign in to comment.