Skip to content

Commit

Permalink
feat: apisixconsumer translator (#474)
Browse files Browse the repository at this point in the history
  • Loading branch information
tokers committed May 28, 2021
1 parent fe2db92 commit 23e5ebd
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 18 deletions.
2 changes: 1 addition & 1 deletion pkg/ingress/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ func (c *Controller) namespaceWatching(key string) (ok bool) {
}
ns, _, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
// Ignore resource with invalid key.
// Ignore resource pkg/types/apisix/v1/plugin_types.gowith invalid key.
ok = false
log.Warnf("resource %s was ignored since: %s", key, err)
return
Expand Down
47 changes: 47 additions & 0 deletions pkg/kube/translation/apisix_consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package translation

import (
"fmt"

configv2alpha1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func (t *translator) TranslateApisixConsumer(ac *configv2alpha1.ApisixConsumer) (*apisixv1.Consumer, error) {
// As the CRD schema ensures that only one authN can be configured,
// so here the order is no matter.

plugins := make(apisixv1.Plugins)
if ac.Spec.AuthParameter.KeyAuth != nil {
cfg, err := t.translateConsumerKeyAuthPlugin(ac.Namespace, ac.Spec.AuthParameter.KeyAuth)
if err != nil {
return nil, fmt.Errorf("invalid key auth config: %s", err)
}
plugins["key-auth"] = cfg
} else if ac.Spec.AuthParameter.BasicAuth != nil {
cfg, err := t.translateConsumerBasicAuthPlugin(ac.Namespace, ac.Spec.AuthParameter.BasicAuth)
if err != nil {
return nil, fmt.Errorf("invalid basic auth config: %s", err)
}
plugins["basic-auth"] = cfg
}

consumer := apisixv1.NewDefaultConsumer()
consumer.Username = apisixv1.ComposeConsumerName(ac.Namespace, ac.Name)
consumer.Plugins = plugins
return consumer, nil
}
74 changes: 74 additions & 0 deletions pkg/kube/translation/apisix_consumer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package translation

import (
"testing"

"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

configv2alpha1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

func TestTranslateApisixConsumer(t *testing.T) {
ac := &configv2alpha1.ApisixConsumer{
ObjectMeta: metav1.ObjectMeta{
Name: "jack",
Namespace: "qa",
},
Spec: configv2alpha1.ApisixConsumerSpec{
AuthParameter: configv2alpha1.ApisixConsumerAuthParameter{
BasicAuth: &configv2alpha1.ApisixConsumerBasicAuth{
Value: &configv2alpha1.ApisixConsumerBasicAuthValue{
Username: "jack",
Password: "jacknice",
},
},
},
},
}
consumer, err := (&translator{}).TranslateApisixConsumer(ac)
assert.Nil(t, err)
assert.Len(t, consumer.Plugins, 1)
cfg := consumer.Plugins["basic-auth"].(*apisixv1.BasicAuthConsumerConfig)
assert.Equal(t, cfg.Username, "jack")
assert.Equal(t, cfg.Password, "jacknice")

ac = &configv2alpha1.ApisixConsumer{
ObjectMeta: metav1.ObjectMeta{
Name: "jack",
Namespace: "qa",
},
Spec: configv2alpha1.ApisixConsumerSpec{
AuthParameter: configv2alpha1.ApisixConsumerAuthParameter{
KeyAuth: &configv2alpha1.ApisixConsumerKeyAuth{
Value: &configv2alpha1.ApisixConsumerKeyAuthValue{
Key: "qwerty",
},
},
},
},
}
consumer, err = (&translator{}).TranslateApisixConsumer(ac)
assert.Nil(t, err)
assert.Len(t, consumer.Plugins, 1)
cfg2 := consumer.Plugins["key-auth"].(*apisixv1.KeyAuthConsumerConfig)
assert.Equal(t, cfg2.Key, "qwerty")

// No test test cases for secret references as we already test them
// in plugin_test.go.
}
50 changes: 50 additions & 0 deletions pkg/kube/translation/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@
package translation

import (
"errors"

configv2alpha1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

var (
_errKeyNotFoundOrInvalid = errors.New("key \"key\" not found or invalid in secret")
_errUsernameNotFoundOrInvalid = errors.New("key \"username\" not found or invalid in secret")
_errPasswordNotFoundOrInvalid = errors.New("key \"password\" not found or invalid in secret")
)

func (t *translator) translateTrafficSplitPlugin(ctx *TranslateContext, ar *configv2alpha1.ApisixRoute, defaultBackendWeight int,
backends []*configv2alpha1.ApisixRouteHTTPBackend) (*apisixv1.TrafficSplitConfig, error) {
var (
Expand Down Expand Up @@ -60,3 +68,45 @@ func (t *translator) translateTrafficSplitPlugin(ctx *TranslateContext, ar *conf
}
return tsCfg, nil
}

func (t *translator) translateConsumerKeyAuthPlugin(consumerNamespace string, cfg *configv2alpha1.ApisixConsumerKeyAuth) (*apisixv1.KeyAuthConsumerConfig, error) {
if cfg.Value != nil {
return &apisixv1.KeyAuthConsumerConfig{Key: cfg.Value.Key}, nil
}

sec, err := t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name)
if err != nil {
return nil, err
}
raw, ok := sec.Data["key"]
if !ok || len(raw) == 0 {
return nil, _errKeyNotFoundOrInvalid
}
return &apisixv1.KeyAuthConsumerConfig{Key: string(raw)}, nil
}

func (t *translator) translateConsumerBasicAuthPlugin(consumerNamespace string, cfg *configv2alpha1.ApisixConsumerBasicAuth) (*apisixv1.BasicAuthConsumerConfig, error) {
if cfg.Value != nil {
return &apisixv1.BasicAuthConsumerConfig{
Username: cfg.Value.Username,
Password: cfg.Value.Password,
}, nil
}

sec, err := t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name)
if err != nil {
return nil, err
}
raw1, ok := sec.Data["username"]
if !ok || len(raw1) == 0 {
return nil, _errUsernameNotFoundOrInvalid
}
raw2, ok := sec.Data["password"]
if !ok || len(raw2) == 0 {
return nil, _errPasswordNotFoundOrInvalid
}
return &apisixv1.BasicAuthConsumerConfig{
Username: string(raw1),
Password: string(raw2),
}, nil
}
154 changes: 152 additions & 2 deletions pkg/kube/translation/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ import (
"context"
"testing"

"github.com/apache/apisix-ingress-controller/pkg/id"

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -28,6 +26,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"

"github.com/apache/apisix-ingress-controller/pkg/id"
configv2alpha1 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2alpha1"
apisixfake "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/clientset/versioned/fake"
apisixinformers "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/client/informers/externalversions"
Expand Down Expand Up @@ -533,3 +532,154 @@ func TestTranslateTrafficSplitPluginBadCases(t *testing.T) {
assert.Nil(t, cfg)
assert.Equal(t, err.Error(), "conflict headless service and backend resolve granularity")
}

func TestTranslateConsumerKeyAuthPluginWithInPlaceValue(t *testing.T) {
keyAuth := &configv2alpha1.ApisixConsumerKeyAuth{
Value: &configv2alpha1.ApisixConsumerKeyAuthValue{Key: "abc"},
}
cfg, err := (&translator{}).translateConsumerKeyAuthPlugin("default", keyAuth)
assert.Nil(t, err)
assert.Equal(t, cfg.Key, "abc")
}

func TestTranslateConsumerKeyAuthWithSecretRef(t *testing.T) {
sec := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "abc-key-auth",
},
Data: map[string][]byte{
"key": []byte("abc"),
},
}
client := fake.NewSimpleClientset()
informersFactory := informers.NewSharedInformerFactory(client, 0)
secretInformer := informersFactory.Core().V1().Secrets().Informer()
secretLister := informersFactory.Core().V1().Secrets().Lister()
processCh := make(chan struct{})
stopCh := make(chan struct{})
secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(_ interface{}) {
processCh <- struct{}{}
},
UpdateFunc: func(_, _ interface{}) {
processCh <- struct{}{}
},
})
go secretInformer.Run(stopCh)

tr := &translator{
&TranslatorOptions{
SecretLister: secretLister,
},
}
_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
assert.Nil(t, err)

<-processCh

keyAuth := &configv2alpha1.ApisixConsumerKeyAuth{
SecretRef: &corev1.LocalObjectReference{Name: "abc-key-auth"},
}
cfg, err := tr.translateConsumerKeyAuthPlugin("default", keyAuth)
assert.Nil(t, err)
assert.Equal(t, cfg.Key, "abc")

cfg, err = tr.translateConsumerKeyAuthPlugin("default2", keyAuth)
assert.Nil(t, cfg)
assert.Contains(t, err.Error(), "not found")

delete(sec.Data, "key")
_, err = client.CoreV1().Secrets("default").Update(context.Background(), sec, metav1.UpdateOptions{})
assert.Nil(t, err)
<-processCh

cfg, err = tr.translateConsumerKeyAuthPlugin("default", keyAuth)
assert.Nil(t, cfg)
assert.Equal(t, err, _errKeyNotFoundOrInvalid)

close(processCh)
close(stopCh)
}

