Skip to content

Commit d904d73

Browse files
authored
feat: add secret/service resource checker for webhook (#2580) (#276)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent 4270ade commit d904d73

13 files changed

+1514
-0
lines changed

config/webhook/manifests.yaml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,86 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-apisix-apache-org-v2-apisixconsumer
14+
failurePolicy: Fail
15+
name: vapisixconsumer-v2.kb.io
16+
rules:
17+
- apiGroups:
18+
- apisix.apache.org
19+
apiVersions:
20+
- v2
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- apisixconsumers
26+
sideEffects: None
27+
- admissionReviewVersions:
28+
- v1
29+
clientConfig:
30+
service:
31+
name: webhook-service
32+
namespace: system
33+
path: /validate-apisix-apache-org-v2-apisixroute
34+
failurePolicy: Fail
35+
name: vapisixroute-v2.kb.io
36+
rules:
37+
- apiGroups:
38+
- apisix.apache.org
39+
apiVersions:
40+
- v2
41+
operations:
42+
- CREATE
43+
- UPDATE
44+
resources:
45+
- apisixroutes
46+
sideEffects: None
47+
- admissionReviewVersions:
48+
- v1
49+
clientConfig:
50+
service:
51+
name: webhook-service
52+
namespace: system
53+
path: /validate-apisix-apache-org-v2-apisixtls
54+
failurePolicy: Fail
55+
name: vapisixtls-v2.kb.io
56+
rules:
57+
- apiGroups:
58+
- apisix.apache.org
59+
apiVersions:
60+
- v2
61+
operations:
62+
- CREATE
63+
- UPDATE
64+
resources:
65+
- apisixtlses
66+
sideEffects: None
67+
- admissionReviewVersions:
68+
- v1
69+
clientConfig:
70+
service:
71+
name: webhook-service
72+
namespace: system
73+
path: /validate-apisix-apache-org-v1alpha1-consumer
74+
failurePolicy: Fail
75+
name: vconsumer-v1alpha1.kb.io
76+
rules:
77+
- apiGroups:
78+
- apisix.apache.org
79+
apiVersions:
80+
- v1alpha1
81+
operations:
82+
- CREATE
83+
- UPDATE
84+
resources:
85+
- consumers
86+
sideEffects: None
787
- admissionReviewVersions:
888
- v1
989
clientConfig:
@@ -24,6 +104,26 @@ webhooks:
24104
resources:
25105
- gateways
26106
sideEffects: None
107+
- admissionReviewVersions:
108+
- v1
109+
clientConfig:
110+
service:
111+
name: webhook-service
112+
namespace: system
113+
path: /validate-apisix-apache-org-v1alpha1-gatewayproxy
114+
failurePolicy: Fail
115+
name: vgatewayproxy-v1alpha1.kb.io
116+
rules:
117+
- apiGroups:
118+
- apisix.apache.org
119+
apiVersions:
120+
- v1alpha1
121+
operations:
122+
- CREATE
123+
- UPDATE
124+
resources:
125+
- gatewayproxies
126+
sideEffects: None
27127
- admissionReviewVersions:
28128
- v1
29129
clientConfig:

internal/manager/webhooks.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,20 @@ func setupWebhooks(_ context.Context, mgr manager.Manager) error {
3535
if err := webhookv1.SetupGatewayWebhookWithManager(mgr); err != nil {
3636
return err
3737
}
38+
if err := webhookv1.SetupGatewayProxyWebhookWithManager(mgr); err != nil {
39+
return err
40+
}
41+
if err := webhookv1.SetupApisixConsumerWebhookWithManager(mgr); err != nil {
42+
return err
43+
}
44+
if err := webhookv1.SetupApisixTlsWebhookWithManager(mgr); err != nil {
45+
return err
46+
}
47+
if err := webhookv1.SetupApisixRouteWebhookWithManager(mgr); err != nil {
48+
return err
49+
}
50+
if err := webhookv1.SetupConsumerWebhookWithManager(mgr); err != nil {
51+
return err
52+
}
3853
return nil
3954
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package v1
17+
18+
import (
19+
"context"
20+
"fmt"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/types"
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
logf "sigs.k8s.io/controller-runtime/pkg/log"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30+
31+
apisixv2 "github.com/apache/apisix-ingress-controller/api/v2"
32+
"github.com/apache/apisix-ingress-controller/internal/webhook/v1/reference"
33+
)
34+
35+
var apisixConsumerLog = logf.Log.WithName("apisixconsumer-resource")
36+
37+
func SetupApisixConsumerWebhookWithManager(mgr ctrl.Manager) error {
38+
return ctrl.NewWebhookManagedBy(mgr).
39+
For(&apisixv2.ApisixConsumer{}).
40+
WithValidator(NewApisixConsumerCustomValidator(mgr.GetClient())).
41+
Complete()
42+
}
43+
44+
// +kubebuilder:webhook:path=/validate-apisix-apache-org-v2-apisixconsumer,mutating=false,failurePolicy=fail,sideEffects=None,groups=apisix.apache.org,resources=apisixconsumers,verbs=create;update,versions=v2,name=vapisixconsumer-v2.kb.io,admissionReviewVersions=v1
45+
46+
type ApisixConsumerCustomValidator struct {
47+
Client client.Client
48+
checker reference.Checker
49+
}
50+
51+
var _ webhook.CustomValidator = &ApisixConsumerCustomValidator{}
52+
53+
func NewApisixConsumerCustomValidator(c client.Client) *ApisixConsumerCustomValidator {
54+
return &ApisixConsumerCustomValidator{
55+
Client: c,
56+
checker: reference.NewChecker(c, apisixConsumerLog),
57+
}
58+
}
59+
60+
func (v *ApisixConsumerCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
61+
consumer, ok := obj.(*apisixv2.ApisixConsumer)
62+
if !ok {
63+
return nil, fmt.Errorf("expected an ApisixConsumer object but got %T", obj)
64+
}
65+
apisixConsumerLog.Info("Validation for ApisixConsumer upon creation", "name", consumer.GetName(), "namespace", consumer.GetNamespace())
66+
67+
return v.collectWarnings(ctx, consumer), nil
68+
}
69+
70+
func (v *ApisixConsumerCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
71+
consumer, ok := newObj.(*apisixv2.ApisixConsumer)
72+
if !ok {
73+
return nil, fmt.Errorf("expected an ApisixConsumer object for the newObj but got %T", newObj)
74+
}
75+
apisixConsumerLog.Info("Validation for ApisixConsumer upon update", "name", consumer.GetName(), "namespace", consumer.GetNamespace())
76+
77+
return v.collectWarnings(ctx, consumer), nil
78+
}
79+
80+
func (*ApisixConsumerCustomValidator) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
81+
return nil, nil
82+
}
83+
84+
func (v *ApisixConsumerCustomValidator) collectWarnings(ctx context.Context, consumer *apisixv2.ApisixConsumer) admission.Warnings {
85+
namespace := consumer.GetNamespace()
86+
var warnings admission.Warnings
87+
88+
addSecretWarning := func(ref *corev1.LocalObjectReference) {
89+
if ref == nil || ref.Name == "" {
90+
return
91+
}
92+
93+
warnings = append(warnings, v.checker.Secret(ctx, reference.SecretRef{
94+
Object: consumer,
95+
NamespacedName: types.NamespacedName{
96+
Namespace: namespace,
97+
Name: ref.Name,
98+
},
99+
})...)
100+
}
101+
102+
params := consumer.Spec.AuthParameter
103+
if params.BasicAuth != nil {
104+
addSecretWarning(params.BasicAuth.SecretRef)
105+
}
106+
if params.KeyAuth != nil {
107+
addSecretWarning(params.KeyAuth.SecretRef)
108+
}
109+
if params.WolfRBAC != nil {
110+
addSecretWarning(params.WolfRBAC.SecretRef)
111+
}
112+
if params.JwtAuth != nil {
113+
addSecretWarning(params.JwtAuth.SecretRef)
114+
}
115+
if params.HMACAuth != nil {
116+
addSecretWarning(params.HMACAuth.SecretRef)
117+
}
118+
if params.LDAPAuth != nil {
119+
addSecretWarning(params.LDAPAuth.SecretRef)
120+
}
121+
122+
return warnings
123+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package v1
17+
18+
import (
19+
"context"
20+
"testing"
21+
22+
"github.com/stretchr/testify/require"
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
27+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
28+
29+
apisixv2 "github.com/apache/apisix-ingress-controller/api/v2"
30+
)
31+
32+
func buildApisixConsumerValidator(t *testing.T, objects ...runtime.Object) *ApisixConsumerCustomValidator {
33+
t.Helper()
34+
35+
scheme := runtime.NewScheme()
36+
require.NoError(t, clientgoscheme.AddToScheme(scheme))
37+
require.NoError(t, apisixv2.AddToScheme(scheme))
38+
39+
builder := fake.NewClientBuilder().WithScheme(scheme)
40+
if len(objects) > 0 {
41+
builder = builder.WithRuntimeObjects(objects...)
42+
}
43+
44+
return NewApisixConsumerCustomValidator(builder.Build())
45+
}
46+
47+
func TestApisixConsumerValidator_MissingBasicAuthSecret(t *testing.T) {
48+
consumer := &apisixv2.ApisixConsumer{
49+
ObjectMeta: metav1.ObjectMeta{
50+
Name: "demo",
51+
Namespace: "default",
52+
},
53+
Spec: apisixv2.ApisixConsumerSpec{
54+
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
55+
BasicAuth: &apisixv2.ApisixConsumerBasicAuth{
56+
SecretRef: &corev1.LocalObjectReference{Name: "basic-auth"},
57+
},
58+
},
59+
},
60+
}
61+
62+
validator := buildApisixConsumerValidator(t)
63+
64+
warnings, err := validator.ValidateCreate(context.Background(), consumer)
65+
require.NoError(t, err)
66+
require.Equal(t, 1, len(warnings))
67+
require.Equal(t, "Referenced Secret 'default/basic-auth' not found", warnings[0])
68+
}
69+
70+
func TestApisixConsumerValidator_MultipleSecretWarnings(t *testing.T) {
71+
consumer := &apisixv2.ApisixConsumer{
72+
ObjectMeta: metav1.ObjectMeta{
73+
Name: "demo",
74+
Namespace: "default",
75+
},
76+
Spec: apisixv2.ApisixConsumerSpec{
77+
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
78+
BasicAuth: &apisixv2.ApisixConsumerBasicAuth{
79+
SecretRef: &corev1.LocalObjectReference{Name: "basic-auth"},
80+
},
81+
JwtAuth: &apisixv2.ApisixConsumerJwtAuth{
82+
SecretRef: &corev1.LocalObjectReference{Name: "jwt-auth"},
83+
},
84+
HMACAuth: &apisixv2.ApisixConsumerHMACAuth{
85+
SecretRef: &corev1.LocalObjectReference{Name: "hmac-auth"},
86+
},
87+
},
88+
},
89+
}
90+
91+
basicAuthSecret := &corev1.Secret{
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: "basic-auth",
94+
Namespace: "default",
95+
},
96+
}
97+
98+
validator := buildApisixConsumerValidator(t, basicAuthSecret)
99+
100+
warnings, err := validator.ValidateCreate(context.Background(), consumer)
101+
require.NoError(t, err)
102+
require.Len(t, warnings, 2)
103+
require.ElementsMatch(t, []string{
104+
"Referenced Secret 'default/jwt-auth' not found",
105+
"Referenced Secret 'default/hmac-auth' not found",
106+
}, warnings)
107+
}
108+
109+
func TestApisixConsumerValidator_NoWarningsWhenSecretsExist(t *testing.T) {
110+
consumer := &apisixv2.ApisixConsumer{
111+
ObjectMeta: metav1.ObjectMeta{
112+
Name: "demo",
113+
Namespace: "default",
114+
},
115+
Spec: apisixv2.ApisixConsumerSpec{
116+
AuthParameter: apisixv2.ApisixConsumerAuthParameter{
117+
KeyAuth: &apisixv2.ApisixConsumerKeyAuth{
118+
SecretRef: &corev1.LocalObjectReference{Name: "key-auth"},
119+
},
120+
WolfRBAC: &apisixv2.ApisixConsumerWolfRBAC{
121+
SecretRef: &corev1.LocalObjectReference{Name: "wolf-rbac"},
122+
},
123+
},
124+
},
125+
}
126+
127+
secrets := []runtime.Object{
128+
&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "key-auth", Namespace: "default"}},
129+
&corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "wolf-rbac", Namespace: "default"}},
130+
}
131+
132+
validator := buildApisixConsumerValidator(t, secrets...)
133+
134+
warnings, err := validator.ValidateCreate(context.Background(), consumer)
135+
require.NoError(t, err)
136+
require.Empty(t, warnings)
137+
}

0 commit comments

Comments
 (0)