You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by zh...@apache.org on 2022/05/26 14:23:55 UTC

[apisix-ingress-controller] branch master updated: feat: add hmac-auth authorization method (#1035)

This is an automated email from the ASF dual-hosted git repository.

zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new 59ba41a8 feat: add hmac-auth authorization method (#1035)
59ba41a8 is described below

commit 59ba41a8c5780b9273610e944aabda700078a3db
Author: Fatpa <fa...@gmail.com>
AuthorDate: Thu May 26 22:23:50 2022 +0800

    feat: add hmac-auth authorization method (#1035)
---
 .gitignore                                         |   1 +
 pkg/kube/apisix/apis/config/v2/types.go            |  20 ++
 .../apisix/apis/config/v2/zz_generated.deepcopy.go |  52 +++++
 pkg/kube/apisix/apis/config/v2beta3/types.go       |  20 ++
 .../apis/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 5fbca57d..aa6584f9 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 aeace4d6..bd841298 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 4815aaa5..f60dffd6 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 07a3a927..36b8dbb7 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 9682c936..80391804 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 982b741f..275f243b 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 a875c8a6..1405f2e6 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 e7dc23c8..5dafd842 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 5b532932..0340a2d5 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 94cbc9ee..54f0bab1 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 d46b3b50..58591293 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 480cd434..2edb5286 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 4c967194..32498386 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 757b8838..eb57ccd8 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 5efef35d..599e0063 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 3fd06649..ade0979c 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