Skip to content

Commit

Permalink
authenticators: Introduce authenticators package
Browse files Browse the repository at this point in the history
Add a new authenticators package which includes the implementations of
all the authentication methods that oidc-authservice currently
supports. Isolate the dependency to the
"k8s.io/apiserver/pkg/authentication/authenticator" package, inside the
new authenticators package.

GitHub-PR: #105

Signed-off-by: Athanasios Markou <[email protected]>
  • Loading branch information
Athanasios Markou committed Feb 3, 2023
1 parent 39faf4c commit 3f80ac1
Show file tree
Hide file tree
Showing 12 changed files with 148 additions and 142 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ COPY *.go ./
COPY common common
COPY oidc oidc
COPY sessions sessions
COPY authenticators authenticators
RUN CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o /go/bin/oidc-authservice


Expand Down
9 changes: 0 additions & 9 deletions authenticator.go

This file was deleted.

13 changes: 13 additions & 0 deletions authenticators/authenticator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package authenticators

import (
"net/http"

"k8s.io/apiserver/pkg/authentication/authenticator"
)

type AuthenticatorRequest authenticator.Request

type Cacheable interface {
GetCacheKey(r *http.Request) string
}
30 changes: 15 additions & 15 deletions authenticator_idtoken.go → authenticators/idtoken.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package authenticators

import (
"net/http"
Expand All @@ -9,29 +9,29 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
)

