Skip to content

Commit

Permalink
feat: support mtls (#55)
Browse files Browse the repository at this point in the history
* feat: support mtls

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* fix conf

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* fix linter

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* sleep more time to destroy apisix

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* fix destroy apisix

Signed-off-by: Ling Samuel (WSL) <[email protected]>

* split to 2 test is faster than 1

Signed-off-by: Ling Samuel (WSL) <[email protected]>

---------

Signed-off-by: Ling Samuel (WSL) <[email protected]>
  • Loading branch information
lingsamuel authored Sep 21, 2023
1 parent 198d901 commit 40130e1
Show file tree
Hide file tree
Showing 70 changed files with 1,337 additions and 91 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/cli_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ jobs:
go install github.com/onsi/ginkgo/v2/ginkgo
sudo cp ~/go/bin/ginkgo /usr/local/bin
- name: Build ADC
working-directory: ./
run: |
make build
- name: Deploy Apache APISIX
run: |
curl -sL https://run.api7.ai/apisix/quickstart | sed 's/3.4.0-debian/dev/g' | sh
Expand All @@ -70,5 +75,4 @@ jobs:
working-directory: ./
run: |
export PATH=$PWD/bin:$PATH
make build
make test
78 changes: 78 additions & 0 deletions .github/workflows/mtls_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Run CLI mTLS E2E Test Suites

on:
push:
branches:
- main
- release/**
pull_request:
branches:
- main
- release/**
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
src: ${{ steps.filter.outputs.src }}
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: filter
with:
token: ${{ secrets.GITHUB_TOKEN }}
filters: |
src:
- '*.go'
- '**/*.go'
- 'go.mod'
- 'go.sum'
- 'Makefile'
- '.github/**'
- 'internal/**'
run-test:
needs: changes
if: |
(needs.changes.outputs.src == 'true')
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Setup Go Environment
uses: actions/setup-go@v3
with:
go-version: '1.20.3'

- name: Install ginkgo
run: |
go install github.com/onsi/ginkgo/v2/ginkgo
sudo cp ~/go/bin/ginkgo /usr/local/bin
- name: Build ADC
working-directory: ./
run: |
make build
- name: Deploy Apache APISIX (mTLS)
run: |
sh utils/quickstart-mtls.sh
docker run --name httpbin --network apisix-quickstart-net --hostname httpbin -d kennethreitz/httpbin
- name: Run E2E mTLS Test Suites
working-directory: ./
run: |
export PATH=$PWD/bin:$PATH
make test-mtls
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ build: ## Build adc
test: ## Run cli test
@cd test/cli && ginkgo -r --nodes=1

.PHONY: test-mtls
test-mtls: ## Run cli test for mtls
@cd test/mtls && ginkgo -r --nodes=1

.PHONY: unit-test
unit-test: ## Run unit test
@go test -v $$(go list ./... | grep -v /test/)
Expand Down
139 changes: 132 additions & 7 deletions cmd/configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ package cmd

import (
"bufio"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/fatih/color"
Expand All @@ -21,37 +26,157 @@ func newConfigureCmd() *cobra.Command {
Short: "Configure ADC with APISIX instance",
Long: `Configures ADC with APISIX's server address and token.`,
RunE: func(cmd *cobra.Command, args []string) error {
return saveConfiguration()
return saveConfiguration(cmd)
},
}

cmd.Flags().BoolP("overwrite", "f", false, "overwrite existed configuration file")

cmd.Flags().StringP("address", "a", "", "apisix server address")

cmd.Flags().String("capath", "", "ca path for mtls connection")
cmd.Flags().String("cert", "", "certificate for mtls connection")
cmd.Flags().String("cert-key", "", "certificate key for mtls connection")
cmd.Flags().BoolP("insecure", "k", false, "insecure connection for mtls connection")

return cmd
}

func saveConfiguration() error {
if rootConfig.Server != "" && rootConfig.Token != "" {
color.Yellow("ADC configured. Run `adc ping` to test the configuration.")
func saveConfiguration(cmd *cobra.Command) error {
overwrite, err := cmd.Flags().GetBool("overwrite")
if err != nil {
color.Red("Failed to get key: %v", err)
return err
}

if !overwrite && rootConfig.Server != "" && rootConfig.Token != "" {
color.Yellow("ADC configured. Run `adc ping` to test the configuration, or pass `-f` to overwrite configuration file.")
return nil
}

rootConfig.Server, err = cmd.Flags().GetString("address")
if err != nil {
color.Red("Failed to get apisix address: %v", err)
return err
}

rootConfig.CAPath, err = cmd.Flags().GetString("capath")
if err != nil {
color.Red("Failed to get ca path: %v", err)
return err
}

rootConfig.Certificate, err = cmd.Flags().GetString("cert")
if err != nil {
color.Red("Failed to get certificate path: %v", err)
return err
}
rootConfig.CertificateKey, err = cmd.Flags().GetString("cert-key")
if err != nil {
color.Red("Failed to get certificate key path: %v", err)
return err
}
rootConfig.Insecure, err = cmd.Flags().GetBool("insecure")
if err != nil {
color.Red("Failed to get insecure option: %v", err)
return err
}

if rootConfig.CAPath != "" {
if rootConfig.Certificate != "" && rootConfig.CertificateKey == "" {
color.Red("Certificate key file path no provided!")
return errors.New("certificate key file path no provided")
}

if rootConfig.Certificate == "" && rootConfig.CertificateKey != "" {
color.Red("Certificate file path no provided!")
return errors.New("certificate file path no provided")
}

rootConfig.CAPath, err = filepath.Abs(rootConfig.CAPath)
if err != nil {
color.Red("Failed to resolve CA path: %v", err)
return err
}
rootConfig.Certificate, err = filepath.Abs(rootConfig.Certificate)
if err != nil {
color.Red("Failed to resolve certificate path: %v", err)
return err
}
rootConfig.CertificateKey, err = filepath.Abs(rootConfig.CertificateKey)
if err != nil {
color.Red("Failed to resolve certificate key path: %v", err)
return err
}

if strings.HasPrefix(rootConfig.Server, "http://") {
color.Yellow("APISIX address is configured with HTTP protocol, replaced by HTTPS")
rootConfig.Server = strings.Replace(rootConfig.Server, "http://", "https://", 1)
}

rootCA, err := os.ReadFile(rootConfig.CAPath)
if err != nil {
color.Red("Failed to read CA file: %v", err)
return err
}

caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(rootCA)
if !ok {
color.Red("Failed to parse CA certificate")
return errors.New("failed to parse CA certificate")
}

cert, err := os.ReadFile(rootConfig.Certificate)
if err != nil {
color.Red("Failed to read certificate file: %v", err)
return err
}
key, err := os.ReadFile(rootConfig.CertificateKey)
if err != nil {
color.Red("Failed to read certificate key file: %v", err)
return err
}
_, err = tls.X509KeyPair(cert, key)
if err != nil {
color.Red("Failed to parse x509 key pair: %v", err)
return err
}
}

reader := bufio.NewReader(os.Stdin)
if rootConfig.Server == "" {
fmt.Println("Please enter the APISIX server address: ")
server, _ := reader.ReadString('\n')
rootConfig.Server = strings.TrimSpace(server)
}

if rootConfig.Token == "" {
_, err = url.Parse(rootConfig.Server)
if err != nil {
color.Red("Parse APISIX server address failed: %v", err)
return err
}

fmt.Println("Please enter the token: ")
if rootConfig.Token == "" {
fmt.Println("Please enter the APISIX token: ")
token, _ := reader.ReadString('\n')
rootConfig.Token = strings.TrimSpace(token)
}

// use viper to save the configuration
viper.Set("server", rootConfig.Server)
viper.Set("token", rootConfig.Token)
if err := viper.WriteConfig(); err != nil {
viper.Set("capath", rootConfig.CAPath)
viper.Set("cert", rootConfig.Certificate)
viper.Set("cert-key", rootConfig.CertificateKey)
viper.Set("insecure", rootConfig.Insecure)

if overwrite {
err = viper.WriteConfig()
} else {
err = viper.SafeWriteConfig()
}
if err != nil {
color.Red("Failed to configure ADC")
return err
}
Expand Down
8 changes: 5 additions & 3 deletions cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ func dumpConfiguration(cmd *cobra.Command) error {
return err
}
if path == "" {
color.Red("Output file path is empty. Please specify a file path: adc dump -o config.yaml")
return nil
path = "/dev/stdout"
}

save := true
if path == "/dev/stdout" {
save = false
}

cluster := apisix.NewCluster(context.Background(), rootConfig.Server, rootConfig.Token)
cluster, err := apisix.NewCluster(context.Background(), rootConfig.ClientConfig)
if err != nil {
return err
}

svcs, err := cluster.Service().List(context.Background())
if err != nil {
Expand Down
7 changes: 5 additions & 2 deletions cmd/ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ func newPingCmd() *cobra.Command {

// pingAPISIX check the connection to the APISIX
func pingAPISIX() error {
cluster := apisix.NewCluster(context.Background(), rootConfig.Server, rootConfig.Token)
cluster, err := apisix.NewCluster(context.Background(), rootConfig.ClientConfig)
if err != nil {
return err
}

err := cluster.Route().Validate(context.Background(), &types.Route{
err = cluster.Route().Validate(context.Background(), &types.Route{
ID: "test",
Name: "test",
Uri: "*",
Expand Down
15 changes: 12 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ import (
"github.com/api7/adc/pkg/config"
)

type Config struct {
config.ClientConfig
APISIXCluster apisix.Cluster
}

var (
cfgFile string
rootConfig config.ClientConfig
rootConfig Config
)

// rootCmd represents the base command when called without any subcommands
Expand Down Expand Up @@ -79,10 +84,14 @@ func initConfig() {

rootConfig.Server = viper.GetString("server")
rootConfig.Token = viper.GetString("token")
cluser := apisix.NewCluster(context.Background(), rootConfig.Server, rootConfig.Token)
rootConfig.CAPath = viper.GetString("capath")
rootConfig.Certificate = viper.GetString("cert")
rootConfig.CertificateKey = viper.GetString("cert-key")
rootConfig.Insecure = viper.GetBool("insecure")
cluster, err := apisix.NewCluster(context.Background(), rootConfig.ClientConfig)
if err != nil {
color.RedString("Failed to create a new cluster: %v", err)
return
}
rootConfig.APISIXCluster = cluser
rootConfig.APISIXCluster = cluster
}
7 changes: 5 additions & 2 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,17 @@ func newValidateCmd() *cobra.Command {

// validateContent validates the content of the configuration file
func validateContent(c *types.Configuration) error {
cluster := apisix.NewCluster(context.Background(), rootConfig.Server, rootConfig.Token)
cluster, err := apisix.NewCluster(context.Background(), rootConfig.ClientConfig)
if err != nil {
return err
}
v, err := validator.NewValidator(c, cluster)
if err != nil {
color.Red("Failed to create validator: %v", err)
return err
}
errs := v.Validate()
if errs != nil && len(errs) > 0 {
if len(errs) > 0 {
color.Red("Some validation failed:")
for _, err := range errs {
color.Red(err.Error())
Expand Down
1 change: 1 addition & 0 deletions internal/pkg/openapi2apisix/openapi2apisix.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func createRoutes(doc *openapi3.T, serviceName string) []*apitypes.Route {
return routes
}

//nolint:unused
func getLabelsByTags(tags openapi3.Tags) apitypes.Labels {
labels := apitypes.Labels{}
for i, tag := range tags {
Expand Down
Loading

0 comments on commit 40130e1

Please sign in to comment.