func TestTranslateConsumerBasicAuthPluginWithInPlaceValue(t *testing.T) {
basicAuth := &configv2alpha1.ApisixConsumerBasicAuth{
Value: &configv2alpha1.ApisixConsumerBasicAuthValue{
Username: "jack",
Password: "jacknice",
},
}
cfg, err := (&translator{}).translateConsumerBasicAuthPlugin("default", basicAuth)
assert.Nil(t, err)
assert.Equal(t, cfg.Username, "jack")
assert.Equal(t, cfg.Password, "jacknice")
}

func TestTranslateConsumerBasicAuthWithSecretRef(t *testing.T) {
sec := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "jack-basic-auth",
},
Data: map[string][]byte{
"username": []byte("jack"),
"password": []byte("jacknice"),
},
}
client := fake.NewSimpleClientset()
informersFactory := informers.NewSharedInformerFactory(client, 0)
secretInformer := informersFactory.Core().V1().Secrets().Informer()
secretLister := informersFactory.Core().V1().Secrets().Lister()
processCh := make(chan struct{})
stopCh := make(chan struct{})
secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(_ interface{}) {
processCh <- struct{}{}
},
UpdateFunc: func(_, _ interface{}) {
processCh <- struct{}{}
},
})
go secretInformer.Run(stopCh)

