This repository has been archived by the owner on Nov 8, 2022. It is now read-only.
/
virtualservice_controller.go
170 lines (145 loc) · 6.19 KB
/
virtualservice_controller.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
170
package apim
import (
"context"
"strings"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
networkingv1alpha3 "istio.io/api/networking/v1alpha3"
securityv1beta1 "istio.io/api/security/v1beta1"
istionetworkingv1alpha3 "istio.io/client-go/pkg/apis/networking/v1alpha3"
istiosecurityv1beta1 "istio.io/client-go/pkg/apis/security/v1beta1"
"github.com/go-logr/logr"
"github.com/kuadrant/kuadrant-controller/pkg/common"
"github.com/kuadrant/kuadrant-controller/pkg/log"
"github.com/kuadrant/kuadrant-controller/pkg/mappers"
"github.com/kuadrant/kuadrant-controller/pkg/reconcilers"
)
const VirtualServiceNamePrefix = "vs"
//+kubebuilder:rbac:groups=networking.istio.io,resources=virtualservices,verbs=get;list;watch;update;patch
//+kubebuilder:rbac:groups=security.istio.io,resources=authorizationpolicies,verbs=get;list;watch;create;update;patch;delete
// VirtualServiceReconciler reconciles Istio's VirtualService object
type VirtualServiceReconciler struct {
*reconcilers.BaseReconciler
Scheme *runtime.Scheme
}
func (r *VirtualServiceReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := r.Logger().WithValues("VirtualService", req.NamespacedName)
ctx := logr.NewContext(eventCtx, logger)
virtualService := istionetworkingv1alpha3.VirtualService{}
if err := r.Client().Get(ctx, req.NamespacedName, &virtualService); err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}
logger.Error(err, "failed to get VirtualService")
return ctrl.Result{}, err
}
// TODO(rahulanand16nov): handle VirtualService deletion for AuthPolicy
// check if this virtualservice is to be protected or not.
_, present := virtualService.GetAnnotations()[mappers.KuadrantAuthProviderAnnotation]
if !present {
for _, gateway := range virtualService.Spec.Gateways {
gwKey := common.NamespacedNameToObjectKey(gateway, virtualService.Namespace)
authPolicy := &istiosecurityv1beta1.AuthorizationPolicy{}
authPolicy.SetName(getAuthPolicyName(gwKey.Name, VirtualServiceNamePrefix, virtualService.Name))
authPolicy.SetNamespace(gwKey.Namespace)
common.TagObjectToDelete(authPolicy)
err := r.ReconcileResource(ctx, &istiosecurityv1beta1.AuthorizationPolicy{}, authPolicy, nil)
if err != nil {
logger.Error(err, "failed to delete orphan authorizationpolicy")
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
}
// reconcile authpolicy for the protected virtualservice
if err := r.reconcileAuthPolicy(ctx, logger, &virtualService); err != nil {
logger.Error(err, "failed to reconcile AuthorizationPolicy")
return ctrl.Result{}, err
}
logger.Info("successfully reconciled AuthorizationPolicy")
return ctrl.Result{}, nil
}
func (r *VirtualServiceReconciler) reconcileAuthPolicy(ctx context.Context, logger logr.Logger, vs *istionetworkingv1alpha3.VirtualService) error {
logger.Info("Reconciling AuthorizationPolicy")
// annotation presence is already checked.
providerName := vs.GetAnnotations()[mappers.KuadrantAuthProviderAnnotation]
// TODO(rahulanand16nov): update following to match HTTPRoute controller
// fill out the rules
authToRules := []*securityv1beta1.Rule_To{}
for _, httpRoute := range vs.Spec.Http {
for idx, matchRequest := range httpRoute.Match {
toRule := &securityv1beta1.Rule_To{
Operation: &securityv1beta1.Operation{},
}
toRule.Operation.Hosts = vs.Spec.Hosts
if normalizedURI := normalizeStringMatch(matchRequest.Uri); normalizedURI != "" {
toRule.Operation.Paths = append(toRule.Operation.Paths, normalizedURI)
}
if normalizedMethod := normalizeStringMatch(matchRequest.Method); normalizedMethod != "" {
// Looks like it's case-sensitive:
// https://istio.io/latest/docs/reference/config/security/normalization/#1-method-not-in-upper-case
method := strings.ToUpper(normalizedMethod)
toRule.Operation.Methods = append(toRule.Operation.Methods, method)
}
// If there is only regex stringmatches then we'll have bunch of repeated To rules with
// only same host filled into each. Following make sure only one field like that is present.
operation := toRule.Operation
if len(operation.Paths) == 0 && len(operation.Methods) == 0 && idx > 0 {
continue
}
authToRules = append(authToRules, toRule)
}
}
authPolicySpec := securityv1beta1.AuthorizationPolicy{
Rules: []*securityv1beta1.Rule{{
To: authToRules,
}},
Action: securityv1beta1.AuthorizationPolicy_CUSTOM,
ActionDetail: &securityv1beta1.AuthorizationPolicy_Provider{
Provider: &securityv1beta1.AuthorizationPolicy_ExtensionProvider{
Name: providerName,
},
},
}
for _, gateway := range vs.Spec.Gateways {
gwKey := common.NamespacedNameToObjectKey(gateway, vs.Namespace)
authPolicy := istiosecurityv1beta1.AuthorizationPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: getAuthPolicyName(gwKey.Name, VirtualServiceNamePrefix, vs.Name),
Namespace: gwKey.Namespace,
},
Spec: authPolicySpec,
}
err := r.ReconcileResource(ctx, &istiosecurityv1beta1.AuthorizationPolicy{}, &authPolicy, alwaysUpdateAuthPolicy)
if err != nil && !apierrors.IsAlreadyExists(err) {
logger.Error(err, "ReconcileResource failed to create/update AuthorizationPolicy resource")
return err
}
}
logger.Info("successfully created/updated AuthorizationPolicy resource(s)")
return nil
}
func normalizeStringMatch(sm *networkingv1alpha3.StringMatch) string {
if prefix := sm.GetPrefix(); prefix != "" {
return prefix + "*"
}
if exact := sm.GetExact(); exact != "" {
return exact
}
// Regex string match is not supported because authpolicy doesn't as well.
return ""
}
// SetupWithManager sets up the controller with the Manager.
func (r *VirtualServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
rlpMapper := &rateLimitPolicyMapper{
K8sClient: r.Client(),
Logger: r.Logger(),
}
return ctrl.NewControllerManagedBy(mgr).
For(&istionetworkingv1alpha3.VirtualService{}, builder.WithPredicates(routingPredicate(rlpMapper))).
WithLogger(log.Log). // use base logger, the manager will add prefixes for watched sources
Complete(r)
}