Skip to content

Commit

Permalink
kuadrant authpolicy command
Browse files Browse the repository at this point in the history
  • Loading branch information
eguzki committed Nov 16, 2023
1 parent 8186bce commit 1c9d628
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/generate_kuadrant.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func generateKuadrantCommand() *cobra.Command {
}

cmd.AddCommand(generateKuadrantRateLimitPolicyCommand())
cmd.AddCommand(generateKuadrantAuthPolicyCommand())

return cmd
}
96 changes: 96 additions & 0 deletions cmd/generate_kuadrant_authpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package cmd

import (
"encoding/json"
"fmt"

"github.com/getkin/kin-openapi/openapi3"
kuadrantapiv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kuadrant/kuadrantctl/pkg/gatewayapi"
"github.com/kuadrant/kuadrantctl/pkg/kuadrantapi"
"github.com/kuadrant/kuadrantctl/pkg/utils"
)

//kuadrantctl generate kuadrant authpolicy --oas [OAS_FILE_PATH | OAS_URL | @]

func generateKuadrantAuthPolicyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "authpolicy",
Short: "Generate Kuadrant AuthPolicy from OpenAPI 3.0.X",
Long: "Generate Kuadrant AuthPolicy from OpenAPI 3.0.X",
RunE: runGenerateKuadrantAuthPolicy,
}

// OpenAPI ref
cmd.Flags().StringVar(&generateGatewayAPIHTTPRouteOAS, "oas", "", "/path/to/file.[json|yaml|yml] OR http[s]://domain/resource/path.[json|yaml|yml] OR @ (required)")
err := cmd.MarkFlagRequired("oas")
if err != nil {
panic(err)
}

return cmd
}

func runGenerateKuadrantAuthPolicy(cmd *cobra.Command, args []string) error {
oasDataRaw, err := utils.ReadExternalResource(generateGatewayAPIHTTPRouteOAS)
if err != nil {
return err
}

openapiLoader := openapi3.NewLoader()
doc, err := openapiLoader.LoadFromData(oasDataRaw)
if err != nil {
return err
}

err = doc.Validate(openapiLoader.Context)
if err != nil {
return fmt.Errorf("OpenAPI validation error: %w", err)
}

ap := buildAuthPolicy(doc)

jsonData, err := json.Marshal(ap)
if err != nil {
return err
}

fmt.Fprintln(cmd.OutOrStdout(), string(jsonData))
return nil
}

func buildAuthPolicy(doc *openapi3.T) *kuadrantapiv1beta2.AuthPolicy {
routeMeta := gatewayapi.HTTPRouteObjectMetaFromOAS(doc)

ap := &kuadrantapiv1beta2.AuthPolicy{
TypeMeta: v1.TypeMeta{
APIVersion: "kuadrant.io/v1beta2",
Kind: "AuthPolicy",
},
ObjectMeta: kuadrantapi.AuthPolicyObjectMetaFromOAS(doc),
Spec: kuadrantapiv1beta2.AuthPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: gatewayapiv1beta1.Group("gateway.networking.k8s.io"),
Kind: gatewayapiv1beta1.Kind("HTTPRoute"),
Name: gatewayapiv1beta1.ObjectName(routeMeta.Name),
},
// Currently only authentication rules enforced
AuthScheme: kuadrantapiv1beta2.AuthSchemeSpec{
Authentication: kuadrantapi.AuthPolicyAuthenticationSchemeFromOAS(doc),
},
},
}

if routeMeta.Namespace != "" {
ap.Spec.TargetRef.Namespace = &[]gatewayapiv1beta1.Namespace{
gatewayapiv1beta1.Namespace(routeMeta.Namespace),
}[0]
}

return ap
}
2 changes: 1 addition & 1 deletion cmd/generate_kuadrant_ratelimitpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/kuadrant/kuadrantctl/pkg/utils"
)

//kuadrantctl generate kuadrant httproute --oas [OAS_FILE_PATH | OAS_URL | @]
//kuadrantctl generate kuadrant ratelimitpolicy --oas [OAS_FILE_PATH | OAS_URL | @]