type idTokenAuthenticator struct {
header string // header name where id token is stored
caBundle []byte
provider oidc.IdProvider
clientID string // need client id to verify the id token
userIDClaim string // retrieve the userid if the claim exists
groupsClaim string
type IDTokenAuthenticator struct {
Header string // header name where id token is stored
CaBundle []byte
Provider oidc.IdProvider
ClientID string // need client id to verify the id token
UserIDClaim string // retrieve the userid if the claim exists
GroupsClaim string
}

func (s *idTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
func (s *IDTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
logger := common.LoggerForRequest(r, "idtoken authenticator")

// get id-token from header
bearer := common.GetBearerToken(r.Header.Get(s.header))
bearer := common.GetBearerToken(r.Header.Get(s.Header))
if len(bearer) == 0 {
logger.Info("No bearer token found")
return nil, false, nil
}

ctx := common.SetTLSContext(r.Context(), s.caBundle)
ctx := common.SetTLSContext(r.Context(), s.CaBundle)

// Verifying received ID token
verifier := s.provider.Verifier(oidc.NewOidcConfig(s.clientID))
verifier := s.Provider.Verifier(oidc.NewOidcConfig(s.ClientID))
token, err := verifier.Verify(ctx, bearer)
if err != nil {
logger.Errorf("id-token verification failed: %v", err)
Expand All @@ -44,14 +44,14 @@ func (s *idTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authentica
return nil, false, nil
}

if claims[s.userIDClaim] == nil {
if claims[s.UserIDClaim] == nil {
// No USERID_CLAIM, pass this authenticator
logger.Error("USERID_CLAIM doesn't exist in the id token")
return nil, false, nil
}

groups := []string{}
groupsClaim := claims[s.groupsClaim]
groupsClaim := claims[s.GroupsClaim]
if groupsClaim != nil {
groups = common.InterfaceSliceToStringSlice(groupsClaim.([]interface{}))
}
Expand All @@ -61,7 +61,7 @@ func (s *idTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authentica

resp := &authenticator.Response{
User: &user.DefaultInfo{
Name: claims[s.userIDClaim].(string),
Name: claims[s.UserIDClaim].(string),
Groups: groups,
Extra: extra,
},
Expand Down
48 changes: 24 additions & 24 deletions authenticator_jwt.go → authenticators/jwt.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package authenticators

import (
"net/http"
Expand All @@ -16,36 +16,36 @@ const (
audienceNotfound = "oidc: expected audience"
)

type jwtTokenAuthenticator struct {
header string // header name where JWT access token is stored
caBundle []byte
provider oidc.IdProvider
audiences []string // need client id to verify the id token
issuer string // need this for the local check
userIDClaim string // retrieve the userid if the claim exists
groupsClaim string
type JWTTokenAuthenticator struct {
Header string // header name where JWT access token is stored
CaBundle []byte
Provider oidc.IdProvider
Audiences []string // need client id to verify the id token
Issuer string // need this for the local check
UserIDClaim string // retrieve the userid if the claim exists
GroupsClaim string
}

type jwtLocalChecks struct {
Issuer string `json:"iss"`
Audiences common.Audience `json:"aud"`
}

func (s *jwtTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
func (s *JWTTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
logger := common.LoggerForRequest(r, "JWT access token authenticator")

// Get JWT access token from header
bearer := common.GetBearerToken(r.Header.Get(s.header))
bearer := common.GetBearerToken(r.Header.Get(s.Header))
if len(bearer) == 0 {
logger.Info("No bearer token found")
return nil, false, nil
}

ctx := common.SetTLSContext(r.Context(), s.caBundle)
ctx := common.SetTLSContext(r.Context(), s.CaBundle)

// Verifying received JWT token
for _, aud := range s.audiences {
verifier := s.provider.Verifier(oidc.NewOidcConfig(aud))
for _, aud := range s.Audiences {
verifier := s.Provider.Verifier(oidc.NewOidcConfig(aud))
token, err := verifier.Verify(ctx, bearer)

if err != nil {
Expand Down Expand Up @@ -99,8 +99,8 @@ func (s *jwtTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authentic

}

// Perform local checks for the issuer and the audiences
func (s *jwtTokenAuthenticator) performLocalChecks(bearer string) (error){
// Perform local checks for the issuer and the audiences
func (s *JWTTokenAuthenticator) performLocalChecks(bearer string) (error){

// Verify that the retrieved Bearer token is a parsable JWT token
payload, localErr := common.ParseJWT(bearer)
Expand All @@ -118,39 +118,39 @@ func (s *jwtTokenAuthenticator) performLocalChecks(bearer string) (error){
}

// Check issuer
if tokenLocalChecks.Issuer != s.issuer { // Check next authenticator
if tokenLocalChecks.Issuer != s.Issuer { // Check next authenticator
localErr = fmt.Errorf("The retrieved \"iss\" did not match the expected one.")
return localErr
}

// Check audiences
if !common.Contains(s.audiences, tokenLocalChecks.Audiences){ // Check next authenticator
if !common.Contains(s.Audiences, tokenLocalChecks.Audiences){ // Check next authenticator
localErr = fmt.Errorf("The retrieved \"aud\" did not match with any of the" +
" expected audiences.")
return localErr
}

// Local checks succeeded.
// Local checks succeeded.
return nil

}

// Retrieve the USERID_CLAIM and the GROUPS_CLAIM from the JWT access token
func (s *jwtTokenAuthenticator) retrieveUserIDGroupsClaims(claims map[string]interface{}) (string, []string, error){
if claims[s.userIDClaim] == nil {
func (s *JWTTokenAuthenticator) retrieveUserIDGroupsClaims(claims map[string]interface{}) (string, []string, error){

if claims[s.UserIDClaim] == nil {
claimErr := fmt.Errorf("USERID_CLAIM not found in the JWT token")
return "", []string{}, claimErr
}

groups := []string{}
groupsClaim := claims[s.groupsClaim]
groupsClaim := claims[s.GroupsClaim]
if groupsClaim == nil {
claimErr := fmt.Errorf("GROUPS_CLAIM not found in the JWT token")
return "", []string{}, claimErr
}

groups = common.InterfaceSliceToStringSlice(groupsClaim.([]interface{}))

return claims[s.userIDClaim].(string), groups, nil
return claims[s.UserIDClaim].(string), groups, nil
}
14 changes: 7 additions & 7 deletions authenticator_jwt_test.go → authenticators/jwt_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package authenticators

import (
"testing"
Expand All @@ -7,13 +7,13 @@ import (

func TestPerformLocalChecks(t *testing.T) {

s := &jwtTokenAuthenticator {
audiences: []string{
s := &JWTTokenAuthenticator {
Audiences: []string{
"myaudience1",
"myaudience2",
"00af7fe8-a019-4859-94af-3d0f4009fed5",
},
issuer: "https://auth.pingone.eu/e6b1425e-6090-4d29-a961-e760860d932a/as",
Issuer: "https://auth.pingone.eu/e6b1425e-6090-4d29-a961-e760860d932a/as",
}

tests := []struct {
Expand Down Expand Up @@ -61,9 +61,9 @@ func TestPerformLocalChecks(t *testing.T) {

func TestRetrieveUserIDGroupsClaims(t *testing.T) {

s := &jwtTokenAuthenticator {
userIDClaim: "preferred_username",
groupsClaim: "groups",
s := &JWTTokenAuthenticator {
UserIDClaim: "preferred_username",
GroupsClaim: "groups",
}

tests := []struct {
Expand Down
20 changes: 10 additions & 10 deletions authenticator_kubernetes.go → authenticators/kubernetes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package authenticators

import (
"net/http"
Expand All @@ -17,25 +17,25 @@ const (
bearerTokenExpiredMsg = "Token has expired"
)

type kubernetesAuthenticator struct {
audiences []string
authenticator authenticator.Request
type KubernetesAuthenticator struct {
Audiences []string
Authenticator authenticator.Request
}

func newKubernetesAuthenticator(c *rest.Config, aud []string) (authenticator.Request, error) {
func NewKubernetesAuthenticator(c *rest.Config, aud []string) (authenticator.Request, error) {
config := authenticatorfactory.DelegatingAuthenticatorConfig{
Anonymous: false,
TokenAccessReviewClient: kubernetes.NewForConfigOrDie(c).AuthenticationV1(),
WebhookRetryBackoff: webhook.DefaultRetryBackoff(),
APIAudiences: aud,
}
k8sAuthenticator, _, err := config.New()
return &kubernetesAuthenticator{audiences: aud, authenticator: k8sAuthenticator}, err
return &KubernetesAuthenticator{Audiences: aud, Authenticator: k8sAuthenticator}, err
}

func (k8sauth *kubernetesAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
resp, found, err := k8sauth.authenticator.AuthenticateRequest(
r.WithContext(authenticator.WithAudiences(r.Context(), k8sauth.audiences)),
func (k8sauth *KubernetesAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
resp, found, err := k8sauth.Authenticator.AuthenticateRequest(
r.WithContext(authenticator.WithAudiences(r.Context(), k8sauth.Audiences)),
)

// If the request contains an expired token, we stop trying and return 403
Expand Down Expand Up @@ -63,7 +63,7 @@ func (k8sauth *kubernetesAuthenticator) AuthenticateRequest(r *http.Request) (*a

// The Kubernetes Authenticator implements the Cacheable
// interface with the getCacheKey().
func (k8sauth *kubernetesAuthenticator) getCacheKey(r *http.Request) (string) {
func (k8sauth *KubernetesAuthenticator) GetCacheKey(r *http.Request) (string) {
return common.GetBearerToken(r.Header.Get("Authorization"))

}
34 changes: 17 additions & 17 deletions authenticator_opaque.go → authenticators/opaque.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package authenticators

import (
"net/http"
Expand All @@ -11,20 +11,20 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
)

type opaqueTokenAuthenticator struct {
header string // header name where opaque access token is stored
caBundle []byte
provider oidc.IdProvider
oauth2Config *oauth2.Config
userIDClaim string // retrieve the userid claim
groupsClaim string // retrieve the groups claim
type OpaqueTokenAuthenticator struct {
Header string // header name where opaque access token is stored
CaBundle []byte
Provider oidc.IdProvider
Oauth2Config *oauth2.Config
UserIDClaim string // retrieve the userid claim
GroupsClaim string // retrieve the groups claim
}

func (s *opaqueTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
func (s *OpaqueTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authenticator.Response, bool, error) {
logger := common.LoggerForRequest(r, "opaque access token authenticator")

// get id-token from header
bearer := common.GetBearerToken(r.Header.Get(s.header))
bearer := common.GetBearerToken(r.Header.Get(s.Header))
if len(bearer) == 0 {
logger.Info("No bearer token found")
return nil, false, nil
Expand All @@ -35,9 +35,9 @@ func (s *opaqueTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authen
TokenType: "Bearer",
}

ctx := common.SetTLSContext(r.Context(), s.caBundle)
ctx := common.SetTLSContext(r.Context(), s.CaBundle)

userInfo, err := oidc.GetUserInfo(ctx, s.provider, s.oauth2Config.TokenSource(ctx, opaque))
userInfo, err := oidc.GetUserInfo(ctx, s.Provider, s.Oauth2Config.TokenSource(ctx, opaque))
if err != nil {
var reqErr *common.RequestError
if !errors.As(err, &reqErr) {
Expand Down Expand Up @@ -73,28 +73,28 @@ func (s *opaqueTokenAuthenticator) AuthenticateRequest(r *http.Request) (*authen
}

// Retrieve the USERID_CLAIM and the GROUPS_CLAIM from the /userinfo response
func (s *opaqueTokenAuthenticator) retrieveUserIDGroupsClaims(claims map[string]interface{}) (string, []string, error){
func (s *OpaqueTokenAuthenticator) retrieveUserIDGroupsClaims(claims map[string]interface{}) (string, []string, error){

if claims[s.userIDClaim] == nil {
if claims[s.UserIDClaim] == nil {
claimErr := errors.New("USERID_CLAIM not found in the response of the userinfo endpoint")
return "", []string{}, claimErr
}

groups := []string{}
groupsClaim := claims[s.groupsClaim]
groupsClaim := claims[s.GroupsClaim]
if groupsClaim == nil {
claimErr := errors.New("GROUPS_CLAIM not found in the response of the userinfo endpoint")
return "", []string{}, claimErr
}

groups = common.InterfaceSliceToStringSlice(groupsClaim.([]interface{}))

return claims[s.userIDClaim].(string), groups, nil
return claims[s.UserIDClaim].(string), groups, nil
}

// The Opaque Access Token Authenticator implements the Cacheable
// interface with the getCacheKey().
func (s *opaqueTokenAuthenticator) getCacheKey(r *http.Request) (string) {
func (s *OpaqueTokenAuthenticator) GetCacheKey(r *http.Request) (string) {
return common.GetBearerToken(r.Header.Get("Authorization"))

}
Loading

0 comments on commit 3f80ac1

Please sign in to comment.