Skip to content

Commit

Permalink
Merge pull request #36 from Kuadrant/generate-istiointegration
Browse files Browse the repository at this point in the history
Generate istiointegration
  • Loading branch information
eguzki authored Jan 31, 2022
2 parents 578161b + 94ec0aa commit 6050b45
Show file tree
Hide file tree
Showing 16 changed files with 547 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/commands.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,4 @@ jobs:
make install
- name: run command
run: |
kuadrantctl install
bin/kuadrantctl install
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test: fmt vet $(GINKGO)
## install: Build and install kuadrantctl binary ($GOBIN or GOPATH/bin)
.PHONY : install
install: fmt vet
$(GO) install
GOBIN=$(PROJECT_PATH)/bin $(GO) install

.PHONY : fmt
fmt:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ go install github.com/kuadrant/kuadrantctl@latest
* [Install Kuadrant](doc/install.md)
* [Uninstall Kuadrant](doc/uninstall.md)
* [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)

## 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
17 changes: 17 additions & 0 deletions cmd/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

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

func generateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "generate",
Short: "Commands related to kubernetes object generation",
Long: "Commands related to kubernetes object generation",
}

cmd.AddCommand(generateIstioCommand())

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

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

func generateIstioCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "istio",
Short: "Generate Istio resources",
Long: "Generate Istio resorces",
}

cmd.AddCommand(generateIstioVirtualServiceCommand())
cmd.AddCommand(generateIstioAuthorizationPolicyCommand())

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

import (
"encoding/json"
"fmt"
"strings"

"github.com/getkin/kin-openapi/openapi3"
"github.com/spf13/cobra"
istiosecurityapi "istio.io/api/security/v1beta1"
istiotypeapi "istio.io/api/type/v1beta1"
istiosecurity "istio.io/client-go/pkg/apis/security/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/kuadrant/kuadrant-controller/pkg/common"
istioutils "github.com/kuadrant/kuadrantctl/pkg/istio"
"github.com/kuadrant/kuadrantctl/pkg/utils"
)

var (
generateIstioAPOAS string
generateIstioAPPublicHost string
generateIstioGatewayLabels []string
)

func generateIstioAuthorizationPolicyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "authorizationpolicy",
Short: "Generate Istio AuthorizationPolicy",
Long: "Generate Istio AuthorizationPolicy",
RunE: func(cmd *cobra.Command, args []string) error {
return runGenerateIstioAuthorizationPolicyCommand(cmd, args)
},
}

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

// gateway labels
cmd.Flags().StringSliceVar(&generateIstioGatewayLabels, "gateway-label", []string{}, "Gateway label (required)")
err = cmd.MarkFlagRequired("gateway-label")
if err != nil {
panic(err)
}

return cmd
}

func runGenerateIstioAuthorizationPolicyCommand(cmd *cobra.Command, args []string) error {
dataRaw, err := utils.ReadExternalResource(generateIstioAPOAS)
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)
}

ap, err := generateIstioAuthorizationPolicy(cmd, doc)
if err != nil {
return err
}

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

fmt.Fprintln(cmd.OutOrStdout(), string(jsonData))

return nil
}

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

matchLabels := map[string]string{}
for idx := range generateIstioGatewayLabels {
labels := strings.Split(generateIstioGatewayLabels[idx], "=")
if len(labels) != 2 {
return nil, fmt.Errorf("gateway labels have wrong syntax: %s", generateIstioGatewayLabels[idx])
}

matchLabels[labels[0]] = labels[1]
}

rules := istioutils.AuthorizationPolicyRulesFromOpenAPI(doc, generateIstioAPPublicHost)

authPolicy := &istiosecurity.AuthorizationPolicy{
TypeMeta: metav1.TypeMeta{
Kind: "AuthorizationPolicy",
APIVersion: "security.istio.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
// Missing namespace
Name: objectName,
},
Spec: istiosecurityapi.AuthorizationPolicy{
Selector: &istiotypeapi.WorkloadSelector{
MatchLabels: matchLabels,
},
Rules: rules,
Action: istiosecurityapi.AuthorizationPolicy_CUSTOM,
ActionDetail: &istiosecurityapi.AuthorizationPolicy_Provider{
Provider: &istiosecurityapi.AuthorizationPolicy_ExtensionProvider{
Name: common.KuadrantAuthorizationProvider,
},
},
},
}

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

