diff --git a/cmd/generate_kuadrant.go b/cmd/generate_kuadrant.go index 8da869e..3e575d6 100644 --- a/cmd/generate_kuadrant.go +++ b/cmd/generate_kuadrant.go @@ -12,6 +12,7 @@ func generateKuadrantCommand() *cobra.Command { } cmd.AddCommand(generateKuadrantRateLimitPolicyCommand()) + cmd.AddCommand(generateKuadrantAuthPolicyCommand()) return cmd } diff --git a/cmd/generate_kuadrant_authpolicy.go b/cmd/generate_kuadrant_authpolicy.go new file mode 100644 index 0000000..177e50a --- /dev/null +++ b/cmd/generate_kuadrant_authpolicy.go @@ -0,0 +1,97 @@ +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), + }, + RouteSelectors: kuadrantapi.AuthPolicyTopRouteSelectorsFromOAS(doc), + }, + } + + if routeMeta.Namespace != "" { + ap.Spec.TargetRef.Namespace = &[]gatewayapiv1beta1.Namespace{ + gatewayapiv1beta1.Namespace(routeMeta.Namespace), + }[0] + } + + return ap +} diff --git a/cmd/generate_kuadrant_ratelimitpolicy.go b/cmd/generate_kuadrant_ratelimitpolicy.go index ed998f9..9115607 100644 --- a/cmd/generate_kuadrant_ratelimitpolicy.go +++ b/cmd/generate_kuadrant_ratelimitpolicy.go @@ -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{ diff --git a/doc/generate-kuadrant-auth-policy.md b/doc/generate-kuadrant-auth-policy.md new file mode 100644 index 0000000..39e99a6 --- /dev/null +++ b/doc/generate-kuadrant-auth-policy.md @@ -0,0 +1,251 @@ +## Generate Kuadrant AuthPolicy object from OpenAPI 3 + +The `kuadrantctl generate kuadrant authpolicy` command generates an [Kuadrant AuthPolicy](https://github.com/Kuadrant/kuadrant-operator/blob/v0.4.1/doc/auth.md) +from your [OpenAPI Specification (OAS) 3.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md) powered with [kuadrant extensions](openapi-kuadrant-extensions.md). + +### OpenAPI specification + +[OpenAPI `v3.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md) + +OpenAPI document resource can be provided by one of the following channels: +* Filename in the available path. +* URL format (supported schemes are HTTP and HTTPS). The CLI will try to download from the given address. +* Read from stdin standard input stream. + +#### openIdConnect type +This initial version of the command only generates AuhPolicy when there is at least one security requirement referencing the +[Security Scheme Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-scheme-object) which type is `openIdConnect`. + +### Description + +The following OAS example has one protected endpoint `GET /dog` with OIDC sec scheme. + +```yaml +paths: + /dog: + get: + operationId: "getDog" + security: + - securedDog: [] + responses: + 405: + description: "invalid input" +components: + securitySchemes: + securedDog: + type: openIdConnect + openIdConnectUrl: https://example.com/.well-known/openid-configuration +``` + +Running the command + +``` +kuadrantctl generate kuadrant authpolicy --oas ./petstore-openapi.yaml | yq -P +``` + +The generated authpolicy (only relevan fields shown here): + +```yaml +kind: AuthPolicy +apiVersion: kuadrant.io/v1beta2 +metadata: + name: petstore + namespace: petstore + creationTimestamp: null +spec: + routeSelectors: + - matches: + - path: + type: Exact + value: /api/v1/dog + method: GET + rules: + authentication: + getDog: + credentials: {} + jwt: + issuerUrl: https://example.com/.well-known/openid-configuration + routeSelectors: + - matches: + - path: + type: Exact + value: /api/v1/dog + method: GET +``` + +### Usage + +```shell +Generate Kuadrant AuthPolicy from OpenAPI 3.0.X + +Usage: + kuadrantctl generate kuadrant authpolicy [flags] + +Flags: + -h, --help help for authpolicy + --oas string /path/to/file.[json|yaml|yml] OR http[s]://domain/resource/path.[json|yaml|yml] OR @ (required) + +Global Flags: + -v, --verbose verbose output +``` + +> Under the example folder there are examples of OAS 3 that can be used to generate the resources + +### User Guide + +* [Optional] Setup SSO service supporting OIDC. For this example, we will be using [keycloak](https://www.keycloak.org). + * Create a new realm `petstore` + * Create a client `petstore`. In the Client Protocol field, select `openid-connect`. + * Configure client settings. Access Type to public. Direct Access Grants Enabled to ON (for this example password will be used directly to generate the token). + * Add a user to the realm + * Click the Users menu on the left side of the window. Click Add user. + * Type the username `bob`, set the Email Verified switch to ON, and click Save. + * On the Credentials tab, set the password `p`. Enter the password in both the fields, set the Temporary switch to OFF to avoid the password reset at the next login, and click `Set Password`. + +Now, let's run local cluster to test the kuadrantctl new command to generate authpolicy. + +* Clone the repo + +```bash +git clone https://github.com/Kuadrant/kuadrantctl.git +cd kuadrantctl +``` + +* Setup cluster, istio and Gateway API CRDs + +```bash +make local-setup +``` + +* Build and install CLI in `bin/kuadrantctl` path + +```bash +make install +``` + +* Install Kuadrant service protection. The CLI can be used to install kuadrant v0.4.1 + +```bash +bin/kuadrantctl install +``` + +* Deploy petstore backend API + +```bash +kubectl create namespace petstore +kubectl apply -n petstore -f examples/petstore/petstore.yaml +``` + +* Let's create Petstore's OpenAPI spec + +
+ +```yaml +cat <petstore-openapi.yaml +--- +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: + backendRefs: + - name: petstore + port: 80 + namespace: petstore + get: # public (not auth) + operationId: "getCat" + responses: + 405: + description: "invalid input" + /dog: + x-kuadrant: + backendRefs: + - name: petstore + port: 80 + namespace: petstore + get: # secured + operationId: "getDog" + security: + - openIdConnect: [] + responses: + 405: + description: "invalid input" +components: + securitySchemes: + openIdConnect: + type: openIdConnect + openIdConnectUrl: https://${KEYCLOAK_PUBLIC_DOMAIN}/auth/realms/petstore +EOF +``` +
+ +> Replace `${KEYCLOAK_PUBLIC_DOMAIN}` with your SSO instance domain + +| Operation | Applied config | +| --- | --- | +| `GET /api/v1/cat` | public (not auth) | +| `GET /api/v1/dog` | OIDC authenticatred | + +* Create the HTTPRoute using the CLI +```bash +bin/kuadrantctl generate gatewayapi httproute --oas petstore-openapi.yaml | kubectl apply -n petstore -f - +``` + +* Create Kuadrant's Auth Policy +```bash +bin/kuadrantctl generate kuadrant authpolicy --oas petstore-openapi.yaml | kubectl apply -n petstore -f - +``` + +Now, we are ready to test OpenAPI endpoints :exclamation: + +- `GET /api/v1/cat` -> It's a public endpoint, hence should return 200 Ok +```bash +curl -H "Host: example.com" -i "http://127.0.0.1:9080/api/v1/cat" +``` +- `GET /api/v1/dog` -> It's a secured endpoint, hence, without credentials, it should return 401 +```bash +curl -H "Host: example.com" -i "http://127.0.0.1:9080/api/v1/dog" +``` +``` +HTTP/1.1 401 Unauthorized +www-authenticate: Bearer realm="getDog" +x-ext-auth-reason: credential not found +date: Tue, 28 Nov 2023 09:38:26 GMT +server: istio-envoy +content-length: 0 +``` +- Get authentication token. This example is using Direct Access Grants oauth2 grant type (also known as Client Credentials grant type). When configuring the Keycloak (OIDC provider) client settings, we enabled Direct Access Grants to enable this procedure. We will be authenticating as `bob` user with `p` password. We previously created `bob` user in Keycloak in the `petstore` realm. +``` +export ACCESS_TOKEN=$(curl -k -H "Content-Type: application/x-www-form-urlencoded" \ + -d 'grant_type=password' \ + -d 'client_id=petstore' \ + -d 'scope=openid' \ + -d 'username=bob' \ + -d 'password=p' "https://${KEYCLOAK_PUBLIC_DOMAIN}/auth/realms/petstore/protocol/openid-connect/token" | jq -r '.access_token') +``` +> Replace `${KEYCLOAK_PUBLIC_DOMAIN}` with your SSO instance domain + +With the access token in place, let's try to get those puppies + +```bash +curl -H "Authorization: Bearer $ACCESS_TOKEN" -H 'Host: example.com' http://127.0.0.1:9080/api/v1/dog -i +``` +should return 200 Ok + +* Clean environment +```bash +make local-cleanup +``` diff --git a/doc/generate-kuadrant-rate-limit-policy.md b/doc/generate-kuadrant-rate-limit-policy.md new file mode 100644 index 0000000..7a5dbee --- /dev/null +++ b/doc/generate-kuadrant-rate-limit-policy.md @@ -0,0 +1,183 @@ +## Generate Kuadrant RateLimitPolicy object from OpenAPI 3 + +The `kuadrantctl generate kuadrant ratelimitpolicy` command generates an [Kuadrant RateLimitPolicy](https://github.com/Kuadrant/kuadrant-operator/blob/v0.4.1/doc/rate-limiting.md) +from your [OpenAPI Specification (OAS) 3.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md) powered with [kuadrant extensions](openapi-kuadrant-extensions.md). + +### OpenAPI specification + +[OpenAPI `v3.0`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md) + +OpenAPI document resource can be provided by one of the following channels: +* Filename in the available path. +* URL format (supported schemes are HTTP and HTTPS). The CLI will try to download from the given address. +* Read from stdin standard input stream. + +### Usage + +```shell +Generate Kuadrant RateLimitPolicy from OpenAPI 3.0.X + +Usage: + kuadrantctl generate kuadrant ratelimitpolicy [flags] + +Flags: + -h, --help help for ratelimitpolicy + --oas string /path/to/file.[json|yaml|yml] OR http[s]://domain/resource/path.[json|yaml|yml] OR @ (required) + +Global Flags: + -v, --verbose verbose output +``` + +> Under the example folder there are examples of OAS 3 that can be used to generate the resources + +### User Guide + +* Clone the repo +```bash +git clone https://github.com/Kuadrant/kuadrantctl.git +cd kuadrantctl +``` +* Setup cluster, istio and Gateway API CRDs +```bash +make local-setup +``` +* Build and install CLI in `bin/kuadrantctl` path +```bash +make install +``` +* Install Kuadrant service protection. The CLI can be used to install kuadrant v0.4.1 +```bash +bin/kuadrantctl install +``` +* Deploy petstore backend API +```bash +kubectl create namespace petstore +kubectl apply -n petstore -f examples/petstore/petstore.yaml +``` +* Let's create Petstore's OpenAPI spec + +
+ +```yaml +cat <petstore-openapi.yaml +--- +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/v1 +paths: + /cat: + x-kuadrant: ## Path level Kuadrant Extension + backendRefs: + - name: petstore + port: 80 + namespace: petstore + rate_limit: + rates: + - limit: 1 + duration: 10 + unit: second + counters: + - request.headers.x-forwarded-for + get: # Added to the route and rate limited + operationId: "getCat" + responses: + 405: + description: "invalid input" + post: # NOT added to the route + x-kuadrant: + disable: true + operationId: "postCat" + responses: + 405: + description: "invalid input" + /dog: + get: # Added to the route and rate limited + x-kuadrant: ## Operation level Kuadrant Extension + backendRefs: + - name: petstore + port: 80 + namespace: petstore + rate_limit: + rates: + - limit: 3 + duration: 10 + unit: second + counters: + - request.headers.x-forwarded-for + operationId: "getDog" + responses: + 405: + description: "invalid input" + post: # Added to the route and NOT rate limited + x-kuadrant: ## Operation level Kuadrant Extension + backendRefs: + - name: petstore + port: 80 + namespace: petstore + operationId: "postDog" + responses: + 405: + description: "invalid input" +EOF +``` + +
+ +> **NOTE**: `servers` base path not included. WIP in following up PRs. + +| Operation | Applied config | +| --- | --- | +| `GET /cat` | It should return 200 Ok and be rate limited (1 req / 10 seconds) | +| `POST /cat` | Not added to the HTTPRoute. It should return 404 Not Found | +| `GET /dog` | It should return 200 Ok and be rate limited (3 req / 10 seconds) | +| `POST /dog` | It should return 200 Ok and NOT rate limited | + + +* Create the HTTPRoute using the CLI +```bash +bin/kuadrantctl generate gatewayapi httproute --oas petstore-openapi.yaml | kubectl apply -n petstore -f - +``` + +* Create the Rate Limit Policy +```bash +bin/kuadrantctl generate kuadrant ratelimitpolicy --oas petstore-openapi.yaml | kubectl apply -n petstore -f - +``` + +* Test OpenAPI endpoints + * `GET /cat` -> It should return 200 Ok and be rate limited (1 req / 10 seconds) + +```bash +curl --resolve example.com:9080:127.0.0.1 -v "http://example.com:9080/cat" +``` + * `POST /cat` -> Not added to the HTTPRoute. It should return 404 Not Found +```bash +curl --resolve example.com:9080:127.0.0.1 -v -X POST "http://example.com:9080/cat" +``` + * `GET /dog` -> It should return 200 Ok and be rate limited (3 req / 10 seconds) + +```bash +curl --resolve example.com:9080:127.0.0.1 -v "http://example.com:9080/dog" +``` + + * `POST /dog` -> It should return 200 Ok and NOT rate limited + +```bash +curl --resolve example.com:9080:127.0.0.1 -v -X POST "http://example.com:9080/dog" +``` + +* Clean environment +```bash +make local-cleanup +``` diff --git a/examples/oas3/petstore-with-oidc-kuadrant-extensions.yaml b/examples/oas3/petstore-with-oidc-kuadrant-extensions.yaml new file mode 100644 index 0000000..cdcc0b8 --- /dev/null +++ b/examples/oas3/petstore-with-oidc-kuadrant-extensions.yaml @@ -0,0 +1,53 @@ +--- +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 + 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 + 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 diff --git a/examples/oas3/petstore-with-kuadrant-extensions.yaml b/examples/oas3/petstore-with-rate-limit-kuadrant-extensions.yaml similarity index 77% rename from examples/oas3/petstore-with-kuadrant-extensions.yaml rename to examples/oas3/petstore-with-rate-limit-kuadrant-extensions.yaml index 1a3b444..f6e5548 100644 --- a/examples/oas3/petstore-with-kuadrant-extensions.yaml +++ b/examples/oas3/petstore-with-rate-limit-kuadrant-extensions.yaml @@ -17,7 +17,6 @@ servers: paths: /cat: x-kuadrant: ## Path level Kuadrant Extension - enable: true backendRefs: - name: petstore port: 80 @@ -36,18 +35,7 @@ paths: description: "invalid input" post: # NOT added to the route x-kuadrant: ## Operation level Kuadrant Extension - enable: false - backendRefs: - - name: petstore - port: 80 - namespace: petstore - rate_limit: - rates: - - limit: 2 - duration: 10 - unit: second - counters: - - request.headers.x-forwarded-for + disable: true operationId: "postCat" responses: 405: @@ -55,7 +43,6 @@ paths: /dog: get: # Added to the route and rate limited x-kuadrant: ## Operation level Kuadrant Extension - enable: true backendRefs: - name: petstore port: 80 @@ -73,7 +60,6 @@ paths: description: "invalid input" post: # Added to the route and NOT rate limited x-kuadrant: ## Operation level Kuadrant Extension - enable: true backendRefs: - name: petstore port: 80 @@ -82,9 +68,3 @@ paths: responses: 405: description: "invalid input" - /mouse: - get: # NOT added to the route - operationId: "getMouse" - responses: - 405: - description: "invalid input" diff --git a/make/kind.mk b/make/kind.mk index 4c26b5b..12f9aab 100644 --- a/make/kind.mk +++ b/make/kind.mk @@ -3,12 +3,12 @@ ## Targets to help install and use kind for development https://kind.sigs.k8s.io -KIND_CLUSTER_NAME ?= kuadrant-local +KIND_CLUSTER_NAME ?= kuadrantctl-local .PHONY: kind-create-cluster -kind-create-cluster: kind ## Create the "kuadrant-local" kind cluster. +kind-create-cluster: kind ## Create the "kuadrantctl-local" kind cluster. $(KIND) create cluster --name $(KIND_CLUSTER_NAME) --config utils/kind-cluster.yaml .PHONY: kind-delete-cluster -kind-delete-cluster: kind ## Delete the "kuadrant-local" kind cluster. +kind-delete-cluster: kind ## Delete the "kuadrantctl-local" kind cluster. - $(KIND) delete cluster --name $(KIND_CLUSTER_NAME) diff --git a/pkg/kuadrantapi/authpolicy.go b/pkg/kuadrantapi/authpolicy.go new file mode 100644 index 0000000..b302edd --- /dev/null +++ b/pkg/kuadrantapi/authpolicy.go @@ -0,0 +1,175 @@ +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, pathMatchType gatewayapiv1beta1.PathMatchType) []kuadrantapiv1beta2.RouteSelector { + match := utils.OpenAPIMatcherFromOASOperations(basePath, path, pathItem, verb, op, pathMatchType) + + return []kuadrantapiv1beta2.RouteSelector{ + { + Matches: []gatewayapiv1beta1.HTTPRouteMatch{match}, + }, + } +} + +func AuthPolicyTopRouteSelectorsFromOAS(doc *openapi3.T) []kuadrantapiv1beta2.RouteSelector { + routeSelectors := make([]kuadrantapiv1beta2.RouteSelector, 0) + + basePath, err := utils.BasePathFromOpenAPI(doc) + if err != nil { + panic(err) + } + + for path, pathItem := range doc.Paths { + kuadrantPathExtension, err := utils.NewKuadrantOASPathExtension(pathItem) + if err != nil { + panic(err) + } + + // Operations + for verb, operation := range pathItem.Operations() { + kuadrantOperationExtension, err := utils.NewKuadrantOASOperationExtension(operation) + if err != nil { + panic(err) + } + + if ptr.Deref(kuadrantOperationExtension.Disable, kuadrantPathExtension.IsDisabled()) { + // 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) + + // Top RouteSelectors define the matching rules to call external auth service + // group together any routes that has at least one security requirement + if len(secRequirements) == 0 { + // no security + continue + } + + // default pathMatchType at the path level + pathMatchType := ptr.Deref( + kuadrantOperationExtension.PathMatchType, + kuadrantPathExtension.GetPathMatchType(), + ) + + routeSelectors = append(routeSelectors, buildAuthPolicyRouteSelectors(basePath, path, pathItem, verb, operation, pathMatchType)...) + } + } + + if len(routeSelectors) == 0 { + return nil + } + + return routeSelectors +} + +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) + } + + // Operations + for verb, operation := range pathItem.Operations() { + kuadrantOperationExtension, err := utils.NewKuadrantOASOperationExtension(operation) + if err != nil { + panic(err) + } + + if ptr.Deref(kuadrantOperationExtension.Disable, kuadrantPathExtension.IsDisabled()) { + // 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 + } + + // default pathMatchType at the path level + pathMatchType := ptr.Deref( + kuadrantOperationExtension.PathMatchType, + kuadrantPathExtension.GetPathMatchType(), + ) + + oidcScheme := findOIDCSecuritySchemesFromRequirements(doc, secRequirements) + + if oidcScheme == nil { + // no oidc sec scheme found + continue + } + + authName := utils.OpenAPIOperationName(path, verb, operation) + + authentication[authName] = kuadrantapiv1beta2.AuthenticationSpec{ + CommonAuthRuleSpec: kuadrantapiv1beta2.CommonAuthRuleSpec{ + RouteSelectors: buildAuthPolicyRouteSelectors(basePath, path, pathItem, verb, operation, pathMatchType), + }, + 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 { + 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 +}