@@ -3,44 +3,117 @@ package ingress
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ awssdk "github.com/aws/aws-sdk-go/aws"
6
7
"github.com/pkg/errors"
8
+ corev1 "k8s.io/api/core/v1"
7
9
networking "k8s.io/api/networking/v1beta1"
10
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
11
+ "k8s.io/apimachinery/pkg/types"
8
12
"k8s.io/apimachinery/pkg/util/intstr"
13
+ "k8s.io/apimachinery/pkg/util/sets"
14
+ "sigs.k8s.io/aws-load-balancer-controller/pkg/algorithm"
9
15
"sigs.k8s.io/aws-load-balancer-controller/pkg/annotations"
16
+ "sigs.k8s.io/controller-runtime/pkg/client"
10
17
)
11
18
12
19
const (
13
20
magicServicePortUseAnnotation = "use-annotation"
21
+
22
+ // the message body of fixed 503 response used when referencing a non-existent Kubernetes service as backend.
23
+ nonExistentBackendServiceMessageBody = "Backend service does not exist"
24
+ // the message body of fixed 503 response used when referencing a non-existent annotation Action as backend.
25
+ nonExistentBackendActionMessageBody = "Backend action does not exist"
26
+ // by default, we tolerate a missing backend service, and use a fixed 503 response instead.
27
+ defaultTolerateNonExistentBackendService = true
28
+ // by default, we tolerate a missing backend action, and use a fixed 503 response instead.
29
+ defaultTolerateNonExistentBackendAction = true
14
30
)
15
31
16
- // An enhanced version of Ingress backend.
17
- // It contains additional routing conditions we parsed from annotations.
32
+ // EnhancedBackend is an enhanced version of Ingress backend.
33
+ // It contains additional routing conditions and authentication configurations we parsed from annotations.
18
34
// Also, when magic string `use-annotation` is specified as backend, the actions will be parsed from annotations as well.
19
35
type EnhancedBackend struct {
20
36
Conditions []RuleCondition
21
37
Action Action
38
+ AuthConfig AuthConfig
39
+ }
40
+
41
+ type EnhancedBackendBuildOptions struct {
42
+ // whether to load backend services
43
+ LoadBackendServices bool
44
+
45
+ // BackendServices contains all services referenced in Action, indexed by service's key.
46
+ // Note: we support to pass BackendServices during backend build, so that we can use the same service snapshot for same service during entire Ingress build process.
47
+ BackendServices map [types.NamespacedName ]* corev1.Service
48
+
49
+ // whether to load auth configuration. when load authConfiguration, LoadBackendServices must be enabled as well.
50
+ LoadAuthConfig bool
51
+ }
52
+
53
+ type EnhancedBackendBuildOption func (opts * EnhancedBackendBuildOptions )
54
+
55
+ func (opts * EnhancedBackendBuildOptions ) ApplyOptions (options ... EnhancedBackendBuildOption ) {
56
+ for _ , option := range options {
57
+ option (opts )
58
+ }
22
59
}
23
60
24
- // EnhancedBackendBuilder is capable of build EnhancedBackend for Ingress backend.
61
+ // WithLoadBackendServices is a option that sets the WithLoadBackendServices and BackendServices.
62
+ func WithLoadBackendServices (loadBackendServices bool , backendServices map [types.NamespacedName ]* corev1.Service ) EnhancedBackendBuildOption {
63
+ return func (opts * EnhancedBackendBuildOptions ) {
64
+ opts .LoadBackendServices = loadBackendServices
65
+ opts .BackendServices = backendServices
66
+ }
67
+ }
68
+
69
+ // WithLoadAuthConfig is a option that sets the LoadAuthConfig.
70
+ func WithLoadAuthConfig (loadAuthConfig bool ) EnhancedBackendBuildOption {
71
+ return func (opts * EnhancedBackendBuildOptions ) {
72
+ opts .LoadAuthConfig = loadAuthConfig
73
+ }
74
+ }
75
+
76
+ // EnhancedBackendBuilder is capable of build EnhancedBackend for Ingress backend.
25
77
type EnhancedBackendBuilder interface {
26
- Build (ctx context.Context , ing * networking.Ingress , backend networking.IngressBackend ) (EnhancedBackend , error )
78
+ Build (ctx context.Context , ing * networking.Ingress , backend networking.IngressBackend , opts ... EnhancedBackendBuildOption ) (EnhancedBackend , error )
27
79
}
28
80
29
81
// NewDefaultEnhancedBackendBuilder constructs new defaultEnhancedBackendBuilder.
30
- func NewDefaultEnhancedBackendBuilder (annotationParser annotations.Parser ) * defaultEnhancedBackendBuilder {
82
+ func NewDefaultEnhancedBackendBuilder (k8sClient client. Client , annotationParser annotations.Parser , authConfigBuilder AuthConfigBuilder ) * defaultEnhancedBackendBuilder {
31
83
return & defaultEnhancedBackendBuilder {
32
- annotationParser : annotationParser ,
84
+ k8sClient : k8sClient ,
85
+ annotationParser : annotationParser ,
86
+ authConfigBuilder : authConfigBuilder ,
87
+
88
+ tolerateNonExistentBackendService : defaultTolerateNonExistentBackendAction ,
89
+ tolerateNonExistentBackendAction : defaultTolerateNonExistentBackendService ,
33
90
}
34
91
}
35
92
36
93
var _ EnhancedBackendBuilder = & defaultEnhancedBackendBuilder {}
37
94
38
95
// default implementation for defaultEnhancedBackendBuilder
39
96
type defaultEnhancedBackendBuilder struct {
40
- annotationParser annotations.Parser
97
+ k8sClient client.Client
98
+ annotationParser annotations.Parser
99
+ authConfigBuilder AuthConfigBuilder
100
+
101
+ // whether to tolerate misconfiguration that used a non-existent backend service.
102
+ // when tolerate, If a single backend service is used and it's non-existent, a fixed 503 response will be used instead.
103
+ tolerateNonExistentBackendService bool
104
+ // whether to tolerate misconfiguration that used a non-existent backend action.
105
+ // when tolerate, If the backend action annotation is non-existent, a fixed 503 response will be used instead.
106
+ tolerateNonExistentBackendAction bool
41
107
}
42
108
43
- func (b * defaultEnhancedBackendBuilder ) Build (ctx context.Context , ing * networking.Ingress , backend networking.IngressBackend ) (EnhancedBackend , error ) {
109
+ func (b * defaultEnhancedBackendBuilder ) Build (ctx context.Context , ing * networking.Ingress , backend networking.IngressBackend , opts ... EnhancedBackendBuildOption ) (EnhancedBackend , error ) {
110
+ buildOpts := EnhancedBackendBuildOptions {
111
+ LoadBackendServices : true ,
112
+ LoadAuthConfig : true ,
113
+ BackendServices : map [types.NamespacedName ]* corev1.Service {},
114
+ }
115
+ buildOpts .ApplyOptions (opts ... )
116
+
44
117
conditions , err := b .buildConditions (ctx , ing .Annotations , backend .ServiceName )
45
118
if err != nil {
46
119
return EnhancedBackend {}, err
@@ -56,9 +129,24 @@ func (b *defaultEnhancedBackendBuilder) Build(ctx context.Context, ing *networki
56
129
action = b .buildActionViaServiceAndServicePort (ctx , backend .ServiceName , backend .ServicePort )
57
130
}
58
131
132
+ var authCfg AuthConfig
133
+ if buildOpts .LoadBackendServices {
134
+ if err := b .loadBackendServices (ctx , & action , ing .Namespace , buildOpts .BackendServices ); err != nil {
135
+ return EnhancedBackend {}, err
136
+ }
137
+
138
+ if buildOpts .LoadAuthConfig {
139
+ authCfg , err = b .buildAuthConfig (ctx , action , ing .Namespace , ing .Annotations , buildOpts .BackendServices )
140
+ if err != nil {
141
+ return EnhancedBackend {}, err
142
+ }
143
+ }
144
+ }
145
+
59
146
return EnhancedBackend {
60
147
Conditions : conditions ,
61
148
Action : action ,
149
+ AuthConfig : authCfg ,
62
150
}, nil
63
151
}
64
152
@@ -77,34 +165,64 @@ func (b *defaultEnhancedBackendBuilder) buildConditions(_ context.Context, ingAn
77
165
return conditions , nil
78
166
}
79
167
80
- func (b * defaultEnhancedBackendBuilder ) buildActionViaAnnotation (_ context.Context , ingAnnotation map [string ]string , svcName string ) (Action , error ) {
168
+ // buildActionViaAnnotation will build the backend action specified via actions annotation.
169
+ func (b * defaultEnhancedBackendBuilder ) buildActionViaAnnotation (ctx context.Context , ingAnnotation map [string ]string , svcName string ) (Action , error ) {
81
170
action := Action {}
82
171
annotationKey := fmt .Sprintf ("actions.%v" , svcName )
83
172
exists , err := b .annotationParser .ParseJSONAnnotation (annotationKey , & action , ingAnnotation )
84
173
if err != nil {
85
174
return Action {}, err
86
175
}
87
176
if ! exists {
177
+ if b .tolerateNonExistentBackendAction {
178
+ return b .build503ResponseAction (nonExistentBackendActionMessageBody ), nil
179
+ }
88
180
return Action {}, errors .Errorf ("missing %v configuration" , annotationKey )
89
181
}
90
182
if err := action .validate (); err != nil {
91
183
return Action {}, err
92
184
}
185
+ b .normalizeSimplifiedSchemaForwardAction (ctx , & action )
186
+ b .normalizeServicePortForBackwardsCompatibility (ctx , & action )
187
+ return action , nil
188
+ }
93
189
94
- // normalize forward action via TargetGroupARN.
95
- if action .Type == ActionTypeForward && action .TargetGroupARN != nil {
96
- action .ForwardConfig = & ForwardActionConfig {
190
+ // buildActionViaServiceAndServicePort will build the backend Action that forward to specified Kubernetes Service.
191
+ func (b * defaultEnhancedBackendBuilder ) buildActionViaServiceAndServicePort (_ context.Context , svcName string , svcPort intstr.IntOrString ) Action {
192
+ action := Action {
193
+ Type : ActionTypeForward ,
194
+ ForwardConfig : & ForwardActionConfig {
97
195
TargetGroups : []TargetGroupTuple {
98
196
{
99
- TargetGroupARN : action .TargetGroupARN ,
197
+ ServiceName : & svcName ,
198
+ ServicePort : & svcPort ,
199
+ },
200
+ },
201
+ },
202
+ }
203
+ return action
204
+ }
205
+
206
+ // normalizeSimplifiedSchemaForwardAction will normalize to the advanced schema for forward action to share common processing logic.
207
+ // we support a simplified schema in action annotation when configure forward to a single TargetGroup.
208
+ func (b * defaultEnhancedBackendBuilder ) normalizeSimplifiedSchemaForwardAction (_ context.Context , action * Action ) {
209
+ if action .Type == ActionTypeForward && action .TargetGroupARN != nil {
210
+ * action = Action {
211
+ Type : ActionTypeForward ,
212
+ ForwardConfig : & ForwardActionConfig {
213
+ TargetGroups : []TargetGroupTuple {
214
+ {
215
+ TargetGroupARN : action .TargetGroupARN ,
216
+ },
100
217
},
101
218
},
102
219
}
103
- action .TargetGroupARN = nil
104
220
}
221
+ }
105
222
106
- // normalize servicePort to be int type if possible.
107
- // this is for backwards-compatibility with old AWSALBIngressController, where ServicePort is defined as Type string.
223
+ // normalizeServicePortForBackwardsCompatibility will normalize servicePort to be int type if possible.
224
+ // this is for backwards-compatibility with old AWSALBIngressController, where ServicePort is defined as Type string.
225
+ func (b * defaultEnhancedBackendBuilder ) normalizeServicePortForBackwardsCompatibility (_ context.Context , action * Action ) {
108
226
if action .Type == ActionTypeForward && action .ForwardConfig != nil {
109
227
for _ , tgt := range action .ForwardConfig .TargetGroups {
110
228
if tgt .ServicePort != nil {
@@ -113,20 +231,65 @@ func (b *defaultEnhancedBackendBuilder) buildActionViaAnnotation(_ context.Conte
113
231
}
114
232
}
115
233
}
234
+ }
116
235
117
- return action , nil
236
+ // loadBackendServices will load referenced backend services into backendServices.
237
+ // when tolerateNonExistentBackendService==true, and forward to a single non-existent Kubernetes Service, a fixed 503 response instead.
238
+ func (b * defaultEnhancedBackendBuilder ) loadBackendServices (ctx context.Context , action * Action , namespace string ,
239
+ backendServices map [types.NamespacedName ]* corev1.Service ) error {
240
+ if action .Type == ActionTypeForward && action .ForwardConfig != nil {
241
+ svcNames := sets .NewString ()
242
+ for _ , tgt := range action .ForwardConfig .TargetGroups {
243
+ if tgt .ServiceName != nil {
244
+ svcNames .Insert (awssdk .StringValue (tgt .ServiceName ))
245
+ }
246
+ }
247
+ forwardToSingleSvc := (len (action .ForwardConfig .TargetGroups ) == 1 ) && (svcNames .Len () == 1 )
248
+ tolerateNonExistentBackendService := b .tolerateNonExistentBackendService && forwardToSingleSvc
249
+ for svcName := range svcNames {
250
+ svcKey := types.NamespacedName {Namespace : namespace , Name : svcName }
251
+ if _ , ok := backendServices [svcKey ]; ok {
252
+ continue
253
+ }
254
+
255
+ svc := & corev1.Service {}
256
+ if err := b .k8sClient .Get (ctx , svcKey , svc ); err != nil {
257
+ if apierrors .IsNotFound (err ) && tolerateNonExistentBackendService {
258
+ * action = b .build503ResponseAction (nonExistentBackendServiceMessageBody )
259
+ return nil
260
+ }
261
+ return err
262
+ }
263
+ backendServices [svcKey ] = svc
264
+ }
265
+ }
266
+ return nil
118
267
}
119
268
120
- func (b * defaultEnhancedBackendBuilder ) buildActionViaServiceAndServicePort (_ context.Context , svcName string , svcPort intstr.IntOrString ) Action {
269
+ func (b * defaultEnhancedBackendBuilder ) buildAuthConfig (ctx context.Context , action Action , namespace string , ingAnnotation map [string ]string , backendServices map [types.NamespacedName ]* corev1.Service ) (AuthConfig , error ) {
270
+ svcAndIngAnnotations := ingAnnotation
271
+ // when forward to a single Service, the auth annotations on that Service will be merged in.
272
+ if action .Type == ActionTypeForward &&
273
+ action .ForwardConfig != nil &&
274
+ len (action .ForwardConfig .TargetGroups ) == 1 &&
275
+ action .ForwardConfig .TargetGroups [0 ].ServiceName != nil {
276
+ svcName := awssdk .StringValue (action .ForwardConfig .TargetGroups [0 ].ServiceName )
277
+ svcKey := types.NamespacedName {Namespace : namespace , Name : svcName }
278
+ svc := backendServices [svcKey ]
279
+ svcAndIngAnnotations = algorithm .MergeStringMap (svc .Annotations , svcAndIngAnnotations )
280
+ }
281
+
282
+ return b .authConfigBuilder .Build (ctx , svcAndIngAnnotations )
283
+ }
284
+
285
+ // build503ResponseAction generates a 503 fixed response action when forward to a single non-existent Kubernetes Service.
286
+ func (b * defaultEnhancedBackendBuilder ) build503ResponseAction (messageBody string ) Action {
121
287
return Action {
122
- Type : ActionTypeForward ,
123
- ForwardConfig : & ForwardActionConfig {
124
- TargetGroups : []TargetGroupTuple {
125
- {
126
- ServiceName : & svcName ,
127
- ServicePort : & svcPort ,
128
- },
129
- },
288
+ Type : ActionTypeFixedResponse ,
289
+ FixedResponseConfig : & FixedResponseActionConfig {
290
+ ContentType : awssdk .String ("text/plain" ),
291
+ StatusCode : "503" ,
292
+ MessageBody : awssdk .String (messageBody ),
130
293
},
131
294
}
132
295
}
0 commit comments