Skip to content

Commit

Permalink
Merge pull request #37 from Kuadrant/generate-authconfig
Browse files Browse the repository at this point in the history
generate kuadrant authconfig
  • Loading branch information
eguzki authored Jan 31, 2022
2 parents 6050b45 + 7d6edc4 commit 369b4c3
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ go install github.com/kuadrant/kuadrantctl@latest
* [Apply Kuadrant API objects](doc/api-apply.md)
* [Generate Istio virtualservice objects](doc/generate-istio-virtualservice.md)
* [Generate Istio authenticationpolicy objects](doc/generate-istio-authorizationpolicy.md)
* [Generate kuadrat authconfig objects](doc/generate-kuadrant-authconfig.md)

## Contributing
The [Development guide](doc/development.md) describes how to build the kuadrantctl CLI and how to test your changes before submitting a patch or opening a PR.
Expand Down
1 change: 1 addition & 0 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func generateCommand() *cobra.Command {
}

cmd.AddCommand(generateIstioCommand())
cmd.AddCommand(generateKuadrantCommand())

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

import (
"github.com/spf13/cobra"
)

func generateKuadrantCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "kuadrant",
Short: "Generate Kuadrant resources",
Long: "Generate Kuadrant resources",
}

cmd.AddCommand(generateKuadrantAuthconfigCommand())

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

import (
"encoding/json"
"fmt"

"github.com/getkin/kin-openapi/openapi3"
authorinov1beta1 "github.com/kuadrant/authorino/api/v1beta1"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

authorinoutils "github.com/kuadrant/kuadrantctl/pkg/authorino"
"github.com/kuadrant/kuadrantctl/pkg/utils"
)

var (
generateKuadrantAuthConfigOAS string
generateKuadrantAuthConfigPublicHost string
)

func generateKuadrantAuthconfigCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "authconfig",
Short: "Generate kuadrant authconfig from OpenAPI 3.x",
Long: "Generate kuadrant authconfig from OpenAPI 3.x",
RunE: func(cmd *cobra.Command, args []string) error {
return runGenerateKuadrantAuthconfigCommand(cmd, args)
},
}

// OpenAPI ref
cmd.Flags().StringVar(&generateKuadrantAuthConfigOAS, "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)
}

// public host
cmd.Flags().StringVar(&generateKuadrantAuthConfigPublicHost, "public-host", "", "The address used by a client when attempting to connect to a service (required)")
err = cmd.MarkFlagRequired("public-host")
if err != nil {
panic(err)
}

return cmd
}

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

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

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

authConfig, err := generateKuadrantAuthConfig(cmd, doc)
if err != nil {
return err
}

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

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

func generateKuadrantAuthConfig(cmd *cobra.Command, doc *openapi3.T) (*authorinov1beta1.AuthConfig, error) {
objectName, err := utils.K8sNameFromOpenAPITitle(doc)
if err != nil {
return nil, err
}

identityList, err := authorinoutils.AuthConfigIdentitiesFromOpenAPI(doc)
if err != nil {
return nil, err
}

metadataList, err := generateKuadrantAuthConfigMetadata(doc)
if err != nil {
return nil, err
}

authorizationList, err := generateKuadrantAuthConfigAuthorization(doc)
if err != nil {
return nil, err
}

responseList, err := generateKuadrantAuthConfigResponse(doc)
if err != nil {
return nil, err
}

authConfig := &authorinov1beta1.AuthConfig{
TypeMeta: metav1.TypeMeta{
Kind: "AuthConfig",
APIVersion: "authorino.kuadrant.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: objectName,
},
Spec: authorinov1beta1.AuthConfigSpec{
Hosts: []string{generateKuadrantAuthConfigPublicHost},
Identity: identityList,
Metadata: metadataList,
Authorization: authorizationList,
Response: responseList,
Patterns: nil,
Conditions: nil,
},
}
return authConfig, nil
}

func generateKuadrantAuthConfigMetadata(doc *openapi3.T) ([]*authorinov1beta1.Metadata, error) {
return nil, nil
}

func generateKuadrantAuthConfigAuthorization(doc *openapi3.T) ([]*authorinov1beta1.Authorization, error) {
return nil, nil
}

func generateKuadrantAuthConfigResponse(doc *openapi3.T) ([]*authorinov1beta1.Response, error) {
return nil, nil
}
29 changes: 29 additions & 0 deletions doc/generate-kuadrant-authconfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Generate Kuadrant AuthConfig objects

