Skip to content

Commit

Permalink
feat: support secrets for plugin configuration
Browse files Browse the repository at this point in the history
Add a "configFrom" field to KongPlugin and KongClusterPlugin. This
contains a secretKeyRef (and namespace, for KongClusterPlugin) pointing
to a secret key containing a JSON or YAML plugin configuration.

Plugins that either reference an invalid secret/key or contain both
config and configFrom are rejected.

Add a "configFrom" field to KongPlugin. This can contain a secretKeyRef
to a secret containing the plugin config, in lieu of including it directly
with the existing "config" field.
  • Loading branch information
Travis Raines committed Apr 23, 2020
1 parent 0920e80 commit 5cde7f2
Show file tree
Hide file tree
Showing 5 changed files with 589 additions and 18 deletions.
12 changes: 12 additions & 0 deletions deploy/manifests/base/custom-types.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ spec:
type: boolean
config:
type: object
configFrom:
type: object
properties:
secretKeyRef:
type: object
run_on:
type: string
enum:
Expand Down Expand Up @@ -104,6 +109,13 @@ spec:
type: boolean
config:
type: object
configFrom:
type: object
properties:
secretKeyRef:
type: object
namespace:
type: string
run_on:
type: string
enum:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ require (
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/pool.v3 v3.1.1
gopkg.in/ini.v1 v1.55.0 // indirect
gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.17.4
k8s.io/apimachinery v0.17.4
k8s.io/client-go v0.17.4
Expand Down
22 changes: 22 additions & 0 deletions internal/apis/configuration/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/golang/glog"
"github.com/hbagdi/go-kong/kong"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

Expand Down Expand Up @@ -57,6 +58,9 @@ type KongClusterPlugin struct {
// Config contains the plugin configuration.
Config Configuration `json:"config,omitempty"`

// ConfigFrom references a secret containing the plugin configuration.
ConfigFrom NamespacedSecretValueFromSource `json:"configFrom,omitempty"`

// PluginName is the name of the plugin to which to apply the config
PluginName string `json:"plugin,omitempty"`

Expand Down Expand Up @@ -99,6 +103,9 @@ type KongPlugin struct {
// Config contains the plugin configuration.
Config Configuration `json:"config,omitempty"`

// ConfigFrom references a secret containing the plugin configuration.
ConfigFrom SecretValueFromSource `json:"configFrom,omitempty"`

// PluginName is the name of the plugin to which to apply the config
PluginName string `json:"plugin,omitempty"`

Expand All @@ -111,6 +118,21 @@ type KongPlugin struct {
Protocols []string `json:"protocols,omitempty"`
}

// SecretValueFromSource represents the source of a secret value
type SecretValueFromSource struct {
// The secret key to select from
SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"`
}

// NamespacedSecretValueFromSource represents the source of a secret value,
// specifying the secret namespace
type NamespacedSecretValueFromSource struct {
// The secret key to select from
SecretKeyRef *corev1.SecretKeySelector `json:"secretKeyRef,omitempty"`
// The namespace containing the secret
Namespace string `json:"namespace,omitempty"`
}

// KongPluginList is a top-level list type. The client methods for lists are automatically created.
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type KongPluginList struct {
Expand Down
89 changes: 71 additions & 18 deletions internal/ingress/controller/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package parser
import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"reflect"
"regexp"
Expand All @@ -20,6 +21,7 @@ import (
"github.com/kong/kubernetes-ingress-controller/internal/ingress/utils"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
corev1 "k8s.io/api/core/v1"
networking "k8s.io/api/networking/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -1483,8 +1485,12 @@ func (p *Parser) globalPlugins() ([]Plugin, error) {
duplicates = append(duplicates, pluginName)
continue
}
res[pluginName] = Plugin{
Plugin: kongPluginFromK8SPlugin(k8sPlugin),
if plugin, err := p.kongPluginFromK8SPlugin(k8sPlugin); err == nil {
res[pluginName] = Plugin{
Plugin: plugin,
}
} else {
glog.Errorf("Failed to generate configuration for plugin %v: %v", pluginName, err)
}
}

Expand All @@ -1508,8 +1514,12 @@ func (p *Parser) globalPlugins() ([]Plugin, error) {
duplicates = append(duplicates, pluginName)
continue
}
res[pluginName] = Plugin{
Plugin: kongPluginFromK8SClusterPlugin(k8sPlugin),
if plugin, err := p.kongPluginFromK8SClusterPlugin(k8sPlugin); err == nil {
res[pluginName] = Plugin{
Plugin: plugin,
}
} else {
glog.Errorf("Failed to generate configuration for plugin %v: %v", pluginName, err)
}
}
for _, plugin := range duplicates {
Expand Down Expand Up @@ -1639,20 +1649,43 @@ func (p *Parser) getPlugin(namespace, name string) (kong.Plugin, error) {
if clusterPlugin.PluginName == "" {
return plugin, errors.Errorf("invalid empty 'plugin' property")
}
plugin = kongPluginFromK8SClusterPlugin(*clusterPlugin)
return plugin, err
}
// handle other errors
if err != nil {
plugin, err = p.kongPluginFromK8SClusterPlugin(*clusterPlugin)
return plugin, err
}
}
// ignore plugins with no name
if k8sPlugin.PluginName == "" {
return plugin, errors.Errorf("invalid empty 'plugin' property")
}
plugin = kongPluginFromK8SPlugin(*k8sPlugin)
return plugin, nil

plugin, err = p.kongPluginFromK8SPlugin(*k8sPlugin)
return plugin, err
}

func (p *Parser) secretToConfiguration(reference configurationv1.SecretValueFromSource, namespace string) (configurationv1.Configuration, error) {
secret, err := p.store.GetSecret(namespace, reference.SecretKeyRef.Name)
if err != nil {
return configurationv1.Configuration{}, errors.Errorf("error fetching credential secret '%v/%v': %v",
namespace, reference.SecretKeyRef.Name, err)
}
secretVal, ok := secret.Data[reference.SecretKeyRef.Key]
if !ok {
return configurationv1.Configuration{}, errors.Errorf("no key '%v' in secret '%v/%v'",
reference.SecretKeyRef.Key, namespace, reference.SecretKeyRef.Name)
}
var config configurationv1.Configuration
if err := json.Unmarshal(secretVal, &config); err != nil {
if err := yaml.Unmarshal(secretVal, &config); err != nil {
return configurationv1.Configuration{}, errors.Errorf("key '%v' in secret '%v/%v' contains neither valid JSON nor valid YAML)",
reference.SecretKeyRef.Key, namespace, reference.SecretKeyRef.Name)
}
}
return config, nil
}

func (p *Parser) namespacedSecretToConfiguration(reference configurationv1.NamespacedSecretValueFromSource) (configurationv1.Configuration, error) {
bareReference := configurationv1.SecretValueFromSource{SecretKeyRef: reference.SecretKeyRef}
return p.secretToConfiguration(bareReference, reference.Namespace)
}

// plugin is a intermediate type to hold plugin related configuration
Expand Down Expand Up @@ -1682,26 +1715,46 @@ func toKongPlugin(plugin plugin) kong.Plugin {
return result
}

func kongPluginFromK8SClusterPlugin(k8sPlugin configurationv1.KongClusterPlugin) kong.Plugin {
func (p *Parser) kongPluginFromK8SClusterPlugin(k8sPlugin configurationv1.KongClusterPlugin) (kong.Plugin, error) {
var err error
if k8sPlugin.ConfigFrom != (configurationv1.NamespacedSecretValueFromSource{}) {
if len(k8sPlugin.Config) > 0 {
err = errors.Errorf("plugin '%v' has both Config and ConfigFrom set", k8sPlugin.Name)
} else {
config, configError := p.namespacedSecretToConfiguration(k8sPlugin.ConfigFrom)
err = configError
k8sPlugin.Config = config
}
}
return toKongPlugin(plugin{
Name: k8sPlugin.PluginName,
Config: k8sPlugin.Config,

RunOn: k8sPlugin.RunOn,
Disabled: k8sPlugin.Disabled,
Protocols: k8sPlugin.Protocols,
})
}), err
}

func kongPluginFromK8SPlugin(k8sPlugin configurationv1.KongPlugin) kong.Plugin {
func (p *Parser) kongPluginFromK8SPlugin(k8sPlugin configurationv1.KongPlugin) (kong.Plugin, error) {
var err error
if k8sPlugin.ConfigFrom != (configurationv1.SecretValueFromSource{}) {
if len(k8sPlugin.Config) > 0 {
err = errors.Errorf("plugin '%v/%v' has both Config and ConfigFrom set",
k8sPlugin.Namespace, k8sPlugin.Name)
} else {
config, configError := p.secretToConfiguration(k8sPlugin.ConfigFrom, k8sPlugin.Namespace)
err = configError
k8sPlugin.Config = config
}
}
return toKongPlugin(plugin{
Name: k8sPlugin.PluginName,
Config: k8sPlugin.Config,

Name: k8sPlugin.PluginName,
Config: k8sPlugin.Config,
RunOn: k8sPlugin.RunOn,
Disabled: k8sPlugin.Disabled,
Protocols: k8sPlugin.Protocols,
})
}), err
}

// getEndpoints returns a list of <endpoint ip>:<port> for a given service/target port combination.
Expand Down
Loading

0 comments on commit 5cde7f2

Please sign in to comment.