/
translate_utils.go
169 lines (152 loc) · 6.65 KB
/
translate_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package translator
import (
"fmt"
"reflect"
"github.com/go-logr/logr"
"github.com/kong/go-kong/kong"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/kongstate"
"github.com/kong/kubernetes-ingress-controller/v3/internal/dataplane/translator/subtranslator"
"github.com/kong/kubernetes-ingress-controller/v3/internal/gatewayapi"
"github.com/kong/kubernetes-ingress-controller/v3/internal/store"
)
// -----------------------------------------------------------------------------
// Translate Utilities - Gateway
// -----------------------------------------------------------------------------
// convertGatewayMatchHeadersToKongRouteMatchHeaders takes an input list of Gateway APIs HTTPHeaderMatch
// and converts these header matching rules to the format expected by go-kong.
func convertGatewayMatchHeadersToKongRouteMatchHeaders(headers []gatewayapi.HTTPHeaderMatch) (map[string][]string, error) {
// iterate through each provided header match checking for invalid
// options and otherwise converting to kong type format.
convertedHeaders := make(map[string][]string)
for _, header := range headers {
if _, exists := convertedHeaders[string(header.Name)]; exists {
return nil, fmt.Errorf("multiple header matches for the same header are not allowed: %s",
string(header.Name))
}
if header.Type != nil && *header.Type == gatewayapi.HeaderMatchRegularExpression {
convertedHeaders[string(header.Name)] = []string{kongHeaderRegexPrefix + header.Value}
} else if header.Type == nil || *header.Type == gatewayapi.HeaderMatchExact {
convertedHeaders[string(header.Name)] = []string{header.Value}
} else {
return nil, fmt.Errorf("unknown/unsupported header match type: %s", string(*header.Type))
}
}
return convertedHeaders, nil
}
// getPermittedForReferenceGrantFrom takes a ReferenceGrant From (a namespace, group, and kind) and returns a map
// from a namespace to a slice of ReferenceGrant Tos. When a To is included in the slice, the key namespace has a
// ReferenceGrant with those Tos and the input From.
func getPermittedForReferenceGrantFrom(
from gatewayapi.ReferenceGrantFrom,
grants []*gatewayapi.ReferenceGrant,
) map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo {
allowed := make(map[gatewayapi.Namespace][]gatewayapi.ReferenceGrantTo)
// loop over all From values in all grants. if we find a match, add all Tos to the list of Tos allowed for the
// grant namespace. this technically could add duplicate copies of the Tos if there are duplicate Froms (it makes
// no sense to add them, but it's allowed), but duplicate Tos are harmless (we only care about having at least one
// matching To when checking if a ReferenceGrant allows a reference)
for _, grant := range grants {
for _, otherFrom := range grant.Spec.From {
if reflect.DeepEqual(from, otherFrom) {
allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)] = append(allowed[gatewayapi.Namespace(grant.ObjectMeta.Namespace)], grant.Spec.To...)
}
}
}
return allowed
}
// generateKongServiceFromBackendRefWithName translates backendRefs into a Kong service for use with the
// rules generated from a Gateway APIs route. The service name is provided by the caller.
func generateKongServiceFromBackendRefWithName(
logger logr.Logger,
storer store.Storer,
rules *ingressRules,
serviceName string,
route client.Object,
protocol string,
backendRefs ...gatewayapi.BackendRef,
) (kongstate.Service, error) {
objName := fmt.Sprintf("%s %s/%s",
route.GetObjectKind().GroupVersionKind().String(), route.GetNamespace(), route.GetName())
grants, err := storer.ListReferenceGrants()
if err != nil {
return kongstate.Service{}, fmt.Errorf("could not retrieve ReferenceGrants for %s: %w", objName, err)
}
allowed := getPermittedForReferenceGrantFrom(gatewayapi.ReferenceGrantFrom{
Group: gatewayapi.Group(route.GetObjectKind().GroupVersionKind().Group),
Kind: gatewayapi.Kind(route.GetObjectKind().GroupVersionKind().Kind),
Namespace: gatewayapi.Namespace(route.GetNamespace()),
}, grants)
backends := backendRefsToKongStateBackends(logger, storer, route, backendRefs, allowed)
// the service host needs to be a resolvable name due to legacy logic so we'll
// use the anchor backendRef as the basis for the name
serviceHost := serviceName
// check if the service is already known, and if not create it
service, ok := rules.ServiceNameToServices[serviceName]
if !ok {
service = kongstate.Service{
Service: kong.Service{
Name: kong.String(serviceName),
Host: kong.String(serviceHost),
Protocol: kong.String(protocol),
ConnectTimeout: kong.Int(DefaultServiceTimeout),
ReadTimeout: kong.Int(DefaultServiceTimeout),
WriteTimeout: kong.Int(DefaultServiceTimeout),
Retries: kong.Int(DefaultRetries),
},
Namespace: route.GetNamespace(),
Backends: backends,
Parent: route,
}
}
// In the context of the gateway API conformance tests, if there is no service for the backend,
// the response must have a status code of 500. Since The default behavior of Kong is returning 503
// if there is no backend for a service, we inject a plugin that terminates all requests with 500
// as status code
if len(service.Backends) == 0 && len(backendRefs) != 0 {
if service.Plugins == nil {
service.Plugins = make([]kong.Plugin, 0)
}
service.Plugins = append(service.Plugins, kong.Plugin{
Name: kong.String("request-termination"),
Config: kong.Configuration{
"status_code": 500,
"message": "no existing backendRef provided",
},
})
}
return service, nil
}
// generateKongServiceFromBackendRefWithRuleNumber translates backendRefs for rule ruleNumber into a Kong service for use with the
// rules generated from a Gateway APIs route. The service name is computed from route and ruleNumber by the function.
func generateKongServiceFromBackendRefWithRuleNumber(
logger logr.Logger,
storer store.Storer,
rules *ingressRules,
route client.Object,
ruleNumber int,
protocol string,
backendRefs ...gatewayapi.BackendRef,
) (kongstate.Service, error) {
// the service name needs to uniquely identify this service given it's list of
// one or more backends.
serviceName := fmt.Sprintf("%s.%d", getUniqueKongServiceNameForObject(route), ruleNumber)
return generateKongServiceFromBackendRefWithName(
logger,
storer,
rules,
serviceName,
route,
protocol,
backendRefs...,
)
}
func applyExpressionToIngressRules(result *ingressRules) {
for _, svc := range result.ServiceNameToServices {
for i := range svc.Routes {
subtranslator.ApplyExpressionToL4KongRoute(&svc.Routes[i])
svc.Routes[i].Destinations = nil
svc.Routes[i].SNIs = nil
}
}
}