Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .ci/setup_kong_ee.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,17 @@ readonly GATEWAY_CONTAINER_NAME=kong

KONG_ROUTER_FLAVOR=${KONG_ROUTER_FLAVOR:-'traditional_compatible'}
KONG_CUSTOM_PLUGIN_STREAMING_ENABLED=${KONG_CUSTOM_PLUGIN_STREAMING_ENABLED:-'on'}
KONG_ENFORCE_RBAC=${KONG_ENFORCE_RBAC:-'off'}
if [[ "${KONG_ENFORCE_RBAC}" == "on" ]]; then
KONG_PASSWORD=${KONG_PASSWORD:-'kong_admin_token'}
fi
KONG_PASSWORD=${KONG_PASSWORD:-''}

initNetwork
initDb
initMigrations "${KONG_IMAGE}" \
-e "KONG_LICENSE_DATA=${KONG_LICENSE_DATA}"
-e "KONG_LICENSE_DATA=${KONG_LICENSE_DATA}" \
-e "KONG_PASSWORD=${KONG_PASSWORD}"

# Start Kong Gateway EE
docker run \
Expand All @@ -83,6 +89,8 @@ docker run \
-e "MY_SECRET_KEY=$MY_SECRET_KEY" \
-e "KONG_ROUTER_FLAVOR=${KONG_ROUTER_FLAVOR}" \
-e "KONG_CUSTOM_PLUGIN_STREAMING_ENABLED=${KONG_CUSTOM_PLUGIN_STREAMING_ENABLED}" \
-e "KONG_ENFORCE_RBAC=${KONG_ENFORCE_RBAC}" \
-e "KONG_PASSWORD=${KONG_PASSWORD}" \
-p 8000:8000 \
-p 8443:8443 \
-p 8001:8001 \
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/integration-enterprise.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,61 @@ jobs:
KONG_LICENSE_DATA: ${{ steps.license.outputs.license }}
run: make test-integration
continue-on-error: ${{ matrix.kong_image == 'kong/kong-gateway-dev:latest' }}

integration-rbac:
# Skip if the PR has the 'skip-ee' label
if: ${{ github.event_name == 'push' || !contains(github.event.pull_request.labels.*.name, 'skip-ee') }}
timeout-minutes: ${{ fromJSON(vars.DECK_WORKFLOW_GW_TIMEOUT || '10') }}
strategy:
matrix:
kong_image:
- 'kong/kong-gateway:2.8'
Comment thread
VarunAthreya marked this conversation as resolved.
- 'kong/kong-gateway:3.4'
- 'kong/kong-gateway:3.5'
- 'kong/kong-gateway:3.6'
- 'kong/kong-gateway:3.7'
- 'kong/kong-gateway:3.8'
- 'kong/kong-gateway:3.9'
- 'kong/kong-gateway:3.10'
- 'kong/kong-gateway:3.11'
- 'kong/kong-gateway:3.12'
- 'kong/kong-gateway:3.13'
- 'kong/kong-gateway:3.14'
- 'kong/kong-gateway-dev:latest'
env:
KONG_ANONYMOUS_REPORTS: "off"
KONG_IMAGE: ${{ matrix.kong_image }}
KONG_ENFORCE_RBAC: 'on'
KONG_PASSWORD: 'kong_admin_token'
KONG_ADMIN_TOKEN: 'kong_admin_token'

runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Setup go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
- name: Login to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
with:
username: ${{secrets.DOCKER_ORG_NAME}}
password: ${{secrets.DOCKER_ORG_TOKEN}}
- uses: Kong/kong-license@master
id: license
with:
op-token: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
- name: Setup Kong
env:
KONG_LICENSE_DATA: ${{ steps.license.outputs.license }}
run: make setup-kong-ee
- name: Run RBAC admin-token integration test
env:
KONG_LICENSE_DATA: ${{ steps.license.outputs.license }}
run: make test-integration GOTESTFLAGS='-run Test_RBAC_AdminToken'
continue-on-error: ${{ matrix.kong_image == 'kong/kong-gateway-dev:latest' }}
50 changes: 45 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ It can be used to export, import, or sync entities to Kong.`,

rootCmd.MarkFlagsMutuallyExclusive("konnect-runtime-group-name", "konnect-control-plane-name")

rootCmd.PersistentFlags().String("kong-admin-token", "",
"Token to use for authentication with Kong's Admin API.\n"+
"This value can also be set using DECK_KONG_ADMIN_TOKEN "+
"environment variable.")
viper.BindPFlag("kong-admin-token",
rootCmd.PersistentFlags().Lookup("kong-admin-token"))

rootCmd.AddCommand(newVersionCmd())
rootCmd.AddCommand(newCompletionCmd())
rootCmd.AddCommand(newSyncCmd(true)) // deprecated, to exist under the `gateway` subcommand only
Expand Down Expand Up @@ -332,7 +339,11 @@ func initConfig() {
tlsSkipVerify := viper.GetBool("tls-skip-verify")
tlsCACert := caCertContent

rootConfig.Headers = extendHeaders(viper.GetStringSlice("headers"))
rootConfig.Headers = extendHeaders(
viper.GetStringSlice("headers"),
header{name: "Kong-Admin-Token", value: viper.GetString("kong-admin-token")},
)

rootConfig.SkipWorkspaceCrud = viper.GetBool("skip-workspace-crud")
rootConfig.Debug = (viper.GetInt("verbose") >= 1)
rootConfig.Timeout = (viper.GetInt("timeout"))
Expand Down Expand Up @@ -451,10 +462,39 @@ func initKonnectConfig() error {
return nil
}

func extendHeaders(headers []string) []string {
userAgentHeader := fmt.Sprintf("User-Agent:decK/%s", VERSION)
headers = append(headers, userAgentHeader)
return headers
type header struct {
name string
value string
}

func extendHeaders(headers []string, extra ...header) []string {
result := make([]string, 0, len(headers)+len(extra)+1)
result = append(result, fmt.Sprintf("User-Agent:decK/%s", VERSION))

for _, h := range headers {
name, _, _ := strings.Cut(h, ":")
if !overriddenBy(name, extra) {
Comment thread
VarunAthreya marked this conversation as resolved.
result = append(result, h)
}
}

for _, h := range extra {
if h.value != "" {
result = append(result, fmt.Sprintf("%s:%s", h.name, h.value))
}
}
return result
}

// reports whether the given header name collides with an explicit
Comment thread
VarunAthreya marked this conversation as resolved.
Outdated
func overriddenBy(name string, extra []header) bool {
name = strings.TrimSpace(name)
for _, h := range extra {
if h.value != "" && strings.EqualFold(name, h.name) {
return true
}
}
return false
}

func init() {
Expand Down
79 changes: 79 additions & 0 deletions tests/integration/rbac_admin_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//go:build integration

package integration

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Test_RBAC_AdminToken exercises the --kong-admin-token flag (added to
// authenticate against an RBAC-enabled Kong Admin API).
//
// It only runs against an Enterprise Kong instance with RBAC enforcement
// enabled, which is provided by the dedicated `integration-rbac` job in
// .github/workflows/integration-enterprise.yaml. When RBAC is enforced, a
// command that talks to the Admin API must fail without a valid admin token
// and succeed once the token is supplied.
func Test_RBAC_AdminToken(t *testing.T) {
runWhenRBAC(t, ">=2.8.0")

// disable analytics for integration tests
t.Setenv("DECK_ANALYTICS", "off")

// The CI job seeds the kong_admin token in KONG_ADMIN_TOKEN; fall back to
// the decK CLI variable for local runs.
adminToken := os.Getenv("KONG_ADMIN_TOKEN")
if adminToken == "" {
adminToken = os.Getenv("DECK_KONG_ADMIN_TOKEN")
}
require.NotEmpty(t, adminToken,
"KONG_ADMIN_TOKEN or DECK_KONG_ADMIN_TOKEN must be set when running RBAC tests")

// online validation hits the Admin API but does not mutate state, so no
// reset/cleanup (which would itself require the token) is needed.
const stateFile = "testdata/validate/kong-ee.yaml"

t.Run("fails when kong-admin-token is not passed", func(t *testing.T) {
// make sure the CLI cannot pick the token up from the environment, so
// the request reaches Kong unauthenticated.
t.Setenv("DECK_KONG_ADMIN_TOKEN", "")

err := validate(ONLINE, stateFile)
require.Error(t, err,
"online validate should fail against an RBAC-enabled Kong without an admin token")
// Assert it fails *specifically* because of authentication (HTTP 401)
// rather than some unrelated error (bad file, gateway down, etc.).
// go-kong formats API errors as `HTTP status 401 (message: ...)`.
assert.Contains(t, err.Error(), "401",
Comment thread
VarunAthreya marked this conversation as resolved.
Outdated
"expected an authentication failure (HTTP 401), got: %v", err)
})

t.Run("succeeds when kong-admin-token is passed", func(t *testing.T) {
// scrub the env so the token can only come from the flag, proving the
// flag is what authenticates the request.
t.Setenv("DECK_KONG_ADMIN_TOKEN", "")

err := validate(ONLINE, stateFile, "--kong-admin-token", adminToken)
require.NoError(t, err,
"online validate should succeed against an RBAC-enabled Kong with a valid admin token")
})

t.Run("kong-admin-token takes precedence over a colliding --headers value", func(t *testing.T) {
// scrub the env so the token can only come from the flags under test.
t.Setenv("DECK_KONG_ADMIN_TOKEN", "")

// Supply an invalid Kong-Admin-Token via --headers alongside the valid
// token via --kong-admin-token. The explicit flag must win, so the
// invalid header value is dropped and the request authenticates.
err := validate(ONLINE, stateFile,
"--headers", "Kong-Admin-Token:invalid-token",
"--kong-admin-token", adminToken)
require.NoError(t, err,
"online validate should succeed: --kong-admin-token must override the "+
"colliding Kong-Admin-Token supplied via --headers")
})
}
6 changes: 6 additions & 0 deletions tests/integration/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,12 @@ func runWhenExpressions(t *testing.T, semverRange string) {
kong.RunWhenKongRouterFlavor(t, "expressions")
}

func runWhenRBAC(t *testing.T, semverRange string) {
t.Helper()
skipWhenKonnect(t)
kong.RunWhenEnterprise(t, semverRange, kong.RequiredFeatures{RBAC: true})
}

func sortSlices(x, y interface{}) bool {
var xName, yName string
switch xEntity := x.(type) {
Expand Down
Loading