Skip to content

Commit 1aa7620

Browse files
committed
feat: support apisix tls
Signed-off-by: ashing <axingfly@gmail.com>
1 parent b55be27 commit 1aa7620

7 files changed

Lines changed: 517 additions & 20 deletions

File tree

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1-
apiVersion: apisix.apache.org.github.com/v2
1+
apiVersion: apisix.apache.org/v2
22
kind: ApisixTls
33
metadata:
44
labels:
55
app.kubernetes.io/name: apisix-ingress-controller
66
app.kubernetes.io/managed-by: kustomize
77
name: apisixtls-sample
88
spec:
9-
# TODO(user): Add fields here
9+
hosts:
10+
- "example.com"
11+
- "*.example.com"
12+
secret:
13+
name: "example-tls-secret"
14+
namespace: "default"
15+
# Optional: Mutual TLS configuration
16+
# client:
17+
# caSecret:
18+
# name: "ca-secret"
19+
# namespace: "default"
20+
# depth: 2
21+
# skip_mtls_uri_regex:
22+
# - "/health"
23+
# - "/metrics"

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ require (
147147
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
148148
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
149149
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
150+
github.com/onsi/ginkgo v1.10.1 // indirect
150151
github.com/opencontainers/go-digest v1.0.0 // indirect
151152
github.com/opencontainers/image-spec v1.1.0 // indirect
152153
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect

internal/controller/apisixtls_controller.go

Lines changed: 231 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,56 +14,269 @@ package controller
1414

1515
import (
1616
"context"
17+
"fmt"
1718

1819
"github.com/go-logr/logr"
20+
corev1 "k8s.io/api/core/v1"
21+
networkingv1 "k8s.io/api/networking/v1"
1922
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2023
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/types"
2125
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/builder"
2227
"sigs.k8s.io/controller-runtime/pkg/client"
23-
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
28+
"sigs.k8s.io/controller-runtime/pkg/handler"
29+
"sigs.k8s.io/controller-runtime/pkg/predicate"
30+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
2431

2532
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
33+
"github.com/apache/apisix-ingress-controller/internal/provider"
34+
"github.com/apache/apisix-ingress-controller/internal/utils"
2635
)
2736

2837
// ApisixTlsReconciler reconciles a ApisixTls object
2938
type ApisixTlsReconciler struct {
3039
client.Client
31-
Scheme *runtime.Scheme
32-
Log logr.Logger
40+
Scheme *runtime.Scheme
41+
Log logr.Logger
42+
Provider provider.Provider
3343
}
3444

35-
// Reconcile FIXME: implement the reconcile logic (For now, it dose nothing other than directly accepting)
45+
// Reconcile processes ApisixTls resources
3646
func (r *ApisixTlsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
37-
r.Log.Info("reconcile", "request", req.NamespacedName)
47+
var tls apiv2.ApisixTls
48+
if err := r.Get(ctx, req.NamespacedName, &tls); err != nil {
49+
if client.IgnoreNotFound(err) == nil {
50+
tls.Namespace = req.Namespace
51+
tls.Name = req.Name
52+
tls.TypeMeta = metav1.TypeMeta{
53+
Kind: "ApisixTls",
54+
APIVersion: apiv2.GroupVersion.String(),
55+
}
3856

39-
var obj apiv2.ApisixTls
40-
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
41-
r.Log.Error(err, "failed to get ApisixConsumer", "request", req.NamespacedName)
57+
if err := r.Provider.Delete(ctx, &tls); err != nil {
58+
r.Log.Error(err, "failed to delete ApisixTls", "tls", tls)
59+
return ctrl.Result{}, err
60+
}
61+
return ctrl.Result{}, nil
62+
}
4263
return ctrl.Result{}, err
4364
}
4465

45-
obj.Status.Conditions = []metav1.Condition{
46-
{
47-
Type: string(gatewayv1.RouteConditionAccepted),
66+
var (
67+
tctx = provider.NewDefaultTranslateContext(ctx)
68+
ic *networkingv1.IngressClass
69+
err error
70+
)
71+
defer func() {
72+
r.updateStatus(&tls, err)
73+
}()
74+
75+
if ic, err = r.getIngressClass(&tls); err != nil {
76+
return ctrl.Result{}, err
77+
}
78+
if err = r.processIngressClassParameters(ctx, tctx, &tls, ic); err != nil {
79+
return ctrl.Result{}, err
80+
}
81+
if err = r.processApisixTls(ctx, tctx, &tls); err != nil {
82+
return ctrl.Result{}, err
83+
}
84+
if err = r.Provider.Update(ctx, tctx, &tls); err != nil {
85+
err = ReasonError{
86+
Reason: string(apiv2.ConditionReasonSyncFailed),
87+
Message: err.Error(),
88+
}
89+
r.Log.Error(err, "failed to process", "ApisixTls", tls)
90+
return ctrl.Result{}, err
91+
}
92+
93+
return ctrl.Result{}, nil
94+
}
95+
96+
func (r *ApisixTlsReconciler) processApisixTls(ctx context.Context, tc *provider.TranslateContext, tls *apiv2.ApisixTls) error {
97+
// Validate the main TLS secret
98+
if err := r.validateSecret(ctx, tc, tls.Spec.Secret); err != nil {
99+
return ReasonError{
100+
Reason: string(apiv2.ConditionReasonInvalidSpec),
101+
Message: fmt.Sprintf("invalid TLS secret: %s", err.Error()),
102+
}
103+
}
104+
105+
// Validate the client CA secret if mutual TLS is configured
106+
if tls.Spec.Client != nil {
107+
if err := r.validateSecret(ctx, tc, tls.Spec.Client.CASecret); err != nil {
108+
return ReasonError{
109+
Reason: string(apiv2.ConditionReasonInvalidSpec),
110+
Message: fmt.Sprintf("invalid client CA secret: %s", err.Error()),
111+
}
112+
}
113+
}
114+
115+
return nil
116+
}
117+
118+
func (r *ApisixTlsReconciler) validateSecret(ctx context.Context, tc *provider.TranslateContext, secretRef apiv2.ApisixSecret) error {
119+
secretKey := types.NamespacedName{
120+
Namespace: secretRef.Namespace,
121+
Name: secretRef.Name,
122+
}
123+
124+
var secret corev1.Secret
125+
if err := r.Get(ctx, secretKey, &secret); err != nil {
126+
return fmt.Errorf("secret %s not found: %w", secretKey, err)
127+
}
128+
129+
tc.Secrets[secretKey] = &secret
130+
return nil
131+
}
132+
133+
func (r *ApisixTlsReconciler) getIngressClass(tls *apiv2.ApisixTls) (*networkingv1.IngressClass, error) {
134+
if tls.Spec.IngressClassName != "" {
135+
var ic networkingv1.IngressClass
136+
if err := r.Get(context.Background(), types.NamespacedName{Name: tls.Spec.IngressClassName}, &ic); err != nil {
137+
return nil, fmt.Errorf("ingressClass %s not found: %w", tls.Spec.IngressClassName, err)
138+
}
139+
return &ic, nil
140+
}
141+
return r.getDefaultIngressClass()
142+
}
143+
144+
func (r *ApisixTlsReconciler) getDefaultIngressClass() (*networkingv1.IngressClass, error) {
145+
var icList networkingv1.IngressClassList
146+
if err := r.List(context.Background(), &icList); err != nil {
147+
return nil, fmt.Errorf("failed to list IngressClasses: %w", err)
148+
}
149+
150+
for _, ic := range icList.Items {
151+
if ic.Annotations["ingressclass.kubernetes.io/is-default-class"] == "true" {
152+
return &ic, nil
153+
}
154+
}
155+
156+
return nil, fmt.Errorf("no default IngressClass found")
157+
}
158+
159+
func (r *ApisixTlsReconciler) processIngressClassParameters(ctx context.Context, tc *provider.TranslateContext, tls *apiv2.ApisixTls, ingressClass *networkingv1.IngressClass) error {
160+
// Similar to ApisixRoute controller, process IngressClass parameters if needed
161+
// For now, this is a placeholder
162+
return nil
163+
}
164+
165+
func (r *ApisixTlsReconciler) updateStatus(tls *apiv2.ApisixTls, err error) {
166+
var condition metav1.Condition
167+
if err != nil {
168+
if reasonErr, ok := err.(ReasonError); ok {
169+
condition = metav1.Condition{
170+
Type: string(apiv2.ConditionReasonAccepted),
171+
Status: metav1.ConditionFalse,
172+
ObservedGeneration: tls.GetGeneration(),
173+
LastTransitionTime: metav1.Now(),
174+
Reason: reasonErr.Reason,
175+
Message: reasonErr.Message,
176+
}
177+
} else {
178+
condition = metav1.Condition{
179+
Type: string(apiv2.ConditionReasonAccepted),
180+
Status: metav1.ConditionFalse,
181+
ObservedGeneration: tls.GetGeneration(),
182+
LastTransitionTime: metav1.Now(),
183+
Reason: string(apiv2.ConditionReasonSyncFailed),
184+
Message: err.Error(),
185+
}
186+
}
187+
} else {
188+
condition = metav1.Condition{
189+
Type: string(apiv2.ConditionReasonAccepted),
48190
Status: metav1.ConditionTrue,
49-
ObservedGeneration: obj.GetGeneration(),
191+
ObservedGeneration: tls.GetGeneration(),
50192
LastTransitionTime: metav1.Now(),
51-
Reason: string(gatewayv1.RouteReasonAccepted),
52-
},
193+
Reason: string(apiv2.ConditionReasonAccepted),
194+
}
53195
}
54196

55-
if err := r.Status().Update(ctx, &obj); err != nil {
56-
r.Log.Error(err, "failed to update status", "request", req.NamespacedName)
57-
return ctrl.Result{}, err
197+
tls.Status.Conditions = []metav1.Condition{condition}
198+
199+
if err := r.Status().Update(context.Background(), tls); err != nil {
200+
r.Log.Error(err, "failed to update ApisixTls status")
58201
}
202+
}
59203

60-
return ctrl.Result{}, nil
204+
func (r *ApisixTlsReconciler) listApisixTlsForSecret(ctx context.Context, obj client.Object) []reconcile.Request {
205+
secret, ok := obj.(*corev1.Secret)
206+
if !ok {
207+
return nil
208+
}
209+
210+
var tlsList apiv2.ApisixTlsList
211+
if err := r.List(ctx, &tlsList); err != nil {
212+
r.Log.Error(err, "failed to list ApisixTls")
213+
return nil
214+
}
215+
216+
var requests []reconcile.Request
217+
for _, tls := range tlsList.Items {
218+
// Check if this secret is referenced by the TLS resource
219+
if (tls.Spec.Secret.Namespace == secret.Namespace && tls.Spec.Secret.Name == secret.Name) ||
220+
(tls.Spec.Client != nil && tls.Spec.Client.CASecret.Namespace == secret.Namespace && tls.Spec.Client.CASecret.Name == secret.Name) {
221+
requests = append(requests, reconcile.Request{
222+
NamespacedName: utils.NamespacedName(&tls),
223+
})
224+
}
225+
}
226+
227+
return requests
228+
}
229+
230+
func (r *ApisixTlsReconciler) matchesIngressController(obj client.Object) bool {
231+
return true // TODO: implement proper matching logic
61232
}
62233

63234
// SetupWithManager sets up the controller with the Manager.
64235
func (r *ApisixTlsReconciler) SetupWithManager(mgr ctrl.Manager) error {
65236
return ctrl.NewControllerManagedBy(mgr).
66237
For(&apiv2.ApisixTls{}).
238+
WithEventFilter(
239+
predicate.Or(
240+
predicate.GenerationChangedPredicate{},
241+
predicate.NewPredicateFuncs(func(obj client.Object) bool {
242+
_, ok := obj.(*corev1.Secret)
243+
return ok
244+
}),
245+
),
246+
).
247+
Watches(&networkingv1.IngressClass{},
248+
handler.EnqueueRequestsFromMapFunc(r.listApisixTlsForIngressClass),
249+
builder.WithPredicates(
250+
predicate.NewPredicateFuncs(r.matchesIngressController),
251+
),
252+
).
253+
Watches(&corev1.Secret{},
254+
handler.EnqueueRequestsFromMapFunc(r.listApisixTlsForSecret),
255+
).
67256
Named("apisixtls").
68257
Complete(r)
69258
}
259+
260+
func (r *ApisixTlsReconciler) listApisixTlsForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request {
261+
ic, ok := obj.(*networkingv1.IngressClass)
262+
if !ok {
263+
return nil
264+
}
265+
266+
var tlsList apiv2.ApisixTlsList
267+
if err := r.List(ctx, &tlsList); err != nil {
268+
r.Log.Error(err, "failed to list ApisixTls")
269+
return nil
270+
}
271+
272+
var requests []reconcile.Request
273+
for _, tls := range tlsList.Items {
274+
if tls.Spec.IngressClassName == ic.Name {
275+
requests = append(requests, reconcile.Request{
276+
NamespacedName: utils.NamespacedName(&tls),
277+
})
278+
}
279+
}
280+
281+
return requests
282+
}

internal/manager/controllers.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,5 +140,11 @@ func setupControllers(ctx context.Context, mgr manager.Manager, pro provider.Pro
140140
Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixPluginConfig"),
141141
Updater: updater,
142142
},
143+
&controller.ApisixTlsReconciler{
144+
Client: mgr.GetClient(),
145+
Scheme: mgr.GetScheme(),
146+
Log: ctrl.LoggerFrom(ctx).WithName("controllers").WithName("ApisixTls"),
147+
Provider: pro,
148+
},
143149
}, nil
144150
}

internal/provider/adc/adc.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ func (d *adcClient) Update(ctx context.Context, tctx *provider.TranslateContext,
122122
case *apiv2.ApisixGlobalRule:
123123
result, err = d.translator.TranslateApisixGlobalRule(tctx, t.DeepCopy())
124124
resourceTypes = append(resourceTypes, "global_rule")
125+
case *apiv2.ApisixTls:
126+
result, err = d.translator.TranslateApisixTls(tctx, t.DeepCopy())
127+
resourceTypes = append(resourceTypes, "ssl")
125128
}
126129
if err != nil {
127130
return err
@@ -217,6 +220,9 @@ func (d *adcClient) Delete(ctx context.Context, obj client.Object) error {
217220
case *apiv2.ApisixGlobalRule:
218221
resourceTypes = append(resourceTypes, "global_rule")
219222
labels = label.GenLabel(obj)
223+
case *apiv2.ApisixTls:
224+
resourceTypes = append(resourceTypes, "ssl")
225+
labels = label.GenLabel(obj)
220226
}
221227

222228
rk := utils.NamespacedNameKind(obj)

0 commit comments

Comments
 (0)