tr := &translator{
&TranslatorOptions{
SecretLister: secretLister,
},
}
_, err := client.CoreV1().Secrets("default").Create(context.Background(), sec, metav1.CreateOptions{})
assert.Nil(t, err)

<-processCh

basicAuth := &configv2alpha1.ApisixConsumerBasicAuth{
SecretRef: &corev1.LocalObjectReference{Name: "jack-basic-auth"},
}
cfg, err := tr.translateConsumerBasicAuthPlugin("default", basicAuth)
assert.Nil(t, err)
assert.Equal(t, cfg.Username, "jack")
assert.Equal(t, cfg.Password, "jacknice")

cfg, err = tr.translateConsumerBasicAuthPlugin("default2", basicAuth)
assert.Nil(t, cfg)
assert.Contains(t, err.Error(), "not found")

delete(sec.Data, "password")
_, err = client.CoreV1().Secrets("default").Update(context.Background(), sec, metav1.UpdateOptions{})
assert.Nil(t, err)
<-processCh

cfg, err = tr.translateConsumerBasicAuthPlugin("default", basicAuth)
assert.Nil(t, cfg)
assert.Equal(t, err, _errPasswordNotFoundOrInvalid)

delete(sec.Data, "username")
_, err = client.CoreV1().Secrets("default").Update(context.Background(), sec, metav1.UpdateOptions{})
assert.Nil(t, err)
<-processCh

cfg, err = tr.translateConsumerBasicAuthPlugin("default", basicAuth)
assert.Nil(t, cfg)
assert.Equal(t, err, _errUsernameNotFoundOrInvalid)

close(processCh)
close(stopCh)
}
5 changes: 4 additions & 1 deletion pkg/kube/translation/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ type Translator interface {
TranslateSSL(*configv1.ApisixTls) (*apisixv1.Ssl, error)
// TranslateClusterConfig translates the configv2alpha1.ApisixClusterConfig object into the APISIX
// Global Rule resource.
TranslateClusterConfig(config *configv2alpha1.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
TranslateClusterConfig(*configv2alpha1.ApisixClusterConfig) (*apisixv1.GlobalRule, error)
// TranslateApisixConsumer translates the configv2alpha1.APisixConsumer object into the APISIX Consumer
// resource.
TranslateApisixConsumer(*configv2alpha1.ApisixConsumer) (*apisixv1.Consumer, error)
}

// TranslatorOptions contains options to help Translator
Expand Down
Loading

0 comments on commit 23e5ebd

Please sign in to comment.