Load plugin configuration field value from Kubernetes Secret
Co-authored-by: nnlquan <longquan0104@gmail.com>
This commit is contained in:
parent
9ccc8cfb25
commit
f8f685193d
7 changed files with 330 additions and 6 deletions
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -232,7 +232,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||||
conf.HTTP.Services[serviceName] = errorPageService
|
conf.HTTP.Services[serviceName] = errorPageService
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin, err := createPluginMiddleware(middleware.Spec.Plugin)
|
plugin, err := createPluginMiddleware(client, middleware.Namespace, middleware.Spec.Plugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctxMid).Errorf("Error while reading plugins middleware: %v", err)
|
log.FromContext(ctxMid).Errorf("Error while reading plugins middleware: %v", err)
|
||||||
continue
|
continue
|
||||||
|
@ -419,7 +419,7 @@ func getServicePort(svc *corev1.Service, port intstr.IntOrString) (*corev1.Servi
|
||||||
return &corev1.ServicePort{Port: port.IntVal}, nil
|
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 {
|
if plugins == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -429,13 +429,73 @@ func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := map[string]dynamic.PluginConf{}
|
pcMap := map[string]dynamic.PluginConf{}
|
||||||
err = json.Unmarshal(data, &pc)
|
if err = json.Unmarshal(data, &pcMap); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
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) {
|
func createCircuitBreakerMiddleware(circuitBreaker *v1alpha1.CircuitBreaker) (*dynamic.CircuitBreaker, error) {
|
||||||
|
|
|
@ -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",
|
desc: "Simple Ingress Route, with error page middleware",
|
||||||
paths: []string{"services.yml", "with_error_page.yml"},
|
paths: []string{"services.yml", "with_error_page.yml"},
|
||||||
|
|
Loading…
Reference in a new issue