...

Source file src/edge-infra.dev/pkg/edge/iam/verify/verify_callback.go

Documentation: edge-infra.dev/pkg/edge/iam/verify

     1  package verify
     2  
     3  import (
     4  	"net/http"
     5  
     6  	"edge-infra.dev/pkg/edge/iam/log"
     7  	"edge-infra.dev/pkg/edge/iam/verify/templates"
     8  
     9  	"github.com/MicahParks/keyfunc"
    10  	"github.com/coreos/go-oidc/v3/oidc"
    11  	"github.com/gin-gonic/gin"
    12  	"github.com/golang-jwt/jwt/v4"
    13  	"golang.org/x/oauth2"
    14  )
    15  
    16  type IDClaims struct {
    17  	Organization string `json:"org"`
    18  }
    19  
    20  func (v *Verifier) callback(ctx *gin.Context) {
    21  	log := log.Get(ctx.Request.Context())
    22  	result := &Result{
    23  		Name: "verify callback result",
    24  		Pass: true,
    25  	}
    26  
    27  	// discover openid config to setup a oauth2 client
    28  	provider, err := oidc.NewProvider(oidc.InsecureIssuerURLContext(ctx, Issuer()), IssuerURL())
    29  	if err != nil {
    30  		err := ctx.AbortWithError(http.StatusInternalServerError, err)
    31  		if err != nil {
    32  			log.Error(err, "failed to abort with error")
    33  		}
    34  		return
    35  	}
    36  
    37  	// weirdly named just config, but it's a client as well
    38  	oauth2Config := oauth2.Config{
    39  		ClientID:     v.ClientID,
    40  		ClientSecret: v.ClientSecret,
    41  		RedirectURL:  v.ClientURL + verifyCallbackPath,
    42  		Endpoint:     provider.Endpoint(),
    43  		Scopes:       []string{oidc.ScopeOpenID, oidc.ScopeOfflineAccess, "profile"},
    44  	}
    45  
    46  	// would have expected to be able to do this
    47  	jwksURL := IssuerURL() + wellKnownJWKSPath
    48  	jwks, err := keyfunc.Get(jwksURL, keyfunc.Options{})
    49  	if err != nil {
    50  		//TODO: replace these repeated 3 lines into `return fail("message")`
    51  		step := Step{Name: "load JWKS", Pass: false}
    52  		writeResult(ctx, templates.Callback, result, step)
    53  		return
    54  	}
    55  
    56  	// grab the auth code from the URL
    57  	code, _ := ctx.GetQuery("code")
    58  
    59  	// fetch the tokenset using the auth code
    60  	tokenSet, err := oauth2Config.Exchange(ctx, code, oauth2.AccessTypeOffline)
    61  	if err != nil {
    62  		step := Step{Name: "exchange code for tokens", Pass: false}
    63  		writeResult(ctx, templates.Callback, result, step)
    64  		return
    65  	}
    66  
    67  	// validate it
    68  	if !tokenSet.Valid() {
    69  		step := Step{Name: "valid token", Pass: false}
    70  		writeResult(ctx, templates.Callback, result, step)
    71  		return
    72  	}
    73  
    74  	// parse and validate the access token
    75  	accessToken, err := jwt.Parse(tokenSet.AccessToken, jwks.Keyfunc)
    76  	if err != nil {
    77  		step := Step{Name: "parse, validate and verify the token", Pass: false}
    78  		writeResult(ctx, templates.Callback, result, step)
    79  		return
    80  	}
    81  
    82  	// extract the id_token
    83  	rawIDToken, ok := tokenSet.Extra("id_token").(string)
    84  	if !ok {
    85  		step := Step{Name: "token includes and id_token", Pass: false}
    86  		writeResult(ctx, templates.Callback, result, step)
    87  		return
    88  	}
    89  
    90  	// verify and parse the id_token
    91  	verifier := provider.Verifier(&oidc.Config{
    92  		ClientID: v.ClientID,
    93  	})
    94  
    95  	//logger.Debug("token", "id-token", rawIDToken)
    96  	idToken, err := verifier.Verify(ctx, rawIDToken)
    97  	if err != nil {
    98  		step := Step{Name: "id_token is valid", Pass: false}
    99  		writeResult(ctx, templates.Callback, result, step)
   100  		return
   101  	}
   102  
   103  	idClaims := IDClaims{}
   104  	if err := idToken.Claims(&idClaims); err != nil {
   105  		step := Step{Name: "id_token has valid claims", Pass: false}
   106  		writeResult(ctx, templates.Callback, result, step)
   107  		return
   108  	}
   109  
   110  	claims, _ := accessToken.Claims.(jwt.MapClaims)
   111  	steps := make([]Step, 0)
   112  	steps = append(steps,
   113  		Step{Name: "valid access token", Pass: accessToken.Valid},
   114  		Step{Name: "organization is correct", IsPublic: true, Pass: claims["org"] == BslExpectedOrganization(), Expected: BslExpectedOrganization(), Got: claims["org"].(string)},
   115  		Step{Name: "subject is correct", IsPublic: true, Pass: claims["sub"] == BslExpectedSubject(), Expected: BslExpectedSubject(), Got: claims["sub"].(string)},
   116  		Step{Name: "we have scopes", Pass: claims["scp"] != ""},
   117  		Step{Name: "we have roles", Pass: claims["rls"] != ""},
   118  		//TODO: [EyalW] - assert also the expected issuer
   119  		//VerificationStep{Name: "ID Token has correct issuer", Pass: idToken.Issuer == EXPECT_ISSUER},
   120  		Step{Name: "id token has correct audience", IsPublic: true, Pass: idToken.Audience[0] == v.ClientID, Expected: v.ClientSecret, Got: idToken.Audience[0]},
   121  		Step{Name: "id token has correct subject", IsPublic: true, Pass: idToken.Subject == BslExpectedSubject(), Expected: BslExpectedSubject(), Got: idToken.Subject},
   122  	)
   123  
   124  	writeResult(ctx, templates.Callback, result, steps...)
   125  }
   126  

View as plain text