Skip to content

Commit

Permalink
APIGOV-26652 - Kong admin api security enhancements (#42)
Browse files Browse the repository at this point in the history
* use proxy admin-api route

* undo unnecessary changes to ParseProperties

* refactor tls config

* change port to ports + (wip) helm updates

* Squashed commit of the following:

commit 509f41c
Author: Jason Collins <[email protected]>
Date:   Tue Nov 21 08:23:27 2023 -0700

    APIGOV-26695 - update the handling and creation of api transactions for the kong agent (#40)

    * update deps

    * some refactoring for event handling

    * do not commit debug bin files

    * use server mux

    * refactor processing

    * reorganize traceability agent files

    * reorg code

    * update deps

    * Fix CRDs creation + fix credential de-provisioning + logging (#39)

    * APIGOV-26709 + APIGOV-26718

    * fix plugins test

    * more credential logging

    * change debug to info log

    * solve log issues

    * code review

    ---------

    Co-authored-by: Dragos Gabriel Ghinea <[email protected]>

commit a129ec2
Merge: 578441a 0291b2b
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 21 17:17:16 2023 +0200

    Merge pull request #36 from Axway/APIGOV-26621

    APIGOV-26621 - Get spec from dev portal

commit 0291b2b
Merge: d3e88f0 578441a
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 21 17:12:58 2023 +0200

    Merge branch 'main' into APIGOV-26621

commit d3e88f0
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 21 17:10:02 2023 +0200

    APIGOV-26621 fix PR comms

commit 15ffe60
Author: Alin Rosca <[email protected]>
Date:   Fri Nov 17 11:21:44 2023 +0200

    APIGOV-26621 get spec from dev portal

commit b7934b6
Merge: a181051 1187f08
Author: Alin Rosca <[email protected]>
Date:   Fri Nov 17 11:00:50 2023 +0200

    Merge branch 'main' into APIGOV-26621

commit a181051
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 14 16:43:52 2023 +0200

    APIGOV-26621 fix PR comms

commit 690002c
Merge: 6a447cb b4fe37b
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 14 15:20:06 2023 +0200

    Merge branch 'APIGOV-26463' into APIGOV-26621

commit 6a447cb
Author: Alin Rosca <[email protected]>
Date:   Tue Nov 14 15:19:17 2023 +0200

    APIGOV-26621 get spec from local

* add sample env vars + helm configs + MR issues

* modify sample env vars

* revert to original config format + helm fixes

* another one

* readme updates

* another one

* camelCase issue

* remove route path and assume full admin api url is
always provided

* remove routepath from readme

* remove unnecessary helm values

* change default proxy ports to 0

* merge issues

* case issue

---------

Co-authored-by: Jason Collins <[email protected]>
Co-authored-by: Jason Collins <[email protected]>
  • Loading branch information
3 people authored Nov 30, 2023
1 parent 1262c18 commit cf2ea55
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 62 deletions.
26 changes: 14 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Once the Kong admin API is secured a gateway service for it must be added to Kon

- Basic authentication
- API Key authentication
- OAuth2 authentication
- OAuth2 authentication (currently, Kong returns an Internal Server Error if securing the admin api with OAuth2. The plugin can be created in Kong, but further requests will not work when receiving the token. The Agent is also configured to (as of now) not work with OAuth2)

#### Specification discovery methods

Expand Down Expand Up @@ -180,7 +180,7 @@ The Kong agents are delivered as containers, kong_discovery_agent and kong_trace

Before beginning to deploy the agents following information will need to be gathered in addition to the details that were noted in setup.

- The full URL to connect to the Kong admin API, `KONG_ADMIN_URL`
- The full URL to connect to the Kong admin API, `KONG_ADMIN_URL`. Note that if secured by kong, the URL should look like: https://host:port/secured-route-from-kong
- The host the agent will use when setting the endpoint of a discovered API, (`KONG_PROXY_HOST`)
- The HTTP `KONG_PROXY_PORTS_HTTP` and HTTPs `KONG_PROXY_PORTS_HTTPS` ports the agent will use with the endpoint above
- The URL paths, hosted by the gateway service, to query for spec files, `KONG_SPEC_URLPATHS`
Expand Down Expand Up @@ -392,13 +392,15 @@ Finally, when a Marketplace user requests a credential, within the Kong environm

All Kong specific environment variables available are listed below

| Name | Description |
| --------------------------------- | ------------------------------------------------------------------------------------- |
| **KONG_ADMIN_URL** | The Kong admin API URL that the agent will query against |
| **KONG_ADMIN_AUTH_APIKEY_HEADER** | The API Key header name the agent will use when authenticating |
| **KONG_ADMIN_AUTH_APIKEY_VALUE** | The API Key value the agent will use when authenticating |
| **KONG_PROXY_HOST** | The proxy endpoint that the agent will use in API Services for discovered Kong routes |
| **KONG_PROXY_PORTS_HTTP** | The HTTP port number that the agent will set for discovered APIS |
| **KONG_PROXY_PORTS_HTTPS** | The HTTPs port number that the agent will set for discovered APIS |
| **KONG_SPEC_LOCALPATH** | The local path that the agent will look in for API definitions |
| **KONG_SPEC_URLPATHS** | The URL paths that the agent will query on the gateway service for API definitions |
| Name | Description |
| -------------------------------------- | ------------------------------------------------------------------------------------- |
| **KONG_ADMIN_URL** | The Kong admin API URL that the agent will query against |
| **KONG_ADMIN_AUTH_APIKEY_HEADER** | The API Key header name the agent will use when authenticating |
| **KONG_ADMIN_AUTH_APIKEY_VALUE** | The API Key value the agent will use when authenticating |
| **KONG_ADMIN_AUTH_BASICAUTH_USERNAME** | The HTTP Basic username that the agent will use when authenticating |
| **KONG_ADMIN_AUTH_BASICAUTH_PASSWORD** | The HTTP Basic password that the agent will use when authenticating |
| **KONG_PROXY_HOST** | The proxy endpoint that the agent will use in API Services for discovered Kong routes |
| **KONG_PROXY_PORTS_HTTP** | The HTTP port number that the agent will set for discovered APIS |
| **KONG_PROXY_PORTS_HTTPS** | The HTTPs port number that the agent will set for discovered APIS |
| **KONG_SPEC_LOCALPATH** | The local path that the agent will look in for API definitions |
| **KONG_SPEC_URLPATHS** | The URL paths that the agent will query on the gateway service for API definitions |
16 changes: 14 additions & 2 deletions helm/kong-agents/templates/discovery-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ spec:
{{- end }}
{{- end }}
env:
- name: KONG_ADMIN_URL
value: "{{ .Values.kong.admin.url }}"
- name: KONG_PROXY_HOST
value: "{{ .Values.kong.proxy.host }}"
- name: KONG_PROXY_PORTS_HTTP
value: "{{ .Values.kong.proxy.ports.http }}"
- name: KONG_PROXY_PORTS_HTTPS
value: "{{ .Values.kong.proxy.ports.https }}"
- name: KONG_ADMIN_URL
value: "{{ .Values.kong.admin.url }}"
{{- if (include "kong-agents.specDownloadPathsString" .) }}
- name: KONG_SPEC_URLPATHS
value: {{ include "kong-agents.specDownloadPathsString" . }}
Expand All @@ -98,6 +98,18 @@ spec:
name: kong-admin-auth-apikey
key: header
{{- end }}
{{- if .Values.kong.admin.auth.basicAuth.username }}
- name: KONG_ADMIN_AUTH_BASICAUTH_USERNAME
valueFrom:
secretKeyRef:
name: kong-admin-auth-basicauth
key: username
- name: KONG_ADMIN_AUTH_BASICAUTH_PASSWORD
valueFrom:
secretKeyRef:
name: kong-admin-auth-basicauth
key: password
{{- end }}
{{- with .Values.env }}
{{- range $key, $value := . }}
{{- if and (not (eq (toString $value) ""))
Expand Down
10 changes: 10 additions & 0 deletions helm/kong-agents/templates/kong-admin-auth-basicauth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{- if .Values.kong.admin.auth.basicAuth.username }}
apiVersion: v1
kind: Secret
metadata:
name: kong-admin-auth-basicauth
type: Opaque
stringData:
username: "{{ .Values.kong.admin.auth.basicAuth.username }}"
password: "{{ .Values.kong.admin.auth.basicAuth.password }}"
{{- end }}
5 changes: 4 additions & 1 deletion helm/kong-agents/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ kong:
apikey:
header:
value:
basicAuth:
username:
password:
proxy:
host:
host:
ports:
http: 8000
https: 8443
Expand Down
5 changes: 0 additions & 5 deletions pkg/cmd/discovery/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,9 @@ func run() error {
// and passed to the callback allowing the agent code to access the central config
func initConfig(centralConfig corecfg.CentralConfig) (interface{}, error) {
rootProps := DiscoveryCmd.GetProperties()

agentConfig = config.AgentConfig{
CentralCfg: centralConfig,
KongGatewayCfg: config.ParseProperties(rootProps),
}
return agentConfig, nil
}

func GetAgentConfig() config.AgentConfig {
return agentConfig
}
109 changes: 88 additions & 21 deletions pkg/config/discovery/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,37 @@ package config

import (
"fmt"
"net/url"
"strings"

"github.com/Axway/agent-sdk/pkg/cmd/properties"
corecfg "github.com/Axway/agent-sdk/pkg/config"
"github.com/Axway/agent-sdk/pkg/util/log"
)

const (
cfgKongAdminURL = "kong.admin.url"
cfgKongAdminAPIKey = "kong.admin.auth.apikey.value"
cfgKongAdminAPIKeyHeader = "kong.admin.auth.apikey.header"
cfgKongProxyHost = "kong.proxy.host"
cfgKongProxyPortHttp = "kong.proxy.port.http"
cfgKongProxyPortHttps = "kong.proxy.port.https"
cfgKongAdminUrl = "kong.admin.url"
cfgKongAdminAPIKey = "kong.admin.auth.apiKey.value"
cfgKongAdminAPIKeyHeader = "kong.admin.auth.apiKey.header"
cfgKongAdminUsername = "kong.admin.auth.basicauth.username"
cfgKongAdminPassword = "kong.admin.auth.basicauth.password"
cfgKongProxyPortHttp = "kong.proxy.ports.http"
cfgKongProxyPortHttps = "kong.proxy.ports.https"
cfgKongSpecURLPaths = "kong.spec.urlPaths"
cfgKongSpecLocalPath = "kong.spec.localPath"
cfgKongSpecFilter = "kong.spec.filter"
)

func AddKongProperties(rootProps properties.Properties) {
rootProps.AddStringProperty(cfgKongAdminURL, "", "The Kong admin endpoint")
rootProps.AddStringProperty(cfgKongAdminUrl, "", "The Admin API url")
rootProps.AddStringProperty(cfgKongAdminAPIKey, "", "API Key value to authenticate with Kong Gateway")
rootProps.AddStringProperty(cfgKongAdminAPIKeyHeader, "", "API Key header to authenticate with Kong Gateway")
rootProps.AddStringProperty(cfgKongAdminUsername, "", "Username for basic auth to authenticate with Kong Admin API")
rootProps.AddStringProperty(cfgKongAdminPassword, "", "Password for basic auth to authenticate with Kong Admin API")
rootProps.AddStringProperty(cfgKongProxyHost, "", "The Kong proxy endpoint")
rootProps.AddIntProperty(cfgKongProxyPortHttp, 0, "The Kong proxy http port")
rootProps.AddIntProperty(cfgKongProxyPortHttps, 0, "The Kong proxy https port")
rootProps.AddIntProperty(cfgKongProxyPortHttp, 80, "The Kong proxy http port")
rootProps.AddIntProperty(cfgKongProxyPortHttps, 443, "The Kong proxy https port")
rootProps.AddStringSliceProperty(cfgKongSpecURLPaths, []string{}, "URL paths that the agent will look in for spec files")
rootProps.AddStringProperty(cfgKongSpecLocalPath, "", "Local paths where the agent will look for spec files")
rootProps.AddStringProperty(cfgKongSpecFilter, "", "SDK Filter format. Empty means filters are ignored.")
Expand All @@ -38,12 +45,18 @@ type AgentConfig struct {
}

type KongAdminConfig struct {
URL string `config:"url"`
Url string `config:"url"`
Auth KongAdminAuthConfig `config:"auth"`
}

type KongAdminAuthConfig struct {
APIKey KongAdminAuthAPIKeyConfig `config:"apikey"`
APIKey KongAdminAuthAPIKeyConfig `config:"apiKey"`
BasicAuth KongAdminBasicAuthConfig `config:"basicAuth"`
}

type KongAdminBasicAuthConfig struct {
Username string `config:"username"`
Password string `config:"password"`
}

type KongAdminAuthAPIKeyConfig struct {
Expand All @@ -52,11 +65,11 @@ type KongAdminAuthAPIKeyConfig struct {
}

type KongProxyConfig struct {
Host string `config:"host"`
Port KongProxyPortConfig `config:"port"`
Host string `config:"host"`
Ports KongPortConfig `config:"ports"`
}

type KongProxyPortConfig struct {
type KongPortConfig struct {
HTTP int `config:"http"`
HTTPS int `config:"https"`
}
Expand All @@ -76,35 +89,89 @@ type KongGatewayConfig struct {
Spec KongSpecConfig `config:"spec"`
}

const (
hostErr = "kong host must be provided."
proxyPortErr = "both proxy port values of http https are required"
invalidUrlErr = "invalid Admin API url provided. Must contain protocol + hostname + port." +
"Examples: <http://kong.com:8001>, <https://kong.com:8444>"
credentialConfigErr = "invalid authorization configuration provided. " +
"If provided, (Username and Password) or (ClientID and ClientSecret) must be non-empty"
)

// ValidateCfg - Validates the gateway config
func (c *KongGatewayConfig) ValidateCfg() (err error) {
if c.Admin.URL == "" {
return fmt.Errorf("error: admin url is required")
}
logger := log.NewFieldLogger().WithPackage("config").WithComponent("ValidateConfig")
if c.Proxy.Host == "" {
return fmt.Errorf("error: proxy host is required")
return fmt.Errorf(hostErr)
}
if c.Proxy.Port.HTTP == 0 && c.Proxy.Port.HTTPS == 0 {
return fmt.Errorf("error: at least one proxy port value of either http or https is required")
if c.Proxy.Ports.HTTP == 0 || c.Proxy.Ports.HTTPS == 0 {
return fmt.Errorf(proxyPortErr)
}
if invalidAdminUrl(c.Admin.Url) {
return fmt.Errorf(invalidUrlErr)
}
if noCredentialsProvided(c) {
logger.Warn("No credentials provided. Assuming Kong Admin API requires no authorization.")
}
if invalidCredentialConfig(c) {
return fmt.Errorf(credentialConfigErr)
}
return
}

func noCredentialsProvided(c *KongGatewayConfig) bool {
apiKey := c.Admin.Auth.APIKey.Value
user := c.Admin.Auth.BasicAuth.Username
pass := c.Admin.Auth.BasicAuth.Password

if apiKey == "" && user == "" && pass == "" {
return true
}
return false
}

func invalidAdminUrl(u string) bool {
parsedUrl, err := url.Parse(u)
if err != nil {
return true
}
if parsedUrl.Port() == "" ||
strings.HasPrefix(parsedUrl.Host, "http://") || strings.HasPrefix(parsedUrl.Host, "https://") {
return true
}
return false
}

func invalidCredentialConfig(c *KongGatewayConfig) bool {
user := c.Admin.Auth.BasicAuth.Username
pass := c.Admin.Auth.BasicAuth.Password

if (user == "" && pass != "") ||
(user != "" && pass == "") {
return true
}
return false
}

func ParseProperties(rootProps properties.Properties) *KongGatewayConfig {
// Parse the config from bound properties and setup gateway config
return &KongGatewayConfig{
Admin: KongAdminConfig{
URL: rootProps.StringPropertyValue(cfgKongAdminURL),
Url: rootProps.StringPropertyValue(cfgKongAdminUrl),
Auth: KongAdminAuthConfig{
APIKey: KongAdminAuthAPIKeyConfig{
Value: rootProps.StringPropertyValue(cfgKongAdminAPIKey),
Header: rootProps.StringPropertyValue(cfgKongAdminAPIKeyHeader),
},
BasicAuth: KongAdminBasicAuthConfig{
Username: rootProps.StringPropertyValue(cfgKongAdminUsername),
Password: rootProps.StringPropertyValue(cfgKongAdminPassword),
},
},
},
Proxy: KongProxyConfig{
Host: rootProps.StringPropertyValue(cfgKongProxyHost),
Port: KongProxyPortConfig{
Ports: KongPortConfig{
HTTP: rootProps.IntPropertyValue(cfgKongProxyPortHttp),
HTTPS: rootProps.IntPropertyValue(cfgKongProxyPortHttps),
},
Expand Down
47 changes: 47 additions & 0 deletions pkg/config/discovery/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package config

import (
"testing"

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

func TestKongGateCfg(t *testing.T) {
cfg := &KongGatewayConfig{}

err := cfg.ValidateCfg()
assert.Equal(t, hostErr, err.Error())

cfg.Proxy.Host = "localhost"
err = cfg.ValidateCfg()
assert.Equal(t, proxyPortErr, err.Error())

cfg.Proxy.Ports.HTTP = 8000
cfg.Proxy.Ports.HTTPS = 8443
err = cfg.ValidateCfg()
assert.Equal(t, invalidUrlErr, err.Error())

cfg.Admin.Url = "sdl.com:8000"
err = cfg.ValidateCfg()
assert.Equal(t, invalidUrlErr, err.Error())

cfg.Admin.Url = "http://sdl.com"
err = cfg.ValidateCfg()
assert.Equal(t, invalidUrlErr, err.Error())

cfg.Admin.Url = "https://sds.com:8000"
cfg.Admin.Auth.BasicAuth.Username = "test"
err = cfg.ValidateCfg()
assert.Equal(t, credentialConfigErr, err.Error())

cfg.Admin.Auth.BasicAuth.Username = ""
cfg.Admin.Auth.BasicAuth.Password = "sas"
err = cfg.ValidateCfg()
assert.Equal(t, credentialConfigErr, err.Error())

cfg.Admin.Auth.BasicAuth.Password = ""

err = cfg.ValidateCfg()
assert.Equal(t, nil, err)

}
5 changes: 3 additions & 2 deletions pkg/gateway/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,10 @@ func (gc *Client) processSingleKongService(ctx context.Context, service *klib.Se
func (gc *Client) specPreparation(ctx context.Context, route *klib.Route, service *klib.Service, spec *Openapi) {
log := gc.logger.WithField(common.AttrRouteID, *route.ID).
WithField(common.AttrServiceID, *service.ID)

proxyHost := gc.kongGatewayCfg.Proxy.Host
httpPort := gc.kongGatewayCfg.Proxy.Port.HTTP
httpsPort := gc.kongGatewayCfg.Proxy.Port.HTTPS
httpPort := gc.kongGatewayCfg.Proxy.Ports.HTTP
httpsPort := gc.kongGatewayCfg.Proxy.Ports.HTTPS

apiPlugins, err := gc.plugins.GetEffectivePlugins(*route.ID, *service.ID)
if err != nil {
Expand Down
9 changes: 1 addition & 8 deletions pkg/gateway/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ import (
)

func TestKongClient(t *testing.T) {
gatewayConfig := &config.KongGatewayConfig{
Admin: config.KongAdminConfig{
URL: "http://localhost",
},
Proxy: config.KongProxyConfig{
Host: "localhost",
},
}
gatewayConfig := &config.KongGatewayConfig{}
_ = config.AgentConfig{
CentralCfg: corecfg.NewCentralConfig(corecfg.DiscoveryAgent),
KongGatewayCfg: gatewayConfig,
Expand Down
Loading

0 comments on commit cf2ea55

Please sign in to comment.