Skip to content

Commit

Permalink
Abstract Authorization
Browse files Browse the repository at this point in the history
Identity uses the shared code, but can obviously authenticate itself!
  • Loading branch information
spjmurray committed Mar 8, 2024
1 parent 0ef2902 commit 0ee9428
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 34 deletions.
4 changes: 2 additions & 2 deletions charts/core/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn Core

type: application

version: v0.1.7
appVersion: v0.1.7
version: v0.1.8
appVersion: v0.1.8

icon: https://assets.unikorn-cloud.org/images/logos/dark-on-light/icon.svg
40 changes: 40 additions & 0 deletions pkg/server/middleware/openapi/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
Copyright 2024 the Unikorn Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package openapi

import (
"github.com/getkin/kin-openapi/openapi3filter"

"github.com/unikorn-cloud/core/pkg/authorization/oauth2/claims"
)

// AuthorizationContext is passed through the middleware to propagate
// information back to the top level handler.
type AuthorizationContext struct {
// Error allows us to return a verbose error, unwrapped by whatever
// the openapi validaiton is doing.
Error error

// Claims contains all claims defined in the token.
Claims claims.Claims
}

// Authorizer allows authorizers to be plugged in interchangeably.
type Authorizer interface {
// Authorize checks the request against the OpenAPI security scheme.
Authorize(ctx *AuthorizationContext, authentication *openapi3filter.AuthenticationInput) error
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package openapi
package oidc

import (
"crypto/tls"
Expand All @@ -25,24 +25,14 @@ import (
"strings"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/openapi3filter"
"github.com/spf13/pflag"

"github.com/unikorn-cloud/core/pkg/authorization/oauth2/claims"
"github.com/unikorn-cloud/core/pkg/server/errors"
"github.com/unikorn-cloud/core/pkg/server/middleware/openapi"
)

// authorizationContext is passed through the middleware to propagate
// information back to the top level handler.
type authorizationContext struct {
// err allows us to return a verbose error, unwrapped by whatever
// the openapi validaiton is doing.
err error

// claims contains all claims defined in the token.
claims claims.Claims
}

type Options struct {
// issuer is used to perform OIDC discovery and verify access tokens
// using the JWKS endpoint.
Expand Down Expand Up @@ -86,7 +76,7 @@ func getHTTPAuthenticationScheme(r *http.Request) (string, string, error) {
}

// authorizeOAuth2 checks APIs that require and oauth2 bearer token.
func (a *Authorizer) authorizeOAuth2(authContext *authorizationContext, r *http.Request, scopes []string) error {
func (a *Authorizer) authorizeOAuth2(authContext *openapi.AuthorizationContext, r *http.Request, scopes []string) error {
authorizationScheme, rawToken, err := getHTTPAuthenticationScheme(r)
if err != nil {
return err
Expand Down Expand Up @@ -153,16 +143,16 @@ func (a *Authorizer) authorizeOAuth2(authContext *authorizationContext, r *http.
}

// Set the claims in the context for use by the handlers.
authContext.claims = claims
authContext.Claims = claims

return nil
}

// authorizeScheme requires the individual scheme to match.
func (a *Authorizer) authorizeScheme(ctx *authorizationContext, r *http.Request, scheme *openapi3.SecurityScheme, scopes []string) error {
if scheme.Type == "oauth2" {
return a.authorizeOAuth2(ctx, r, scopes)
func (a *Authorizer) Authorize(ctx *openapi.AuthorizationContext, authentication *openapi3filter.AuthenticationInput) error {
if authentication.SecurityScheme.Type == "oauth2" {
return a.authorizeOAuth2(ctx, authentication.RequestValidationInput.Request, authentication.Scopes)
}

return errors.OAuth2InvalidRequest("authorization scheme unsupported").WithValues("scheme", scheme.Type)
return errors.OAuth2InvalidRequest("authorization scheme unsupported").WithValues("scheme", authentication.SecurityScheme.Type)
}
22 changes: 10 additions & 12 deletions pkg/server/middleware/openapi/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type Validator struct {
next http.Handler

// authorizer provides security policy enforcement.
authorizer *Authorizer
authorizer Authorizer

// openapi caches the Schema schema.
openapi *Schema
Expand All @@ -49,7 +49,7 @@ type Validator struct {
var _ http.Handler = &Validator{}

// NewValidator returns an initialized validator middleware.
func NewValidator(authorizer *Authorizer, next http.Handler, openapi *Schema) *Validator {
func NewValidator(authorizer Authorizer, next http.Handler, openapi *Schema) *Validator {
return &Validator{
authorizer: authorizer,
next: next,
Expand Down Expand Up @@ -106,18 +106,16 @@ func (w *bufferingResponseWriter) StatusCode() int {
return w.code
}

func (v *Validator) validateRequest(r *http.Request, authContext *authorizationContext) (*openapi3filter.ResponseValidationInput, error) {
func (v *Validator) validateRequest(r *http.Request, authContext *AuthorizationContext) (*openapi3filter.ResponseValidationInput, error) {
route, params, err := v.openapi.FindRoute(r)
if err != nil {
return nil, errors.OAuth2ServerError("route lookup failure").WithError(err)
}

authorizationFunc := func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
err := v.authorizer.authorizeScheme(authContext, input.RequestValidationInput.Request, input.SecurityScheme, input.Scopes)
authContext.Error = v.authorizer.Authorize(authContext, input)

authContext.err = err

return err
return authContext.Error
}

options := &openapi3filter.Options{
Expand All @@ -133,8 +131,8 @@ func (v *Validator) validateRequest(r *http.Request, authContext *authorizationC
}

if err := openapi3filter.ValidateRequest(r.Context(), requestValidationInput); err != nil {
if authContext.err != nil {
return nil, authContext.err
if authContext.Error != nil {
return nil, authContext.Error
}

return nil, errors.OAuth2InvalidRequest("request body invalid").WithError(err)
Expand All @@ -160,7 +158,7 @@ func (v *Validator) validateResponse(w *bufferingResponseWriter, r *http.Request

// ServeHTTP implements the http.Handler interface.
func (v *Validator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
authContext := &authorizationContext{}
authContext := &AuthorizationContext{}

responseValidationInput, err := v.validateRequest(r, authContext)
if err != nil {
Expand All @@ -170,7 +168,7 @@ func (v *Validator) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

// Add any contextual information to bubble up to the handler.
r = r.WithContext(claims.NewContext(r.Context(), &authContext.claims))
r = r.WithContext(claims.NewContext(r.Context(), &authContext.Claims))

// Override the writer so we can inspect the contents and status.
writer := &bufferingResponseWriter{
Expand All @@ -184,7 +182,7 @@ func (v *Validator) ServeHTTP(w http.ResponseWriter, r *http.Request) {

// Middleware returns a function that generates per-request
// middleware functions.
func Middleware(authorizer *Authorizer, openapi *Schema) func(http.Handler) http.Handler {
func Middleware(authorizer Authorizer, openapi *Schema) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return NewValidator(authorizer, next, openapi)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/middleware/opentelemetry/opentelemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func httpStatusToOtelCode(status int) (codes.Code, string) {
}

// Middleware attaches logging context to the request.
func Middleware(serviceName, version, application string) func(next http.Handler) http.Handler {
func Middleware(serviceName, version string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract the tracing information from the HTTP headers.
Expand Down

0 comments on commit 0ee9428

Please sign in to comment.