Skip to content

Commit

Permalink
Load plugin configuration field value from Kubernetes Secret
Browse files Browse the repository at this point in the history
Co-authored-by: nnlquan <[email protected]>
  • Loading branch information
rtribotte and longquan0104 committed Jun 20, 2022
1 parent 9ccc8cf commit f8f6851
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
apiVersion: v1
kind: Secret
metadata:
name: name
namespace: default

data:
key: dGhpc19pc190aGVfdmVyeV9kZWVwX3NlY3JldA==

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-secret
namespace: default

spec:
plugin:
test-secret:
secret_0:
secret_1:
secret_2:
user: admin
secret: urn:k8s:secret:name:key
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Secret
metadata:
name: name
namespace: default

data:
key1: YWRtaW5fcGFzc3dvcmQ=
key2: dXNlcl9wYXNzd29yZA==

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-secret
namespace: default

spec:
plugin:
test-secret:
users:
- name: admin
secret: urn:k8s:secret:name:key1
- name: user
secret: urn:k8s:secret:name:key2
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: v1
kind: Secret
metadata:
name: name
namespace: default

data:
key1: c2VjcmV0X2RhdGEx
key2: c2VjcmV0X2RhdGEy

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-secret
namespace: default

spec:
plugin:
test-secret:
secret:
- urn:k8s:secret:name:key1
- urn:k8s:secret:name:key2
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-secret
namespace: default

spec:
plugin:
test-secret:
user: admin
secret: urn:k8s:secret:notfound:key
21 changes: 21 additions & 0 deletions pkg/provider/kubernetes/crd/fixtures/with_plugin_read_secret.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: v1
kind: Secret
metadata:
name: name
namespace: default

data:
key: dGhpc19pc190aGVfc2VjcmV0

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-secret
namespace: default

spec:
plugin:
test-secret:
user: admin
secret: urn:k8s:secret:name:key
72 changes: 66 additions & 6 deletions pkg/provider/kubernetes/crd/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
conf.HTTP.Services[serviceName] = errorPageService
}

plugin, err := createPluginMiddleware(middleware.Spec.Plugin)
plugin, err := createPluginMiddleware(client, middleware.Namespace, middleware.Spec.Plugin)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading plugins middleware: %v", err)
continue
Expand Down Expand Up @@ -419,7 +419,7 @@ func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.Servi
return &corev1.ServicePort{Port: port.IntVal}, nil
}

func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]dynamic.PluginConf, error) {
func createPluginMiddleware(k8sClient Client, ns string, plugins map[string]apiextensionv1.JSON) (map[string]dynamic.PluginConf, error) {
if plugins == nil {
return nil, nil
}
Expand All @@ -429,13 +429,73 @@ func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]
return nil, err
}

pc := map[string]dynamic.PluginConf{}
err = json.Unmarshal(data, &pc)
if err != nil {
pcMap := map[string]dynamic.PluginConf{}
if err = json.Unmarshal(data, &pcMap); err != nil {
return nil, err
}

return pc, nil
for _, pc := range pcMap {
for key := range pc {
if pc[key], err = loadSecretKeys(k8sClient, ns, pc[key]); err != nil {
return nil, err
}
}
}

return pcMap, nil
}

func loadSecretKeys(k8sClient Client, ns string, i interface{}) (interface{}, error) {
var err error
switch iv := i.(type) {
case string:
if !strings.HasPrefix(iv, "urn:k8s:secret:") {
return iv, nil
}

return getSecretValue(k8sClient, ns, iv)

case []interface{}:
for i := range iv {
if iv[i], err = loadSecretKeys(k8sClient, ns, iv[i]); err != nil {
return nil, err
}
}

case map[string]interface{}:
for k := range iv {
if iv[k], err = loadSecretKeys(k8sClient, ns, iv[k]); err != nil {
return nil, err
}
}
}

return i, nil
}

func getSecretValue(c Client, ns, urn string) (string, error) {
parts := strings.Split(urn, ":")
if len(parts) != 5 {
return "", fmt.Errorf("malformed secret URN %q", urn)
}

secretName := parts[3]
secret, ok, err := c.GetSecret(ns, secretName)
if err != nil {
return "", err
}

if !ok {
return "", fmt.Errorf("secret %s/%s is not found", ns, secretName)
}

secretKey := parts[4]
secretValue, ok := secret.Data[secretKey]
if !ok {
return "", fmt.Errorf("key %q not found in secret %s/%s", secretKey, ns, secretName)
}

return string(secretValue), nil
}

func createCircuitBreakerMiddleware(circuitBreaker *v1alpha1.CircuitBreaker) (*dynamic.CircuitBreaker, error) {
Expand Down
160 changes: 160 additions & 0 deletions pkg/provider/kubernetes/crd/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3339,6 +3339,166 @@ func TestLoadIngressRoutes(t *testing.T) {
},
},
},
{
desc: "Simple Ingress Route, with test middleware read config from secret",
paths: []string{"services.yml", "with_plugin_read_secret.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{
"default-test-secret": {
Plugin: map[string]dynamic.PluginConf{
"test-secret": map[string]interface{}{
"user": "admin",
"secret": "this_is_the_secret",
},
},
},
},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "Simple Ingress Route, with test middleware read config from deep secret",
paths: []string{"services.yml", "with_plugin_deep_read_secret.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{
"default-test-secret": {
Plugin: map[string]dynamic.PluginConf{
"test-secret": map[string]interface{}{
"secret_0": map[string]interface{}{
"secret_1": map[string]interface{}{
"secret_2": map[string]interface{}{
"user": "admin",
"secret": "this_is_the_very_deep_secret",
},
},
},
},
},
},
},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "Simple Ingress Route, with test middleware read config from an array of secret",
paths: []string{"services.yml", "with_plugin_read_array_of_secret.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{
"default-test-secret": {
Plugin: map[string]dynamic.PluginConf{
"test-secret": map[string]interface{}{
"secret": []interface{}{"secret_data1", "secret_data2"},
},
},
},
},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "Simple Ingress Route, with test middleware read config from an array of secret",
paths: []string{"services.yml", "with_plugin_read_array_of_map_contain_secret.yml"},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{
"default-test-secret": {
Plugin: map[string]dynamic.PluginConf{
"test-secret": map[string]interface{}{
"users": []interface{}{
map[string]interface{}{
"name": "admin",
"secret": "admin_password",
},
map[string]interface{}{
"name": "user",
"secret": "user_password",
},
},
},
},
},
},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "Simple Ingress Route, with test middleware read config from secret that not found",
paths: []string{"services.yml", "with_plugin_read_not_exist_secret.yml"},
allowCrossNamespace: true,
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TLS: &dynamic.TLSConfiguration{},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{
desc: "Simple Ingress Route, with error page middleware",
paths: []string{"services.yml", "with_error_page.yml"},
Expand Down

0 comments on commit f8f6851

Please sign in to comment.