Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion api/v2/apisixconsumer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ type ApisixConsumerSpec struct {
IngressClassName string `json:"ingressClassName,omitempty" yaml:"ingressClassName,omitempty"`

// AuthParameter defines the authentication credentials and configuration for this consumer.
AuthParameter ApisixConsumerAuthParameter `json:"authParameter" yaml:"authParameter"`
// +kubebuilder:validation:Optional
AuthParameter *ApisixConsumerAuthParameter `json:"authParameter,omitempty" yaml:"authParameter,omitempty"`

Comment thread
AlinsRan marked this conversation as resolved.
// Plugins lists additional consumer-scoped plugins to attach to this consumer.
// These plugins are applied alongside any authentication plugin derived from AuthParameter.
// An enabled plugin with the same name as the auth plugin derived from AuthParameter takes precedence.
Plugins []ApisixRoutePlugin `json:"plugins,omitempty" yaml:"plugins,omitempty"`
}

// ApisixConsumerStatus defines the observed state of ApisixConsumer.
Expand Down Expand Up @@ -130,6 +136,7 @@ type ApisixConsumerJwtAuth struct {
}

// ApisixConsumerJwtAuthValue defines configuration for JWT authentication.
//
// For asymmetric algorithms (RS*, ES*, PS*, EdDSA), at least one of public_key
// or private_key must be provided. Symmetric algorithms (HS256, HS384, HS512)
// and unset algorithm do not require any key field.
Expand Down
24 changes: 12 additions & 12 deletions api/v2/apisixconsumer_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestApisixConsumer_JwtAuth_SymmetricHS256(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -130,7 +130,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricWithWhitespaceOnlyPublicKey(t *testing
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -150,7 +150,7 @@ func TestApisixConsumer_JwtAuth_SymmetricHS512(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -168,7 +168,7 @@ func TestApisixConsumer_JwtAuth_NoAlgorithmDefaultsToSymmetric(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -185,7 +185,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricRS256WithPublicKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -203,7 +203,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricRS256WithPrivateKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -221,7 +221,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricRS256WithBothKeys(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -240,7 +240,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricRS256WithoutAnyKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -259,7 +259,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricES256WithoutAnyKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -278,7 +278,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricEdDSAWithoutAnyKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -297,7 +297,7 @@ func TestApisixConsumer_JwtAuth_AsymmetricWithEmptyPublicKey(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand All @@ -321,7 +321,7 @@ func TestApisixConsumer_JwtAuth_EmptyAlgorithmTreatedAsSymmetric(t *testing.T) {
v := loadApisixConsumerSchema(t)
ac := &apisixv2.ApisixConsumer{
Spec: apisixv2.ApisixConsumerSpec{
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
AuthParameter: &apisixv2.ApisixConsumerAuthParameter{
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
Value: &apisixv2.ApisixConsumerJwtAuthValue{
Key: "my-key",
Expand Down
42 changes: 26 additions & 16 deletions api/v2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 26 additions & 2 deletions config/crd/bases/apisix.apache.org_apisixconsumers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,32 @@ spec:
IngressClassName is the name of an IngressClass cluster resource.
The controller uses this field to decide whether the resource should be managed.
type: string
required:
- authParameter
plugins:
description: |-
Plugins lists additional consumer-scoped plugins to attach to this consumer.
These plugins are applied alongside any authentication plugin derived from AuthParameter.
An enabled plugin with the same name as the auth plugin derived from AuthParameter takes precedence.
items:
description: ApisixRoutePlugin represents an APISIX plugin.
properties:
config:
description: Plugin configuration.
x-kubernetes-preserve-unknown-fields: true
enable:
default: true
description: Whether this plugin is in use, default is true.
type: boolean
name:
description: The plugin name.
type: string
secretRef:
description: Plugin configuration secretRef.
type: string
required:
- enable
- name
type: object
type: array
type: object
status:
description: ApisixStatus is the status report for Apisix ingress Resources
Expand Down
4 changes: 3 additions & 1 deletion docs/en/latest/reference/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ _Appears in:_
#### ApisixConsumerJwtAuthValue


ApisixConsumerJwtAuthValue defines configuration for JWT authentication.
ApisixConsumerJwtAuthValue defines configuration for JWT authentication.<br /><br />
For asymmetric algorithms (RS*, ES*, PS*, EdDSA), at least one of public_key
or private_key must be provided. Symmetric algorithms (HS256, HS384, HS512)
and unset algorithm do not require any key field.
Expand Down Expand Up @@ -875,6 +875,7 @@ ApisixConsumerSpec defines the desired state of ApisixConsumer.
| --- | --- |
| `ingressClassName` _string_ | IngressClassName is the name of an IngressClass cluster resource. The controller uses this field to decide whether the resource should be managed. |
| `authParameter` _[ApisixConsumerAuthParameter](#apisixconsumerauthparameter)_ | AuthParameter defines the authentication credentials and configuration for this consumer. |
| `plugins` _[ApisixRoutePlugin](#apisixrouteplugin) array_ | Plugins lists additional consumer-scoped plugins to attach to this consumer. These plugins are applied alongside any authentication plugin derived from AuthParameter. An enabled plugin with the same name as the auth plugin derived from AuthParameter takes precedence. |


_Appears in:_
Expand Down Expand Up @@ -1163,6 +1164,7 @@ ApisixRoutePlugin represents an APISIX plugin.


_Appears in:_
- [ApisixConsumerSpec](#apisixconsumerspec)
- [ApisixGlobalRuleSpec](#apisixglobalrulespec)
- [ApisixPluginConfigSpec](#apisixpluginconfigspec)
- [ApisixRouteHTTP](#apisixroutehttp)
Expand Down
100 changes: 64 additions & 36 deletions internal/adc/translator/apisixconsumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,42 +55,54 @@ const (
func (t *Translator) TranslateApisixConsumer(tctx *provider.TranslateContext, ac *v2.ApisixConsumer) (*TranslateResult, error) {
result := &TranslateResult{}
plugins := make(adctypes.Plugins)
if ac.Spec.AuthParameter.KeyAuth != nil {
cfg, err := t.translateConsumerKeyAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.KeyAuth)
if err != nil {
return nil, fmt.Errorf("invalid key auth config: %s", err)
if ap := ac.Spec.AuthParameter; ap != nil {
if ap.KeyAuth != nil {
cfg, err := t.translateConsumerKeyAuthPlugin(tctx, ac.Namespace, ap.KeyAuth)
if err != nil {
return nil, fmt.Errorf("invalid key auth config: %s", err)
}
plugins["key-auth"] = cfg
} else if ap.BasicAuth != nil {
cfg, err := t.translateConsumerBasicAuthPlugin(tctx, ac.Namespace, ap.BasicAuth)
Comment thread
AlinsRan marked this conversation as resolved.
if err != nil {
return nil, fmt.Errorf("invalid basic auth config: %s", err)
}
plugins["basic-auth"] = cfg
} else if ap.JwtAuth != nil {
cfg, err := t.translateConsumerJwtAuthPlugin(tctx, ac.Namespace, ap.JwtAuth)
if err != nil {
return nil, fmt.Errorf("invalid jwt auth config: %s", err)
}
plugins["jwt-auth"] = cfg
} else if ap.WolfRBAC != nil {
cfg, err := t.translateConsumerWolfRBACPlugin(tctx, ac.Namespace, ap.WolfRBAC)
if err != nil {
return nil, fmt.Errorf("invalid wolf rbac config: %s", err)
}
plugins["wolf-rbac"] = cfg
} else if ap.HMACAuth != nil {
cfg, err := t.translateConsumerHMACAuthPlugin(tctx, ac.Namespace, ap.HMACAuth)
if err != nil {
return nil, fmt.Errorf("invalid hmac auth config: %s", err)
}
plugins["hmac-auth"] = cfg
} else if ap.LDAPAuth != nil {
cfg, err := t.translateConsumerLDAPAuthPlugin(tctx, ac.Namespace, ap.LDAPAuth)
if err != nil {
return nil, fmt.Errorf("invalid ldap auth config: %s", err)
}
plugins["ldap-auth"] = cfg
}
plugins["key-auth"] = cfg
} else if ac.Spec.AuthParameter.BasicAuth != nil {
cfg, err := t.translateConsumerBasicAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.BasicAuth)
if err != nil {
return nil, fmt.Errorf("invalid basic auth config: %s", err)
}
plugins["basic-auth"] = cfg
} else if ac.Spec.AuthParameter.JwtAuth != nil {
cfg, err := t.translateConsumerJwtAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.JwtAuth)
if err != nil {
return nil, fmt.Errorf("invalid jwt auth config: %s", err)
}
plugins["jwt-auth"] = cfg
} else if ac.Spec.AuthParameter.WolfRBAC != nil {
cfg, err := t.translateConsumerWolfRBACPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.WolfRBAC)
if err != nil {
return nil, fmt.Errorf("invalid wolf rbac config: %s", err)
}
plugins["wolf-rbac"] = cfg
} else if ac.Spec.AuthParameter.HMACAuth != nil {
cfg, err := t.translateConsumerHMACAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.HMACAuth)
if err != nil {
return nil, fmt.Errorf("invalid hmac auth config: %s", err)
}
plugins["hmac-auth"] = cfg
} else if ac.Spec.AuthParameter.LDAPAuth != nil {
cfg, err := t.translateConsumerLDAPAuthPlugin(tctx, ac.Namespace, ac.Spec.AuthParameter.LDAPAuth)
if err != nil {
return nil, fmt.Errorf("invalid ldap auth config: %s", err)
}

// Merge generic consumer-scoped plugins. Only enabled entries are merged;
// an enabled plugin with the same name as an auth plugin derived from authParameter takes precedence.
for _, plugin := range ac.Spec.Plugins {
if !plugin.Enable {
continue
}
Comment thread
AlinsRan marked this conversation as resolved.
plugins["ldap-auth"] = cfg
config := t.buildPluginConfig(plugin, ac.Namespace, tctx.Secrets)
plugins[plugin.Name] = config
}

username := adctypes.ComposeConsumerName(ac.Namespace, ac.Name)
Expand All @@ -107,7 +119,9 @@ func (t *Translator) translateConsumerKeyAuthPlugin(tctx *provider.TranslateCont
if cfg.Value != nil {
return &adctypes.KeyAuthConsumerConfig{Key: cfg.Value.Key}, nil
}

if cfg.SecretRef == nil {
return nil, fmt.Errorf("key-auth: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand All @@ -129,7 +143,9 @@ func (t *Translator) translateConsumerBasicAuthPlugin(tctx *provider.TranslateCo
Password: cfg.Value.Password,
}, nil
}

if cfg.SecretRef == nil {
return nil, fmt.Errorf("basic-auth: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand Down Expand Up @@ -159,6 +175,9 @@ func (t *Translator) translateConsumerWolfRBACPlugin(tctx *provider.TranslateCon
HeaderPrefix: cfg.Value.HeaderPrefix,
}, nil
}
if cfg.SecretRef == nil {
return nil, fmt.Errorf("wolf-rbac: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand Down Expand Up @@ -194,6 +213,9 @@ func (t *Translator) translateConsumerJwtAuthPlugin(tctx *provider.TranslateCont
}, nil
}

if cfg.SecretRef == nil {
return nil, fmt.Errorf("jwt-auth: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand Down Expand Up @@ -251,6 +273,9 @@ func (t *Translator) translateConsumerHMACAuthPlugin(tctx *provider.TranslateCon
}, nil
}

if cfg.SecretRef == nil {
return nil, fmt.Errorf("hmac-auth: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand Down Expand Up @@ -357,6 +382,9 @@ func (t *Translator) translateConsumerLDAPAuthPlugin(tctx *provider.TranslateCon
}, nil
}

if cfg.SecretRef == nil {
return nil, fmt.Errorf("ldap-auth: either value or secretRef must be specified")
}
sec := tctx.Secrets[k8stypes.NamespacedName{
Namespace: consumerNamespace,
Name: cfg.SecretRef.Name,
Expand Down
Loading
Loading