import (
"encoding/json"
"fmt"

"github.com/getkin/kin-openapi/openapi3"
"github.com/spf13/cobra"
istionetworkingapi "istio.io/api/networking/v1beta1"
istionetworking "istio.io/client-go/pkg/apis/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

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

var (
generateIstioVSOAS string
generateIstioVSPublicHost string
generateIstioVSServiceName string
generateIstioVSServiceNamespace string
generateIstioVSServicePort int32
generateIstioVSGateways []string
)

func generateIstioVirtualServiceCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "virtualservice",
Short: "Generate Istio VirtualService from OpenAPI 3.x",
Long: "Generate Istio VirtualService from OpenAPI 3.x",
RunE: func(cmd *cobra.Command, args []string) error {
return rungenerateistiovirtualservicecommand(cmd, args)
},
}

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

// service name
cmd.Flags().StringVar(&generateIstioVSServiceName, "service-name", "", "Service name (required)")
err = cmd.MarkFlagRequired("service-name")
if err != nil {
panic(err)
}

// service namespace
cmd.Flags().StringVarP(&generateIstioVSServiceNamespace, "service-namespace", "", "", "Service namespace (required)")
err = cmd.MarkFlagRequired("service-namespace")
if err != nil {
panic(err)
}

// service port
cmd.Flags().Int32VarP(&generateIstioVSServicePort, "service-port", "p", 80, "Service port")

// gateways
cmd.Flags().StringSliceVar(&generateIstioVSGateways, "gateway", []string{}, "Gateways (required)")
err = cmd.MarkFlagRequired("gateway")
if err != nil {
panic(err)
}

return cmd
}

func rungenerateistiovirtualservicecommand(cmd *cobra.Command, args []string) error {
dataRaw, err := utils.ReadExternalResource(generateIstioVSOAS)
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)
}

vs, err := generateIstioVirtualService(cmd, doc)
if err != nil {
return err
}

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

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

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

destination := &istionetworkingapi.Destination{
Host: fmt.Sprintf("%s.%s.svc", generateIstioVSServiceName, generateIstioVSServiceNamespace),
Port: &istionetworkingapi.PortSelector{Number: uint32(generateIstioVSServicePort)},
}

httpRoutes, err := istioutils.HTTPRoutesFromOpenAPI(doc, destination)
if err != nil {
return nil, err
}

vs := &istionetworking.VirtualService{
TypeMeta: metav1.TypeMeta{
Kind: "VirtualService",
APIVersion: "networking.istio.io/v1beta1",
},
ObjectMeta: metav1.ObjectMeta{
// Missing namespace
Name: objectName,
},
Spec: istionetworkingapi.VirtualService{
Gateways: generateIstioVSGateways,
Hosts: []string{generateIstioVSPublicHost},
Http: httpRoutes,
},
}

return vs, nil
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func GetRootCmd(args []string) *cobra.Command {
rootCmd.AddCommand(installCommand())
rootCmd.AddCommand(uninstallCommand())
rootCmd.AddCommand(versionCommand())
rootCmd.AddCommand(generateCommand())

loggerOpts := zap.Options{Development: verbose}
logf.SetLogger(zap.New(zap.UseFlagOptions(&loggerOpts)))
Expand Down
30 changes: 30 additions & 0 deletions doc/generate-istio-authorizationpolicy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
## Generate Istio AuthorizationPolicy objects

The `kuadrantctl generate istio authorizationpolicy` command generates an [Istio AuthorizationPolicy](https://istio.io/latest/docs/reference/config/security/authorization-policy/)
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 istio authorizationpolicy -h
Generate Istio AuthorizationPolicy

Usage:
kuadrantctl generate istio authorizationpolicy [flags]

Flags:
--gateway-label strings Gateway label (required)
-h, --help help for authorizationpolicy
--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
```
Loading

0 comments on commit 6050b45

Please sign in to comment.