func generateKuadrantRateLimitPolicyCommand() *cobra.Command {
cmd := &cobra.Command{
Expand Down
55 changes: 55 additions & 0 deletions examples/oas3/petstore-with-oidc-kuadrant-extensions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
openapi: "3.0.3"
info:
title: "Pet Store API"
version: "1.0.0"
x-kuadrant:
route:
name: "petstore"
namespace: "petstore"
hostnames:
- example.com
parentRefs:
- name: istio-ingressgateway
namespace: istio-system
servers:
- url: https://example.io/api/v1
paths:
/cat:
x-kuadrant: ## Path level Kuadrant Extension
enable: true
backendRefs:
- name: petstore
port: 80
namespace: petstore
get: # Added to the route and public (not auth)
operationId: "getCat"
responses:
405:
description: "invalid input"
post: # NOT added to the route
x-kuadrant: ## Operation level Kuadrant Extension
enable: false
operationId: "postCat"
responses:
405:
description: "invalid input"
/dog:
get: # Added to the route and authenticated
x-kuadrant: ## Operation level Kuadrant Extension
enable: true
backendRefs:
- name: petstore
port: 80
namespace: petstore
operationId: "getDog"
security:
- openIdConnect: []
responses:
405:
description: "invalid input"
components:
securitySchemes:
openIdConnect:
type: openIdConnect
openIdConnectUrl: https://example.com/.well-known/openid-configuration
112 changes: 112 additions & 0 deletions pkg/kuadrantapi/authpolicy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package kuadrantapi

import (
"github.com/getkin/kin-openapi/openapi3"
authorinoapi "github.com/kuadrant/authorino/api/v1beta2"
kuadrantapiv1beta2 "github.com/kuadrant/kuadrant-operator/api/v1beta2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
gatewayapiv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"

"github.com/kuadrant/kuadrantctl/pkg/gatewayapi"
"github.com/kuadrant/kuadrantctl/pkg/utils"
)

func AuthPolicyObjectMetaFromOAS(doc *openapi3.T) metav1.ObjectMeta {
return gatewayapi.HTTPRouteObjectMetaFromOAS(doc)
}

func buildAuthPolicyRouteSelectors(basePath, path string, pathItem *openapi3.PathItem, verb string, op *openapi3.Operation) []kuadrantapiv1beta2.RouteSelector {
match := utils.OpenAPIMatcherFromOASOperations(basePath, path, pathItem, verb, op)

return []kuadrantapiv1beta2.RouteSelector{
{
Matches: []gatewayapiv1beta1.HTTPRouteMatch{match},
},
}
}

func AuthPolicyAuthenticationSchemeFromOAS(doc *openapi3.T) map[string]kuadrantapiv1beta2.AuthenticationSpec {
authentication := make(map[string]kuadrantapiv1beta2.AuthenticationSpec)

basePath, err := utils.BasePathFromOpenAPI(doc)
if err != nil {
panic(err)
}

// Paths
for path, pathItem := range doc.Paths {
kuadrantPathExtension, err := utils.NewKuadrantOASPathExtension(pathItem)
if err != nil {
panic(err)
}

pathEnabled := kuadrantPathExtension.IsEnabled()

// Operations
for verb, operation := range pathItem.Operations() {
kuadrantOperationExtension, err := utils.NewKuadrantOASOperationExtension(operation)
if err != nil {
panic(err)
}

if !ptr.Deref(kuadrantOperationExtension.Enable, pathEnabled) {
// not enabled for the operation
//fmt.Printf("OUT not enabled: path: %s, method: %s\n", path, verb)
continue
}

// Get operation level security requirements or fallback to global security requirements
secRequirements := ptr.Deref(operation.Security, doc.Security)

if len(secRequirements) == 0 {
// no security
continue
}

oidcScheme := findOIDCSecuritySchemesFromRequirements(doc, secRequirements)

authName := utils.OpenAPIOperationName(path, verb, operation)

authentication[authName] = kuadrantapiv1beta2.AuthenticationSpec{
CommonAuthRuleSpec: kuadrantapiv1beta2.CommonAuthRuleSpec{
RouteSelectors: buildAuthPolicyRouteSelectors(basePath, path, pathItem, verb, operation),
},
AuthenticationSpec: authorinoapi.AuthenticationSpec{
AuthenticationMethodSpec: authorinoapi.AuthenticationMethodSpec{
Jwt: &authorinoapi.JwtAuthenticationSpec{
IssuerUrl: oidcScheme.OpenIdConnectUrl,
},
},
},
}
}
}

if len(authentication) == 0 {
return nil
}

return authentication
}

func findOIDCSecuritySchemesFromRequirements(doc *openapi3.T, secRequirements openapi3.SecurityRequirements) *openapi3.SecurityScheme {
for _, secReq := range secRequirements {
for secReqItemName, _ := range secReq {

Check failure on line 95 in pkg/kuadrantapi/authpolicy.go

View workflow job for this annotation

GitHub Actions / Lint

S1005: unnecessary assignment to the blank identifier (gosimple)
secScheme, ok := doc.Components.SecuritySchemes[secReqItemName]
if !ok {
// should never happen. OpenAPI validation should detect this issue
continue
}
if secScheme == nil || secScheme.Value == nil {
continue
}
// Ref https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-23
if secScheme.Value.Type == "openIdConnect" {
return secScheme.Value
}
}
}

return nil
}

0 comments on commit 1c9d628

Please sign in to comment.