The `kuadrantctl generate kuadrant authconfig` command generates an [Authorino AuthConfig](https://github.com/Kuadrant/authorino/blob/v0.7.0/docs/architecture.md#the-authorino-authconfig-custom-resource-definition-crd)
from your [OpenAPI Specification (OAS) 3.x](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.2.md) and kubernetes service information.

### OpenAPI specification

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
$ kuadrantctl generate kuadrant authconfig -h
Generate kuadrant authconfig from OpenAPI 3.x

Usage:
kuadrantctl generate kuadrant authconfig [flags]

Flags:
-h, --help help for authconfig
--oas string /path/to/file.[json|yaml|yml] OR http[s]://domain/resource/path.[json|yaml|yml] OR - (required)
--public-host string The address used by a client when attempting to connect to a service (required)

Global Flags:
-v, --verbose verbose output
```
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.16
require (
github.com/getkin/kin-openapi v0.76.0
github.com/google/uuid v1.3.0
github.com/kuadrant/authorino v0.7.0
github.com/kuadrant/authorino-operator v0.1.0
github.com/kuadrant/kuadrant-controller v0.2.1
github.com/kuadrant/limitador-operator v0.2.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kuadrant/authorino v0.7.0 h1:YgeFzteyzlfFZqWZN3b7cXbyOZZ2vgHP2UHp7yktrvg=
github.com/kuadrant/authorino v0.7.0/go.mod h1:+ddl2McmSC8vKufmR/iA4ILOwkTCe5dbX+uzWSZ4w1Q=
github.com/kuadrant/authorino-operator v0.1.0 h1:2MluwjhdtQl/z3C5BkM7BMvQr8WRzlUrDRe96DYdq6w=
github.com/kuadrant/authorino-operator v0.1.0/go.mod h1:enEBTG0San0QUHhqZ08OXiVDkURkRE2UBEQelcNTiGI=
Expand Down
134 changes: 134 additions & 0 deletions pkg/authorino/auth_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package authorino

import (
"fmt"
"strings"

"github.com/getkin/kin-openapi/openapi3"
authorinov1beta1 "github.com/kuadrant/authorino/api/v1beta1"

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

func AuthConfigIdentitiesFromOpenAPI(oasDoc *openapi3.T) ([]*authorinov1beta1.Identity, error) {
identities := []*authorinov1beta1.Identity{}

workloadName, err := utils.K8sNameFromOpenAPITitle(oasDoc)
if err != nil {
return nil, err
}

for path, pathItem := range oasDoc.Paths {
for opVerb, operation := range pathItem.Operations() {
secReqsP := utils.OpenAPIOperationSecRequirements(oasDoc, operation)

if secReqsP == nil {
continue
}

for _, secReq := range *secReqsP {
// Authorino AuthConfig currently only supports one identity method for each identity evaluator.
// It does not support, for instance, auth based on two api keys or api key AND oidc.
// Thus, some OpenAPI 3.X security requirements are not supported:
//
// Not Supported:
// security:
// - petstore_api_key: []
// toystore_api_key: []
// toystore_oidc: []
//
// Supported:
// security:
// - petstore_api_key: []
// - toystore_api_key: []
// - toystore_oidc: []
//

// scopes not being used now
for secSchemeName := range secReq {

secSchemeI, err := oasDoc.Components.SecuritySchemes.JSONLookup(secSchemeName)
if err != nil {
return nil, err
}

secScheme := secSchemeI.(*openapi3.SecurityScheme) // panic if assertion fails

identity, err := AuthConfigIdentityFromSecurityRequirement(
operation.OperationID, // TODO(eastizle): OperationID can be null, fallback to some custom name
path, opVerb, workloadName, secScheme)
if err != nil {
return nil, err
}

identities = append(identities, identity)
// currently only support for one schema per requirement
break
}
}

}
}
return identities, nil
}

func AuthConfigConditionsFromOperation(opPath, opVerb string) []authorinov1beta1.JSONPattern {
return []authorinov1beta1.JSONPattern{
{
JSONPatternExpression: authorinov1beta1.JSONPatternExpression{
Selector: `context.request.http.path.@extract:{"sep":"/"}`,
Operator: "eq",
Value: opPath,
},
},
{
JSONPatternExpression: authorinov1beta1.JSONPatternExpression{
Selector: "context.request.http.method.@case:lower",
Operator: "eq",
Value: strings.ToLower(opVerb),
},
},
}
}

func AuthConfigIdentityFromSecurityRequirement(name, opPath, opVerb, workloadName string, secScheme *openapi3.SecurityScheme) (*authorinov1beta1.Identity, error) {
if secScheme == nil {
return nil, fmt.Errorf("sec scheme nil for operation path:%s method:%s", opPath, opVerb)
}

identity := &authorinov1beta1.Identity{
Name: name,
Conditions: AuthConfigConditionsFromOperation(opPath, opVerb),
}

switch secScheme.Type {
case "apiKey":
AuthConfigIdentityFromApiKeyScheme(identity, secScheme, workloadName)
case "openIdConnect":
AuthConfigIdentityFromOIDCScheme(identity, secScheme)
default:
return nil, fmt.Errorf("sec scheme type %s not supported for path:%s method:%s", secScheme.Type, opPath, opVerb)
}

return identity, nil
}

func AuthConfigIdentityFromApiKeyScheme(identity *authorinov1beta1.Identity, secScheme *openapi3.SecurityScheme, workloadName string) {
// Fixed label selector for now
apikey := authorinov1beta1.Identity_APIKey{
LabelSelectors: map[string]string{
"authorino.kuadrant.io/managed-by": "authorino",
"app": workloadName,
},
}

identity.Credentials.In = authorinov1beta1.Credentials_In(secScheme.In)
identity.Credentials.KeySelector = secScheme.Name
identity.APIKey = &apikey
}

func AuthConfigIdentityFromOIDCScheme(identity *authorinov1beta1.Identity, secScheme *openapi3.SecurityScheme) {
identity.Oidc = &authorinov1beta1.Identity_OidcConfig{
Endpoint: secScheme.OpenIdConnectUrl,
}
}

0 comments on commit 369b4c3

Please sign in to comment.