From 59ba41a8c5780b9273610e944aabda700078a3db Mon Sep 17 00:00:00 2001 From: Fatpa Date: Thu, 26 May 2022 22:23:50 +0800 Subject: [PATCH] feat: add hmac-auth authorization method (#1035) --- .gitignore | 1 + pkg/kube/apisix/apis/config/v2/types.go | 20 ++ .../apis/config/v2/zz_generated.deepcopy.go | 52 +++++ pkg/kube/apisix/apis/config/v2beta3/types.go | 20 ++ .../config/v2beta3/zz_generated.deepcopy.go | 52 +++++ pkg/kube/translation/apisix_consumer.go | 12 + pkg/kube/translation/apisix_consumer_test.go | 46 ++++ pkg/kube/translation/apisix_route.go | 8 + pkg/kube/translation/plugin.go | 217 ++++++++++++++++++ pkg/kube/translation/plugin_test.go | 104 ++++++++- pkg/types/apisix/v1/plugin_types.go | 15 ++ pkg/types/apisix/v1/zz_generated.deepcopy.go | 21 ++ samples/deploy/crd/v1/ApisixConsumer.yaml | 43 +++- samples/deploy/crd/v1/ApisixRoute.yaml | 118 +++++++--- test/e2e/suite-features/consumer.go | 202 ++++++++++++++++ tools.go | 1 + 16 files changed, 887 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 5fbca57d4b..aa6584f9bd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ *.out release +.vscode .idea .DS_Store coverage.txt diff --git a/pkg/kube/apisix/apis/config/v2/types.go b/pkg/kube/apisix/apis/config/v2/types.go index aeace4d61d..bd8412981b 100644 --- a/pkg/kube/apisix/apis/config/v2/types.go +++ b/pkg/kube/apisix/apis/config/v2/types.go @@ -341,6 +341,7 @@ type ApisixConsumerAuthParameter struct { KeyAuth *ApisixConsumerKeyAuth `json:"keyAuth,omitempty" yaml:"keyAuth"` WolfRBAC *ApisixConsumerWolfRBAC `json:"wolfRBAC,omitempty" yaml:"wolfRBAC"` JwtAuth *ApisixConsumerJwtAuth `json:"jwtAuth,omitempty" yaml:"jwtAuth"` + HMACAuth *ApisixConsumerHMACAuth `json:"hmacAuth,omitempty" yaml:"hmacAuth"` } // ApisixConsumerBasicAuth defines the configuration for basic auth. @@ -396,6 +397,25 @@ type ApisixConsumerJwtAuthValue struct { Base64Secret bool `json:"base64_secret,omitempty" yaml:"base64_secret,omitempty"` } +// ApisixConsumerHMACAuth defines the configuration for the hmac auth. +type ApisixConsumerHMACAuth struct { + SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty" yaml:"secretRef,omitempty"` + Value *ApisixConsumerHMACAuthValue `json:"value,omitempty" yaml:"value,omitempty"` +} + +// ApisixConsumerHMACAuthValue defines the in-place configuration for hmac auth. +type ApisixConsumerHMACAuthValue struct { + AccessKey string `json:"access_key" yaml:"access_key"` + SecretKey string `json:"secret_key" yaml:"secret_key"` + Algorithm string `json:"algorithm,omitempty" yaml:"algorithm,omitempty"` + ClockSkew int64 `json:"clock_skew,omitempty" yaml:"clock_skew,omitempty"` + SignedHeaders []string `json:"signed_headers,omitempty" yaml:"signed_headers,omitempty"` + KeepHeaders bool `json:"keep_headers,omitempty" yaml:"keep_headers,omitempty"` + EncodeURIParams bool `json:"encode_uri_params,omitempty" yaml:"encode_uri_params,omitempty"` + ValidateRequestBody bool `json:"validate_request_body,omitempty" yaml:"validate_request_body,omitempty"` + MaxReqBody int64 `json:"max_req_body,omitempty" yaml:"max_req_body,omitempty"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ApisixConsumerList contains a list of ApisixConsumer. diff --git a/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go index 4815aaa549..f60dffd63d 100644 --- a/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go +++ b/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go @@ -302,6 +302,11 @@ func (in *ApisixConsumerAuthParameter) DeepCopyInto(out *ApisixConsumerAuthParam *out = new(ApisixConsumerJwtAuth) (*in).DeepCopyInto(*out) } + if in.HMACAuth != nil { + in, out := &in.HMACAuth, &out.HMACAuth + *out = new(ApisixConsumerHMACAuth) + (*in).DeepCopyInto(*out) + } return } @@ -357,6 +362,53 @@ func (in *ApisixConsumerBasicAuthValue) DeepCopy() *ApisixConsumerBasicAuthValue return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApisixConsumerHMACAuth) DeepCopyInto(out *ApisixConsumerHMACAuth) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(ApisixConsumerHMACAuthValue) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApisixConsumerHMACAuth. +func (in *ApisixConsumerHMACAuth) DeepCopy() *ApisixConsumerHMACAuth { + if in == nil { + return nil + } + out := new(ApisixConsumerHMACAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApisixConsumerHMACAuthValue) DeepCopyInto(out *ApisixConsumerHMACAuthValue) { + *out = *in + if in.SignedHeaders != nil { + in, out := &in.SignedHeaders, &out.SignedHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApisixConsumerHMACAuthValue. +func (in *ApisixConsumerHMACAuthValue) DeepCopy() *ApisixConsumerHMACAuthValue { + if in == nil { + return nil + } + out := new(ApisixConsumerHMACAuthValue) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApisixConsumerJwtAuth) DeepCopyInto(out *ApisixConsumerJwtAuth) { *out = *in diff --git a/pkg/kube/apisix/apis/config/v2beta3/types.go b/pkg/kube/apisix/apis/config/v2beta3/types.go index 07a3a92727..36b8dbb714 100644 --- a/pkg/kube/apisix/apis/config/v2beta3/types.go +++ b/pkg/kube/apisix/apis/config/v2beta3/types.go @@ -342,6 +342,7 @@ type ApisixConsumerAuthParameter struct { KeyAuth *ApisixConsumerKeyAuth `json:"keyAuth,omitempty" yaml:"keyAuth"` WolfRBAC *ApisixConsumerWolfRBAC `json:"wolfRBAC,omitempty" yaml:"wolfRBAC"` JwtAuth *ApisixConsumerJwtAuth `json:"jwtAuth,omitempty" yaml:"jwtAuth"` + HMACAuth *ApisixConsumerHMACAuth `json:"hmacAuth,omitempty" yaml:"hmacAuth"` } // ApisixConsumerBasicAuth defines the configuration for basic auth. @@ -397,6 +398,25 @@ type ApisixConsumerJwtAuthValue struct { Base64Secret bool `json:"base64_secret,omitempty" yaml:"base64_secret,omitempty"` } +// ApisixConsumerHMACAuth defines the configuration for the hmac auth. +type ApisixConsumerHMACAuth struct { + SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty" yaml:"secretRef,omitempty"` + Value *ApisixConsumerHMACAuthValue `json:"value,omitempty" yaml:"value,omitempty"` +} + +// ApisixConsumerHMACAuthValue defines the in-place configuration for hmac auth. +type ApisixConsumerHMACAuthValue struct { + AccessKey string `json:"access_key" yaml:"access_key"` + SecretKey string `json:"secret_key" yaml:"secret_key"` + Algorithm string `json:"algorithm,omitempty" yaml:"algorithm,omitempty"` + ClockSkew int64 `json:"clock_skew,omitempty" yaml:"clock_skew,omitempty"` + SignedHeaders []string `json:"signed_headers,omitempty" yaml:"signed_headers,omitempty"` + KeepHeaders bool `json:"keep_headers,omitempty" yaml:"keep_headers,omitempty"` + EncodeURIParams bool `json:"encode_uri_params,omitempty" yaml:"encode_uri_params,omitempty"` + ValidateRequestBody bool `json:"validate_request_body,omitempty" yaml:"validate_request_body,omitempty"` + MaxReqBody int64 `json:"max_req_body,omitempty" yaml:"max_req_body,omitempty"` +} + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // ApisixConsumerList contains a list of ApisixConsumer. diff --git a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go index 9682c93694..8039180449 100644 --- a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go +++ b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go @@ -303,6 +303,11 @@ func (in *ApisixConsumerAuthParameter) DeepCopyInto(out *ApisixConsumerAuthParam *out = new(ApisixConsumerJwtAuth) (*in).DeepCopyInto(*out) } + if in.HMACAuth != nil { + in, out := &in.HMACAuth, &out.HMACAuth + *out = new(ApisixConsumerHMACAuth) + (*in).DeepCopyInto(*out) + } return } @@ -358,6 +363,53 @@ func (in *ApisixConsumerBasicAuthValue) DeepCopy() *ApisixConsumerBasicAuthValue return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApisixConsumerHMACAuth) DeepCopyInto(out *ApisixConsumerHMACAuth) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.LocalObjectReference) + **out = **in + } + if in.Value != nil { + in, out := &in.Value, &out.Value + *out = new(ApisixConsumerHMACAuthValue) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApisixConsumerHMACAuth. +func (in *ApisixConsumerHMACAuth) DeepCopy() *ApisixConsumerHMACAuth { + if in == nil { + return nil + } + out := new(ApisixConsumerHMACAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ApisixConsumerHMACAuthValue) DeepCopyInto(out *ApisixConsumerHMACAuthValue) { + *out = *in + if in.SignedHeaders != nil { + in, out := &in.SignedHeaders, &out.SignedHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApisixConsumerHMACAuthValue. +func (in *ApisixConsumerHMACAuthValue) DeepCopy() *ApisixConsumerHMACAuthValue { + if in == nil { + return nil + } + out := new(ApisixConsumerHMACAuthValue) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ApisixConsumerJwtAuth) DeepCopyInto(out *ApisixConsumerJwtAuth) { *out = *in diff --git a/pkg/kube/translation/apisix_consumer.go b/pkg/kube/translation/apisix_consumer.go index 982b741fe0..275f243b44 100644 --- a/pkg/kube/translation/apisix_consumer.go +++ b/pkg/kube/translation/apisix_consumer.go @@ -51,6 +51,12 @@ func (t *translator) TranslateApisixConsumerV2beta3(ac *configv2beta3.ApisixCons return nil, fmt.Errorf("invalid wolf rbac config: %s", err) } plugins["wolf-rbac"] = cfg + } else if ac.Spec.AuthParameter.HMACAuth != nil { + cfg, err := t.translateConsumerHMACAuthPluginV2beta3(ac.Namespace, ac.Spec.AuthParameter.HMACAuth) + if err != nil { + return nil, fmt.Errorf("invaild hmac auth config: %s", err) + } + plugins["hmac-auth"] = cfg } consumer := apisixv1.NewDefaultConsumer() @@ -88,6 +94,12 @@ func (t *translator) TranslateApisixConsumerV2(ac *configv2.ApisixConsumer) (*ap return nil, fmt.Errorf("invalid wolf rbac config: %s", err) } plugins["wolf-rbac"] = cfg + } else if ac.Spec.AuthParameter.HMACAuth != nil { + cfg, err := t.translateConsumerHMACAuthPluginV2(ac.Namespace, ac.Spec.AuthParameter.HMACAuth) + if err != nil { + return nil, fmt.Errorf("invaild hmac auth config: %s", err) + } + plugins["hmac-auth"] = cfg } consumer := apisixv1.NewDefaultConsumer() diff --git a/pkg/kube/translation/apisix_consumer_test.go b/pkg/kube/translation/apisix_consumer_test.go index a875c8a694..1405f2e6c5 100644 --- a/pkg/kube/translation/apisix_consumer_test.go +++ b/pkg/kube/translation/apisix_consumer_test.go @@ -126,6 +126,29 @@ func TestTranslateApisixConsumerV2beta3(t *testing.T) { assert.Equal(t, "https://httpbin.org", cfg4.Server) assert.Equal(t, "test01", cfg4.Appid) + ac = &configv2beta3.ApisixConsumer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "jack", + Namespace: "qa", + }, + Spec: configv2beta3.ApisixConsumerSpec{ + AuthParameter: configv2beta3.ApisixConsumerAuthParameter{ + HMACAuth: &configv2beta3.ApisixConsumerHMACAuth{ + Value: &configv2beta3.ApisixConsumerHMACAuthValue{ + AccessKey: "foo", + SecretKey: "bar", + }, + }, + }, + }, + } + consumer, err = (&translator{}).TranslateApisixConsumerV2beta3(ac) + assert.Nil(t, err) + assert.Len(t, consumer.Plugins, 1) + cfg5 := consumer.Plugins["hmac-auth"].(*apisixv1.HMACAuthConsumerConfig) + assert.Equal(t, "foo", cfg5.AccessKey) + assert.Equal(t, "bar", cfg5.SecretKey) + // No test test cases for secret references as we already test them // in plugin_test.go. } @@ -231,6 +254,29 @@ func TestTranslateApisixConsumerV2(t *testing.T) { assert.Equal(t, "https://httpbin.org", cfg4.Server) assert.Equal(t, "test01", cfg4.Appid) + ac = &configv2.ApisixConsumer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "jack", + Namespace: "qa", + }, + Spec: configv2.ApisixConsumerSpec{ + AuthParameter: configv2.ApisixConsumerAuthParameter{ + HMACAuth: &configv2.ApisixConsumerHMACAuth{ + Value: &configv2.ApisixConsumerHMACAuthValue{ + AccessKey: "foo", + SecretKey: "bar", + }, + }, + }, + }, + } + consumer, err = (&translator{}).TranslateApisixConsumerV2(ac) + assert.Nil(t, err) + assert.Len(t, consumer.Plugins, 1) + cfg5 := consumer.Plugins["hmac-auth"].(*apisixv1.HMACAuthConsumerConfig) + assert.Equal(t, "foo", cfg5.AccessKey) + assert.Equal(t, "bar", cfg5.SecretKey) + // No test test cases for secret references as we already test them // in plugin_test.go. } diff --git a/pkg/kube/translation/apisix_route.go b/pkg/kube/translation/apisix_route.go index e7dc23c8f1..5dafd8426c 100644 --- a/pkg/kube/translation/apisix_route.go +++ b/pkg/kube/translation/apisix_route.go @@ -276,6 +276,8 @@ func (t *translator) translateHTTPRouteV2beta3(ctx *TranslateContext, ar *config pluginMap["wolf-rbac"] = make(map[string]interface{}) case "jwtAuth": pluginMap["jwt-auth"] = part.Authentication.JwtAuth + case "hmacAuth": + pluginMap["hmac-auth"] = make(map[string]interface{}) default: pluginMap["basic-auth"] = make(map[string]interface{}) } @@ -410,6 +412,8 @@ func (t *translator) translateHTTPRouteV2(ctx *TranslateContext, ar *configv2.Ap pluginMap["wolf-rbac"] = make(map[string]interface{}) case "jwtAuth": pluginMap["jwt-auth"] = part.Authentication.JwtAuth + case "hmacAuth": + pluginMap["hmac-auth"] = make(map[string]interface{}) default: pluginMap["basic-auth"] = make(map[string]interface{}) } @@ -635,6 +639,8 @@ func (t *translator) translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext, pluginMap["wolf-rbac"] = make(map[string]interface{}) case "jwtAuth": pluginMap["jwt-auth"] = part.Authentication.JwtAuth + case "hmacAuth": + pluginMap["hmac-auth"] = make(map[string]interface{}) default: pluginMap["basic-auth"] = make(map[string]interface{}) } @@ -692,6 +698,8 @@ func (t *translator) translateHTTPRouteV2NotStrictly(ctx *TranslateContext, ar * pluginMap["wolf-rbac"] = make(map[string]interface{}) case "jwtAuth": pluginMap["jwt-auth"] = part.Authentication.JwtAuth + case "hmacAuth": + pluginMap["hmac-auth"] = make(map[string]interface{}) default: pluginMap["basic-auth"] = make(map[string]interface{}) } diff --git a/pkg/kube/translation/plugin.go b/pkg/kube/translation/plugin.go index 5b532932ae..0340a2d5c2 100644 --- a/pkg/kube/translation/plugin.go +++ b/pkg/kube/translation/plugin.go @@ -29,6 +29,13 @@ var ( _errPasswordNotFoundOrInvalid = errors.New("key \"password\" not found or invalid in secret") _jwtAuthExpDefaultValue = int64(868400) + + _hmacAuthAlgorithmDefaultValue = "hmac-sha256" + _hmacAuthClockSkewDefaultValue = int64(0) + _hmacAuthKeepHeadersDefaultValue = false + _hmacAuthEncodeURIParamsDefaultValue = true + _hmacAuthValidateRequestBodyDefaultValue = false + _hmacAuthMaxReqBodyDefaultValue = int64(524288) ) func (t *translator) translateTrafficSplitPlugin(ctx *TranslateContext, ns string, defaultBackendWeight int, @@ -302,3 +309,213 @@ func (t *translator) translateConsumerJwtAuthPluginV2(consumerNamespace string, Base64Secret: base64Secret, }, nil } + +func (t *translator) translateConsumerHMACAuthPluginV2beta3(consumerNamespace string, cfg *configv2beta3.ApisixConsumerHMACAuth) (*apisixv1.HMACAuthConsumerConfig, error) { + if cfg.Value != nil { + return &apisixv1.HMACAuthConsumerConfig{ + AccessKey: cfg.Value.AccessKey, + SecretKey: cfg.Value.SecretKey, + Algorithm: cfg.Value.Algorithm, + ClockSkew: cfg.Value.ClockSkew, + SignedHeaders: cfg.Value.SignedHeaders, + KeepHeaders: cfg.Value.KeepHeaders, + EncodeURIParams: cfg.Value.EncodeURIParams, + ValidateRequestBody: cfg.Value.ValidateRequestBody, + MaxReqBody: cfg.Value.MaxReqBody, + }, nil + } + + sec, err := t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name) + if err != nil { + return nil, err + } + + accessKeyRaw, ok := sec.Data["access_key"] + if !ok || len(accessKeyRaw) == 0 { + return nil, _errKeyNotFoundOrInvalid + } + + secretKeyRaw, ok := sec.Data["secret_key"] + if !ok || len(secretKeyRaw) == 0 { + return nil, _errKeyNotFoundOrInvalid + } + + algorithmRaw, ok := sec.Data["algorithm"] + var algorithm string + if !ok { + algorithm = _hmacAuthAlgorithmDefaultValue + } else { + algorithm = string(algorithmRaw) + } + + clockSkewRaw := sec.Data["clock_skew"] + clockSkew, _ := strconv.ParseInt(string(clockSkewRaw), 10, 64) + if clockSkew < 0 { + clockSkew = _hmacAuthClockSkewDefaultValue + } + + var signedHeaders []string + signedHeadersRaw := sec.Data["signed_headers"] + for _, b := range signedHeadersRaw { + signedHeaders = append(signedHeaders, string(b)) + } + + var keepHeader bool + keepHeaderRaw, ok := sec.Data["keep_headers"] + if !ok { + keepHeader = _hmacAuthKeepHeadersDefaultValue + } else { + if string(keepHeaderRaw) == "true" { + keepHeader = true + } else { + keepHeader = false + } + } + + var encodeURIParams bool + encodeURIParamsRaw, ok := sec.Data["encode_uri_params"] + if !ok { + encodeURIParams = _hmacAuthEncodeURIParamsDefaultValue + } else { + if string(encodeURIParamsRaw) == "true" { + encodeURIParams = true + } else { + encodeURIParams = false + } + } + + var validateRequestBody bool + validateRequestBodyRaw, ok := sec.Data["validate_request_body"] + if !ok { + validateRequestBody = _hmacAuthValidateRequestBodyDefaultValue + } else { + if string(validateRequestBodyRaw) == "true" { + validateRequestBody = true + } else { + validateRequestBody = false + } + } + + maxReqBodyRaw := sec.Data["max_req_body"] + maxReqBody, _ := strconv.ParseInt(string(maxReqBodyRaw), 10, 64) + if maxReqBody < 0 { + maxReqBody = _hmacAuthMaxReqBodyDefaultValue + } + + return &apisixv1.HMACAuthConsumerConfig{ + AccessKey: string(accessKeyRaw), + SecretKey: string(secretKeyRaw), + Algorithm: algorithm, + ClockSkew: clockSkew, + SignedHeaders: signedHeaders, + KeepHeaders: keepHeader, + EncodeURIParams: encodeURIParams, + ValidateRequestBody: validateRequestBody, + MaxReqBody: maxReqBody, + }, nil +} + +func (t *translator) translateConsumerHMACAuthPluginV2(consumerNamespace string, cfg *configv2.ApisixConsumerHMACAuth) (*apisixv1.HMACAuthConsumerConfig, error) { + if cfg.Value != nil { + return &apisixv1.HMACAuthConsumerConfig{ + AccessKey: cfg.Value.AccessKey, + SecretKey: cfg.Value.SecretKey, + Algorithm: cfg.Value.Algorithm, + ClockSkew: cfg.Value.ClockSkew, + SignedHeaders: cfg.Value.SignedHeaders, + KeepHeaders: cfg.Value.KeepHeaders, + EncodeURIParams: cfg.Value.EncodeURIParams, + ValidateRequestBody: cfg.Value.ValidateRequestBody, + MaxReqBody: cfg.Value.MaxReqBody, + }, nil + } + + sec, err := t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name) + if err != nil { + return nil, err + } + + accessKeyRaw, ok := sec.Data["access_key"] + if !ok || len(accessKeyRaw) == 0 { + return nil, _errKeyNotFoundOrInvalid + } + + secretKeyRaw, ok := sec.Data["secret_key"] + if !ok || len(secretKeyRaw) == 0 { + return nil, _errKeyNotFoundOrInvalid + } + + algorithmRaw, ok := sec.Data["algorithm"] + var algorithm string + if !ok { + algorithm = _hmacAuthAlgorithmDefaultValue + } else { + algorithm = string(algorithmRaw) + } + + clockSkewRaw := sec.Data["clock_skew"] + clockSkew, _ := strconv.ParseInt(string(clockSkewRaw), 10, 64) + if clockSkew < 0 { + clockSkew = _hmacAuthClockSkewDefaultValue + } + + var signedHeaders []string + signedHeadersRaw := sec.Data["signed_headers"] + for _, b := range signedHeadersRaw { + signedHeaders = append(signedHeaders, string(b)) + } + + var keepHeader bool + keepHeaderRaw, ok := sec.Data["keep_headers"] + if !ok { + keepHeader = _hmacAuthKeepHeadersDefaultValue + } else { + if string(keepHeaderRaw) == "true" { + keepHeader = true + } else { + keepHeader = false + } + } + + var encodeURIParams bool + encodeURIParamsRaw, ok := sec.Data["encode_uri_params"] + if !ok { + encodeURIParams = _hmacAuthEncodeURIParamsDefaultValue + } else { + if string(encodeURIParamsRaw) == "true" { + encodeURIParams = true + } else { + encodeURIParams = false + } + } + + var validateRequestBody bool + validateRequestBodyRaw, ok := sec.Data["validate_request_body"] + if !ok { + validateRequestBody = _hmacAuthValidateRequestBodyDefaultValue + } else { + if string(validateRequestBodyRaw) == "true" { + validateRequestBody = true + } else { + validateRequestBody = false + } + } + + maxReqBodyRaw := sec.Data["max_req_body"] + maxReqBody, _ := strconv.ParseInt(string(maxReqBodyRaw), 10, 64) + if maxReqBody < 0 { + maxReqBody = _hmacAuthMaxReqBodyDefaultValue + } + + return &apisixv1.HMACAuthConsumerConfig{ + AccessKey: string(accessKeyRaw), + SecretKey: string(secretKeyRaw), + Algorithm: algorithm, + ClockSkew: clockSkew, + SignedHeaders: signedHeaders, + KeepHeaders: keepHeader, + EncodeURIParams: encodeURIParams, + ValidateRequestBody: validateRequestBody, + MaxReqBody: maxReqBody, + }, nil +} diff --git a/pkg/kube/translation/plugin_test.go b/pkg/kube/translation/plugin_test.go index 94cbc9ee7e..54f0bab14a 100644 --- a/pkg/kube/translation/plugin_test.go +++ b/pkg/kube/translation/plugin_test.go @@ -789,7 +789,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "public") @@ -797,7 +797,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "private") @@ -805,7 +805,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "algorithm") @@ -813,7 +813,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "exp") @@ -821,7 +821,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "base64_secret") @@ -829,7 +829,7 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) + _, err = tr.translateConsumerJwtAuthPluginV2beta3("default", jwtAuth) assert.Nil(t, err) delete(sec.Data, "key") @@ -914,7 +914,7 @@ func TestTranslateConsumerWolfRBACWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) + _, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) assert.Nil(t, err) delete(sec.Data, "appid") @@ -922,7 +922,7 @@ func TestTranslateConsumerWolfRBACWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) + _, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) assert.Nil(t, err) delete(sec.Data, "header_prefix") @@ -930,9 +930,95 @@ func TestTranslateConsumerWolfRBACWithSecretRef(t *testing.T) { assert.Nil(t, err) <-processCh - cfg, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) + _, err = tr.translateConsumerWolfRBACPluginV2beta3("default", wolfRBAC) assert.Nil(t, err) close(processCh) close(stopCh) } + +func TestTranslateConsumerHMACAuthPluginWithInPlaceValue(t *testing.T) { + hmacAuth := &configv2beta3.ApisixConsumerHMACAuth{ + Value: &configv2beta3.ApisixConsumerHMACAuthValue{ + AccessKey: "foo", + SecretKey: "foo-secret", + ClockSkew: 0, + SignedHeaders: []string{"User-Agent"}, + }, + } + cfg, err := (&translator{}).translateConsumerHMACAuthPluginV2beta3("default", hmacAuth) + assert.Nil(t, err) + assert.Equal(t, "foo", cfg.AccessKey) + assert.Equal(t, "foo-secret", cfg.SecretKey) + assert.Equal(t, int64(0), cfg.ClockSkew) + assert.Equal(t, []string{"User-Agent"}, cfg.SignedHeaders) +} + +func TestTranslateConsumerHMACAuthPluginWithSecretRef(t *testing.T) { + sec := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "fatpa-hmac-auth", + }, + Data: map[string][]byte{ + "access_key": []byte("foo"), + "secret_key": []byte("foo-secret"), + "clock_skew": []byte("0"), + }, + } + + 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 + + hmacAuth := &configv2beta3.ApisixConsumerHMACAuth{ + SecretRef: &corev1.LocalObjectReference{Name: "fatpa-hmac-auth"}, + } + cfg, err := tr.translateConsumerHMACAuthPluginV2beta3("default", hmacAuth) + assert.Nil(t, err) + assert.Equal(t, "foo", cfg.AccessKey) + assert.Equal(t, "foo-secret", cfg.SecretKey) + assert.Equal(t, int64(0), cfg.ClockSkew) + + delete(sec.Data, "access_key") + _, err = client.CoreV1().Secrets("default").Update(context.Background(), sec, metav1.UpdateOptions{}) + assert.Nil(t, err) + <-processCh + + cfg, err = tr.translateConsumerHMACAuthPluginV2beta3("default", hmacAuth) + assert.Nil(t, cfg) + assert.Equal(t, _errKeyNotFoundOrInvalid, err) + + delete(sec.Data, "secret_key") + _, err = client.CoreV1().Secrets("default").Update(context.Background(), sec, metav1.UpdateOptions{}) + assert.Nil(t, err) + <-processCh + + cfg, err = tr.translateConsumerHMACAuthPluginV2beta3("default", hmacAuth) + assert.Nil(t, cfg) + assert.Equal(t, _errKeyNotFoundOrInvalid, err) + + close(processCh) + close(stopCh) +} diff --git a/pkg/types/apisix/v1/plugin_types.go b/pkg/types/apisix/v1/plugin_types.go index d46b3b50ff..5859129398 100644 --- a/pkg/types/apisix/v1/plugin_types.go +++ b/pkg/types/apisix/v1/plugin_types.go @@ -83,6 +83,21 @@ type JwtAuthConsumerConfig struct { Base64Secret bool `json:"base64_secret,omitempty" yaml:"base64_secret,omitempty"` } +// HMACAuthConsumerConfig is the rule config for hmac-auth plugin +// used in Consumer object. +// +k8s:deepcopy-gen=true +type HMACAuthConsumerConfig struct { + AccessKey string `json:"access_key" yaml:"access_key"` + SecretKey string `json:"secret_key" yaml:"secret_key"` + Algorithm string `json:"algorithm,omitempty" yaml:"algorithm,omitempty"` + ClockSkew int64 `json:"clock_skew,omitempty" yaml:"clock_skew,omitempty"` + SignedHeaders []string `json:"signed_headers,omitempty" yaml:"signed_headers,omitempty"` + KeepHeaders bool `json:"keep_headers,omitempty" yaml:"keep_headers,omitempty"` + EncodeURIParams bool `json:"encode_uri_params,omitempty" yaml:"encode_uri_params,omitempty"` + ValidateRequestBody bool `json:"validate_request_body,omitempty" yaml:"validate_request_body,omitempty"` + MaxReqBody int64 `json:"max_req_body,omitempty" yaml:"max_req_body,omitempty"` +} + // BasicAuthRouteConfig is the rule config for basic-auth plugin // used in Route object. // +k8s:deepcopy-gen=true diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go b/pkg/types/apisix/v1/zz_generated.deepcopy.go index 480cd434e8..2edb528681 100644 --- a/pkg/types/apisix/v1/zz_generated.deepcopy.go +++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go @@ -155,6 +155,27 @@ func (in *GlobalRule) DeepCopy() *GlobalRule { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HMACAuthConsumerConfig) DeepCopyInto(out *HMACAuthConsumerConfig) { + *out = *in + if in.SignedHeaders != nil { + in, out := &in.SignedHeaders, &out.SignedHeaders + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HMACAuthConsumerConfig. +func (in *HMACAuthConsumerConfig) DeepCopy() *HMACAuthConsumerConfig { + if in == nil { + return nil + } + out := new(HMACAuthConsumerConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *IPRestrictConfig) DeepCopyInto(out *IPRestrictConfig) { *out = *in diff --git a/samples/deploy/crd/v1/ApisixConsumer.yaml b/samples/deploy/crd/v1/ApisixConsumer.yaml index 4c967194b9..324983866e 100644 --- a/samples/deploy/crd/v1/ApisixConsumer.yaml +++ b/samples/deploy/crd/v1/ApisixConsumer.yaml @@ -51,6 +51,7 @@ spec: - required: ["keyAuth"] - required: ["wolfRBAC"] - required: ["jwtAuth"] + - required: ["hmacAuth"] properties: basicAuth: type: object @@ -122,7 +123,7 @@ spec: type: string exp: type: integer - base64_secret: + base64_secret: type: boolean required: - key @@ -157,3 +158,43 @@ spec: minLength: 1 required: - name + hmacAuth: + type: object + oneOf: + - required: ["value"] + - required: ["secretRef"] + properties: + value: + type: object + properties: + access_key: + type: string + secret_key: + type: string + algorithm: + type: string + clock_skew: + type: integer + signed_headers: + type: array + items: + type: string + keep_headers: + type: boolean + encode_uri_params: + type: boolean + validate_request_body: + type: boolean + max_req_body: + type: integer + required: + - access_key + - secret_key + secretRef: + type: object + properties: + name: + type: string + minLength: 1 + required: + - name diff --git a/samples/deploy/crd/v1/ApisixRoute.yaml b/samples/deploy/crd/v1/ApisixRoute.yaml index 757b88386d..eb57ccd88c 100644 --- a/samples/deploy/crd/v1/ApisixRoute.yaml +++ b/samples/deploy/crd/v1/ApisixRoute.yaml @@ -93,7 +93,7 @@ spec: match: type: object required: - - paths + - paths properties: paths: type: array @@ -112,7 +112,16 @@ spec: minItems: 1 items: type: string - enum: ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"] + enum: + - "CONNECT" + - "DELETE" + - "GET" + - "HEAD" + - "OPTIONS" + - "PATCH" + - "POST" + - "PUT" + - "TRACE" remoteAddrs: type: array minItems: 1 @@ -129,12 +138,16 @@ spec: properties: scope: type: string - enum: ["Cookie", "Header", "Path", "Query"] + enum: + - "Cookie" + - "Header" + - "Path" + - "Query" name: type: string minLength: 1 required: - - scope + - scope op: type: string enum: @@ -206,7 +219,9 @@ spec: type: boolean type: type: string - enum: [ "basicAuth", "keyAuth" ] + enum: + - "basicAuth" + - "keyAuth" keyAuth: type: object properties: @@ -219,11 +234,11 @@ spec: minItems: 1 items: type: object - required: [ "name", "match", "backend", "protocol" ] + required: ["name", "match", "backend", "protocol"] properties: "protocol": type: string - enum: [ "TCP", "UDP" ] + enum: ["TCP", "UDP"] name: type: string minLength: 1 @@ -248,7 +263,7 @@ spec: maximum: 65535 resolveGranularity: type: string - enum: [ "endpoint", "service" ] + enum: ["endpoint", "service"] subset: type: string required: @@ -276,7 +291,7 @@ spec: served: true storage: false subresources: - status: { } + status: {} additionalPrinterColumns: - jsonPath: .spec.http[].match.hosts name: Hosts @@ -309,15 +324,15 @@ spec: spec: type: object anyOf: - - required: [ "http" ] - - required: [ "stream" ] + - required: ["http"] + - required: ["stream"] properties: http: type: array minItems: 1 items: type: object - required: [ "name", "match", "backends" ] + required: ["name", "match", "backends"] properties: name: type: string @@ -355,7 +370,16 @@ spec: minItems: 1 items: type: string - enum: [ "CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE" ] + enum: + - "CONNECT" + - "DELETE" + - "GET" + - "HEAD" + - "OPTIONS" + - "PATCH" + - "POST" + - "PUT" + - "TRACE" remoteAddrs: type: array minItems: 1 @@ -372,7 +396,11 @@ spec: properties: scope: type: string - enum: [ "Cookie", "Header", "Path", "Query" ] + enum: + - "Cookie" + - "Header" + - "Path" + - "Query" name: type: string minLength: 1 @@ -398,8 +426,8 @@ spec: items: type: string oneOf: - - required: [ "subject", "op", "value" ] - - required: [ "subject", "op", "set" ] + - required: ["subject", "op", "value"] + - required: ["subject", "op", "set"] websocket: type: boolean plugin_config_name: @@ -420,7 +448,7 @@ spec: maximum: 65535 resolveGranularity: type: string - enum: [ "endpoint", "service" ] + enum: ["endpoint", "service"] weight: type: integer minimum: 0 @@ -452,7 +480,12 @@ spec: type: boolean type: type: string - enum: [ "basicAuth", "keyAuth", "jwtAuth", "wolfRBAC" ] + enum: + - "basicAuth" + - "keyAuth" + - "jwtAuth" + - "wolfRBAC" + - "hmacAuth" keyAuth: type: object properties: @@ -474,11 +507,11 @@ spec: minItems: 1 items: type: object - required: [ "name", "match", "backend", "protocol" ] + required: ["name", "match", "backend", "protocol"] properties: "protocol": type: string - enum: [ "TCP", "UDP" ] + enum: ["TCP", "UDP"] name: type: string minLength: 1 @@ -503,7 +536,7 @@ spec: maximum: 65535 resolveGranularity: type: string - enum: [ "endpoint", "service" ] + enum: ["endpoint", "service"] subset: type: string required: @@ -531,7 +564,7 @@ spec: served: true storage: true subresources: - status: { } + status: {} additionalPrinterColumns: - jsonPath: .spec.http[].match.hosts name: Hosts @@ -564,15 +597,15 @@ spec: spec: type: object anyOf: - - required: [ "http" ] - - required: [ "stream" ] + - required: ["http"] + - required: ["stream"] properties: http: type: array minItems: 1 items: type: object - required: [ "name", "match", "backends" ] + required: ["name", "match", "backends"] properties: name: type: string @@ -610,7 +643,16 @@ spec: minItems: 1 items: type: string - enum: [ "CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE" ] + enum: + - "CONNECT" + - "DELETE" + - "GET" + - "HEAD" + - "OPTIONS" + - "PATCH" + - "POST" + - "PUT" + - "TRACE" remoteAddrs: type: array minItems: 1 @@ -627,7 +669,11 @@ spec: properties: scope: type: string - enum: [ "Cookie", "Header", "Path", "Query" ] + enum: + - "Cookie" + - "Header" + - "Path" + - "Query" name: type: string minLength: 1 @@ -653,8 +699,8 @@ spec: items: type: string oneOf: - - required: [ "subject", "op", "value" ] - - required: [ "subject", "op", "set" ] + - required: ["subject", "op", "value"] + - required: ["subject", "op", "set"] websocket: type: boolean plugin_config_name: @@ -675,7 +721,7 @@ spec: maximum: 65535 resolveGranularity: type: string - enum: [ "endpoint", "service" ] + enum: ["endpoint", "service"] weight: type: integer minimum: 0 @@ -707,7 +753,9 @@ spec: type: boolean type: type: string - enum: [ "basicAuth", "keyAuth" ] + enum: + - "basicAuth" + - "keyAuth" keyAuth: type: object properties: @@ -720,11 +768,11 @@ spec: minItems: 1 items: type: object - required: [ "name", "match", "backend", "protocol" ] + required: ["name", "match", "backend", "protocol"] properties: "protocol": type: string - enum: [ "TCP", "UDP" ] + enum: ["TCP", "UDP"] name: type: string minLength: 1 @@ -749,7 +797,7 @@ spec: maximum: 65535 resolveGranularity: type: string - enum: [ "endpoint", "service" ] + enum: ["endpoint", "service"] subset: type: string required: @@ -772,4 +820,4 @@ spec: message: type: string observedGeneration: - type: integer \ No newline at end of file + type: integer diff --git a/test/e2e/suite-features/consumer.go b/test/e2e/suite-features/consumer.go index 5efef35d3a..599e0063e7 100644 --- a/test/e2e/suite-features/consumer.go +++ b/test/e2e/suite-features/consumer.go @@ -636,6 +636,208 @@ spec: assert.Contains(ginkgo.GinkgoT(), msg401, "Missing rbac token in request") }) + ginkgo.It("ApisixRoute with hmacAuth consumer", func() { + ac := ` +apiVersion: apisix.apache.org/v2beta3 +kind: ApisixConsumer +metadata: + name: hmacvalue +spec: + authParameter: + hmacAuth: + value: + access_key: papa + secret_key: fatpa + algorithm: "hmac-sha256" + clock_skew: 0 +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), "creating hmacAuth ApisixConsumer") + + // Wait until the ApisixConsumer create event was delivered. + time.Sleep(6 * time.Second) + + grs, err := s.ListApisixConsumers() + assert.Nil(ginkgo.GinkgoT(), err, "listing consumer") + assert.Len(ginkgo.GinkgoT(), grs, 1) + assert.Len(ginkgo.GinkgoT(), grs[0].Plugins, 1) + hmacAuth, _ := grs[0].Plugins["hmac-auth"].(map[string]interface{}) + assert.Equal(ginkgo.GinkgoT(), "papa", hmacAuth["access_key"]) + assert.Equal(ginkgo.GinkgoT(), "fatpa", hmacAuth["secret_key"]) + assert.Equal(ginkgo.GinkgoT(), "hmac-sha256", hmacAuth["algorithm"]) + assert.Equal(ginkgo.GinkgoT(), float64(0), hmacAuth["clock_skew"]) + + backendSvc, backendPorts := s.DefaultHTTPBackend() + ar := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2beta3 +kind: ApisixRoute +metadata: + name: httpbin-route +spec: + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /ip + exprs: + - subject: + scope: Header + name: X-Foo + op: Equal + value: bar + backends: + - serviceName: %s + servicePort: %d + authentication: + enable: true + type: hmacAuth +`, backendSvc, backendPorts[0]) + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), "creating ApisixRoute with hmacAuth") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1), "Checking number of routes") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams") + + _ = s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + WithHeader("X-HMAC-SIGNATURE", "l3Uka7E1kxPA/owQ2+OqJUmflRppjD5q8xPcWbyKKrg="). + WithHeader("X-HMAC-ACCESS-KEY", "papa"). + WithHeader("X-HMAC-ALGORITHM", "hmac-sha256"). + WithHeader("X-HMAC-SIGNED-HEADERS", "User-Agent;X-Foo"). + WithHeader("User-Agent", "curl/7.29.0"). + Expect(). + Status(http.StatusOK) + + msg := s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + Expect(). + Status(http.StatusUnauthorized). + Body(). + Raw() + assert.Contains(ginkgo.GinkgoT(), msg, "access key or signature missing") + + msg = s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "baz"). + WithHeader("X-HMAC-SIGNATURE", "MhGJMkEYFD+98qtvoDPlvCGIUSmmUaw0In/D0vt2Z4E="). + WithHeader("X-HMAC-ACCESS-KEY", "papa"). + WithHeader("X-HMAC-ALGORITHM", "hmac-sha256"). + WithHeader("X-HMAC-SIGNED-HEADERS", "User-Agent;X-Foo"). + WithHeader("User-Agent", "curl/7.29.0"). + Expect(). + Status(http.StatusNotFound). + Body(). + Raw() + assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found") + }) + + ginkgo.It("ApisixRoute with hmacAuth consumer using secret", func() { + secret := ` +apiVersion: v1 +kind: Secret +metadata: + name: hmac +data: + access_key: cGFwYQ== + secret_key: ZmF0cGE= + algorithm: aG1hYy1zaGEyNTY= + clock_skew: MA== +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(secret), "creating hmac secret for ApisixConsumer") + + ac := ` +apiVersion: apisix.apache.org/v2beta3 +kind: ApisixConsumer +metadata: + name: hmacvalue +spec: + authParameter: + hmacAuth: + secretRef: + name: hmac +` + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), "creating hmacAuth ApisixConsumer") + + // Wait until the ApisixConsumer create event was delivered. + time.Sleep(6 * time.Second) + + grs, err := s.ListApisixConsumers() + assert.Nil(ginkgo.GinkgoT(), err, "listing consumer") + assert.Len(ginkgo.GinkgoT(), grs, 1) + assert.Len(ginkgo.GinkgoT(), grs[0].Plugins, 1) + hmacAuth, _ := grs[0].Plugins["hmac-auth"].(map[string]interface{}) + assert.Equal(ginkgo.GinkgoT(), "papa", hmacAuth["access_key"]) + assert.Equal(ginkgo.GinkgoT(), "fatpa", hmacAuth["secret_key"]) + assert.Equal(ginkgo.GinkgoT(), "hmac-sha256", hmacAuth["algorithm"]) + assert.Equal(ginkgo.GinkgoT(), float64(0), hmacAuth["clock_skew"]) + + backendSvc, backendPorts := s.DefaultHTTPBackend() + ar := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2beta3 +kind: ApisixRoute +metadata: + name: httpbin-route +spec: + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /ip + exprs: + - subject: + scope: Header + name: X-Foo + op: Equal + value: bar + backends: + - serviceName: %s + servicePort: %d + authentication: + enable: true + type: hmacAuth +`, backendSvc, backendPorts[0]) + assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), "creating ApisixRoute with hmacAuth") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1), "Checking number of routes") + assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams") + + _ = s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + WithHeader("X-HMAC-SIGNATURE", "l3Uka7E1kxPA/owQ2+OqJUmflRppjD5q8xPcWbyKKrg="). + WithHeader("X-HMAC-ACCESS-KEY", "papa"). + WithHeader("X-HMAC-ALGORITHM", "hmac-sha256"). + WithHeader("X-HMAC-SIGNED-HEADERS", "User-Agent;X-Foo"). + WithHeader("User-Agent", "curl/7.29.0"). + Expect(). + Status(http.StatusOK) + + msg := s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + Expect(). + Status(http.StatusUnauthorized). + Body(). + Raw() + assert.Contains(ginkgo.GinkgoT(), msg, "access key or signature missing") + + msg = s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "baz"). + WithHeader("X-HMAC-SIGNATURE", "MhGJMkEYFD+98qtvoDPlvCGIUSmmUaw0In/D0vt2Z4E="). + WithHeader("X-HMAC-ACCESS-KEY", "papa"). + WithHeader("X-HMAC-ALGORITHM", "hmac-sha256"). + WithHeader("X-HMAC-SIGNED-HEADERS", "User-Agent;X-Foo"). + WithHeader("User-Agent", "curl/7.29.0"). + Expect(). + Status(http.StatusNotFound). + Body(). + Raw() + assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found") + }) + ginkgo.It("ApisixRoute with jwtAuth consumer", func() { ac := ` apiVersion: apisix.apache.org/v2beta3 diff --git a/tools.go b/tools.go index 3fd06649ec..ade0979c3b 100644 --- a/tools.go +++ b/tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools // Licensed to the Apache Software Foundation (ASF) under one or more