diff --git a/api/v1alpha1/dnspolicy_types.go b/api/v1alpha1/dnspolicy_types.go index 26db6149f..20f5116cb 100644 --- a/api/v1alpha1/dnspolicy_types.go +++ b/api/v1alpha1/dnspolicy_types.go @@ -68,6 +68,11 @@ type DNSPolicySpec struct { // +kubebuilder:validation:MaxItems=1 // +kubebuilder:validation:MinItems=1 ProviderRefs []dnsv1alpha1.ProviderRef `json:"providerRefs"` + + // ExcludeAddresses is a list of addresses (either hostnames, CIDR or IPAddresses) that DNSPolicy should not use as values in the configured DNS provider records. The default is to allow all addresses configured in the Gateway DNSPolicy is targeting + // +optional + // +kubebuilder:validation:MaxItems=20 + ExcludeAddresses []string `json:"excludeAddresses,omitempty"` } type LoadBalancingSpec struct { @@ -125,6 +130,9 @@ type DNSPolicyStatus struct { // +optional RecordConditions map[string][]metav1.Condition `json:"recordConditions,omitempty"` + // TotalRecords records the total number of individual DNSRecords managed by this DNSPolicy + // +optional + TotalRecords int32 `json:"totalRecords,omitempty"` } func (s *DNSPolicyStatus) GetConditions() []metav1.Condition { @@ -251,6 +259,13 @@ func (p *DNSPolicy) WithProviderSecret(s corev1.Secret) *DNSPolicy { }) } +//excludeAddresses + +func (p *DNSPolicy) WithExcludeAddresses(excluded []string) *DNSPolicy { + p.Spec.ExcludeAddresses = excluded + return p +} + //TargetRef func (p *DNSPolicy) WithTargetGateway(gwName string) *DNSPolicy { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 51b71361a..53a868b6c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -146,6 +146,11 @@ func (in *DNSPolicySpec) DeepCopyInto(out *DNSPolicySpec) { *out = make([]apiv1alpha1.ProviderRef, len(*in)) copy(*out, *in) } + if in.ExcludeAddresses != nil { + in, out := &in.ExcludeAddresses, &out.ExcludeAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DNSPolicySpec. diff --git a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml index 7dd5ef599..46dd88642 100644 --- a/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml +++ b/bundle/manifests/kuadrant-operator.clusterserviceversion.yaml @@ -106,7 +106,7 @@ metadata: capabilities: Basic Install categories: Integration & Delivery containerImage: quay.io/kuadrant/kuadrant-operator:latest - createdAt: "2024-10-04T07:47:31Z" + createdAt: "2024-10-04T10:00:39Z" description: A Kubernetes Operator to manage the lifecycle of the Kuadrant system operators.operatorframework.io/builder: operator-sdk-v1.32.0 operators.operatorframework.io/project_layout: go.kubebuilder.io/v3 diff --git a/bundle/manifests/kuadrant.io_dnspolicies.yaml b/bundle/manifests/kuadrant.io_dnspolicies.yaml index 3a2ddd777..852e586a9 100644 --- a/bundle/manifests/kuadrant.io_dnspolicies.yaml +++ b/bundle/manifests/kuadrant.io_dnspolicies.yaml @@ -66,6 +66,15 @@ spec: spec: description: DNSPolicySpec defines the desired state of DNSPolicy properties: + excludeAddresses: + description: ExcludeAddresses is a list of addresses (either hostnames, + CIDR or IPAddresses) that DNSPolicy should not use as values in + the configured DNS provider records. The default is to allow all + addresses configured in the Gateway DNSPolicy is targeting + items: + type: string + maxItems: 20 + type: array healthCheck: description: |- HealthCheckSpec configures health checks in the DNS provider. @@ -511,6 +520,11 @@ spec: type: object type: array type: object + totalRecords: + description: TotalRecords records the total number of individual DNSRecords + managed by this DNSPolicy + format: int32 + type: integer type: object type: object served: true diff --git a/charts/kuadrant-operator/templates/manifests.yaml b/charts/kuadrant-operator/templates/manifests.yaml index 09c5293cd..4de7b775e 100644 --- a/charts/kuadrant-operator/templates/manifests.yaml +++ b/charts/kuadrant-operator/templates/manifests.yaml @@ -13266,6 +13266,15 @@ spec: spec: description: DNSPolicySpec defines the desired state of DNSPolicy properties: + excludeAddresses: + description: ExcludeAddresses is a list of addresses (either hostnames, + CIDR or IPAddresses) that DNSPolicy should not use as values in + the configured DNS provider records. The default is to allow all + addresses configured in the Gateway DNSPolicy is targeting + items: + type: string + maxItems: 20 + type: array healthCheck: description: |- HealthCheckSpec configures health checks in the DNS provider. @@ -13711,6 +13720,11 @@ spec: type: object type: array type: object + totalRecords: + description: TotalRecords records the total number of individual DNSRecords + managed by this DNSPolicy + format: int32 + type: integer type: object type: object served: true diff --git a/config/crd/bases/kuadrant.io_dnspolicies.yaml b/config/crd/bases/kuadrant.io_dnspolicies.yaml index 32aaf6407..2ae5aca3e 100644 --- a/config/crd/bases/kuadrant.io_dnspolicies.yaml +++ b/config/crd/bases/kuadrant.io_dnspolicies.yaml @@ -65,6 +65,15 @@ spec: spec: description: DNSPolicySpec defines the desired state of DNSPolicy properties: + excludeAddresses: + description: ExcludeAddresses is a list of addresses (either hostnames, + CIDR or IPAddresses) that DNSPolicy should not use as values in + the configured DNS provider records. The default is to allow all + addresses configured in the Gateway DNSPolicy is targeting + items: + type: string + maxItems: 20 + type: array healthCheck: description: |- HealthCheckSpec configures health checks in the DNS provider. @@ -510,6 +519,11 @@ spec: type: object type: array type: object + totalRecords: + description: TotalRecords records the total number of individual DNSRecords + managed by this DNSPolicy + format: int32 + type: integer type: object type: object served: true diff --git a/controllers/dns_helper.go b/controllers/dns_helper.go index a2aac7432..152194945 100644 --- a/controllers/dns_helper.go +++ b/controllers/dns_helper.go @@ -3,6 +3,8 @@ package controllers import ( "context" "fmt" + "net" + "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -97,6 +99,7 @@ func (dh *dnsHelper) deleteDNSRecordForListener(ctx context.Context, owner metav // GatewayWrapper is a wrapper for gateway to implement interface form the builder type GatewayWrapper struct { *gatewayapiv1.Gateway + excludedAddresses []string } func NewGatewayWrapper(gateway *gatewayapiv1.Gateway) *GatewayWrapper { @@ -113,3 +116,36 @@ func (g GatewayWrapper) GetAddresses() []builder.TargetAddress { } return addresses } + +func (g *GatewayWrapper) RemoveExcludedStatusAddresses(p *v1alpha1.DNSPolicy) error { + g.excludedAddresses = p.Spec.ExcludeAddresses + newAddresses := []gatewayapiv1.GatewayStatusAddress{} + for _, address := range g.Gateway.Status.Addresses { + found := false + for _, exclude := range p.Spec.ExcludeAddresses { + //Only a CIDR will have / in the address so attempt to parse fail if not valid + if strings.Contains(exclude, "/") { + _, network, err := net.ParseCIDR(exclude) + if err != nil { + return fmt.Errorf("could not parse the CIDR from the excludeAddresses field %w", err) + } + ip := net.ParseIP(address.Value) + // only check addresses that are actually IPs + if ip != nil && network.Contains(ip) { + found = true + break + } + } + if exclude == address.Value { + found = true + break + } + } + if !found { + newAddresses = append(newAddresses, address) + } + } + // setting this in memory only wont be saved to actual gateway + g.Status.Addresses = newAddresses + return nil +} diff --git a/controllers/dns_helper_test.go b/controllers/dns_helper_test.go new file mode 100644 index 000000000..39e26bb70 --- /dev/null +++ b/controllers/dns_helper_test.go @@ -0,0 +1,167 @@ +package controllers_test + +import ( + "testing" + + gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + + "github.com/kuadrant/kuadrant-operator/api/v1alpha1" + "github.com/kuadrant/kuadrant-operator/controllers" +) + +func TestRemoveExcludedStatusAddresses(t *testing.T) { + ipaddress := gatewayapiv1.IPAddressType + hostaddress := gatewayapiv1.HostnameAddressType + testCases := []struct { + Name string + Gateway *gatewayapiv1.Gateway + DNSPolicy *v1alpha1.DNSPolicy + Validate func(t *testing.T, g *gatewayapiv1.GatewayStatus) + ExpectErr bool + }{ + { + Name: "ensure addresses in ingore are are removed from status", + Gateway: &gatewayapiv1.Gateway{ + Status: gatewayapiv1.GatewayStatus{ + Addresses: []gatewayapiv1.GatewayStatusAddress{ + { + Type: &ipaddress, + Value: "1.1.1.1", + }, + { + Type: &hostaddress, + Value: "example.com", + }, + }, + }, + }, + DNSPolicy: &v1alpha1.DNSPolicy{ + Spec: v1alpha1.DNSPolicySpec{ + ExcludeAddresses: []string{ + "1.1.1.1", + }, + }, + }, + Validate: func(t *testing.T, g *gatewayapiv1.GatewayStatus) { + if len(g.Addresses) != 1 { + t.Fatalf("expected a single address but got %v ", len(g.Addresses)) + } + for _, addr := range g.Addresses { + if addr.Value == "1.1.1.1" { + t.Fatalf("did not expect address %s to be present", "1.1.1.1") + } + } + }, + }, + { + Name: "ensure all addresses if nothing ignored", + Gateway: &gatewayapiv1.Gateway{ + Status: gatewayapiv1.GatewayStatus{ + Addresses: []gatewayapiv1.GatewayStatusAddress{ + { + Type: &ipaddress, + Value: "1.1.1.1", + }, + { + Type: &hostaddress, + Value: "example.com", + }, + }, + }, + }, + DNSPolicy: &v1alpha1.DNSPolicy{ + Spec: v1alpha1.DNSPolicySpec{ + ExcludeAddresses: []string{}, + }, + }, + Validate: func(t *testing.T, g *gatewayapiv1.GatewayStatus) { + if len(g.Addresses) != 2 { + t.Fatalf("expected a both address but got %v ", len(g.Addresses)) + } + }, + }, + { + Name: "ensure addresses removed if CIDR is set and hostname", + Gateway: &gatewayapiv1.Gateway{ + Status: gatewayapiv1.GatewayStatus{ + Addresses: []gatewayapiv1.GatewayStatusAddress{ + { + Type: &ipaddress, + Value: "1.1.1.1", + }, + { + Type: &hostaddress, + Value: "example.com", + }, + { + Type: &ipaddress, + Value: "81.17.21.22", + }, + }, + }, + }, + DNSPolicy: &v1alpha1.DNSPolicy{ + Spec: v1alpha1.DNSPolicySpec{ + ExcludeAddresses: []string{ + "1.1.0.0/16", + "10.0.0.1/32", + "example.com", + }, + }, + }, + Validate: func(t *testing.T, g *gatewayapiv1.GatewayStatus) { + if len(g.Addresses) != 1 { + t.Fatalf("expected only a single address but got %v %v ", len(g.Addresses), g.Addresses) + } + if g.Addresses[0].Value != "81.17.21.22" { + t.Fatalf("expected the only remaining address to be 81.17.21.22 but got %s", g.Addresses[0].Value) + } + }, + }, + { + Name: "ensure invalid CIDR causes error", + ExpectErr: true, + Gateway: &gatewayapiv1.Gateway{ + Status: gatewayapiv1.GatewayStatus{ + Addresses: []gatewayapiv1.GatewayStatusAddress{ + { + Type: &ipaddress, + Value: "1.1.1.1", + }, + { + Type: &hostaddress, + Value: "example.com", + }, + { + Type: &ipaddress, + Value: "81.17.21.22", + }, + }, + }, + }, + DNSPolicy: &v1alpha1.DNSPolicy{ + Spec: v1alpha1.DNSPolicySpec{ + ExcludeAddresses: []string{ + "1.1.0.0/161", + "example.com", + }, + }, + }, + Validate: func(t *testing.T, g *gatewayapiv1.GatewayStatus) {}, + }, + } + + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + gw := controllers.NewGatewayWrapper(tc.Gateway) + err := gw.RemoveExcludedStatusAddresses(tc.DNSPolicy) + if err != nil && !tc.ExpectErr { + t.Fatalf("unexpected error %s", err) + } + if tc.ExpectErr && err == nil { + t.Fatalf("expected an error but got none") + } + tc.Validate(t, &gw.Status) + }) + } +} diff --git a/controllers/dnspolicy_controller.go b/controllers/dnspolicy_controller.go index 4147276d1..749c6efc6 100644 --- a/controllers/dnspolicy_controller.go +++ b/controllers/dnspolicy_controller.go @@ -133,7 +133,7 @@ func (r *DNSPolicyReconciler) reconcileResources(ctx context.Context, dnsPolicy } if err = r.reconcileDNSRecords(ctx, dnsPolicy, gatewayDiffObj); err != nil { - return fmt.Errorf("reconcile DNSRecords error %w", err) + return fmt.Errorf("error reconciling DNSRecords %w", err) } // set direct back ref - i.e. claim the target network object as taken asap diff --git a/controllers/dnspolicy_dnsrecords.go b/controllers/dnspolicy_dnsrecords.go index 3606d1f9a..0c4d28a99 100644 --- a/controllers/dnspolicy_dnsrecords.go +++ b/controllers/dnspolicy_dnsrecords.go @@ -21,6 +21,11 @@ import ( "github.com/kuadrant/kuadrant-operator/pkg/library/utils" ) +var ( + ErrNoRoutes = fmt.Errorf("no routes attached to any gateway listeners") + ErrNoAddresses = fmt.Errorf("no valid status addresses to use on gateway") +) + func (r *DNSPolicyReconciler) reconcileDNSRecords(ctx context.Context, dnsPolicy *v1alpha1.DNSPolicy, gwDiffObj *reconcilerutils.GatewayDiffs) error { log := crlog.FromContext(ctx) @@ -36,7 +41,7 @@ func (r *DNSPolicyReconciler) reconcileDNSRecords(ctx context.Context, dnsPolicy for _, gw := range append(gwDiffObj.GatewaysWithValidPolicyRef, gwDiffObj.GatewaysMissingPolicyRef...) { log.V(1).Info("reconcileDNSRecords: gateway with valid or missing policy ref", "key", gw.Key()) if err := r.reconcileGatewayDNSRecords(ctx, gw.Gateway, dnsPolicy); err != nil { - return fmt.Errorf("error reconciling dns records for gateway %v: %w", gw.Gateway.Name, err) + return fmt.Errorf("reconciling dns records for gateway %v: error %w", gw.Gateway.Name, err) } } return nil @@ -48,14 +53,27 @@ func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, ga if err != nil { return fmt.Errorf("failed to generate cluster ID: %w", err) } + gw := gateway.DeepCopy() + gatewayWrapper := NewGatewayWrapper(gw) + // modify the status addresses based on any that need to be excluded + if err := gatewayWrapper.RemoveExcludedStatusAddresses(dnsPolicy); err != nil { + return fmt.Errorf("failed to reconcile gateway dns records error: %w ", err) + } - if err = r.dnsHelper.removeDNSForDeletedListeners(ctx, gateway); err != nil { + if err = r.dnsHelper.removeDNSForDeletedListeners(ctx, gw); err != nil { log.V(3).Info("error removing DNS for deleted listeners") return err } - log.V(3).Info("checking gateway for attached routes ", "gateway", gateway.Name) - for _, listener := range gateway.Spec.Listeners { + log.V(3).Info("checking gateway for attached routes ", "gateway", gw.Name) + var totalPolicyRecords int32 + var gatewayHasAttachedRoutes = false + + if len(gw.Status.Addresses) == 0 { + return ErrNoAddresses + } + + for _, listener := range gw.Spec.Listeners { if listener.Hostname == nil || *listener.Hostname == "" { log.Info("skipping listener no hostname assigned", "listener", listener.Name, "in ns ", gateway.Namespace) continue @@ -68,16 +86,19 @@ func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, ga } } + if hasAttachedRoute { + gatewayHasAttachedRoutes = true + } if !hasAttachedRoute { // delete record log.V(1).Info("no cluster gateways, deleting DNS record", " for listener ", listener.Name) - if err := r.dnsHelper.deleteDNSRecordForListener(ctx, gateway, listener); client.IgnoreNotFound(err) != nil { + if err := r.dnsHelper.deleteDNSRecordForListener(ctx, gw, listener); client.IgnoreNotFound(err) != nil { return fmt.Errorf("failed to delete dns record for listener %s : %w", listener.Name, err) } continue } - dnsRecord, err := r.desiredDNSRecord(gateway, clusterID, dnsPolicy, listener) + dnsRecord, err := r.desiredDNSRecord(gw, clusterID, dnsPolicy, listener) if err != nil { return err } @@ -87,11 +108,25 @@ func (r *DNSPolicyReconciler) reconcileGatewayDNSRecords(ctx context.Context, ga return err } + if len(dnsRecord.Spec.Endpoints) == 0 { + log.V(1).Info("no endpoint addresses for DNSRecord ", "removing any records for listener", listener) + if err := r.dnsHelper.deleteDNSRecordForListener(ctx, gatewayWrapper, listener); client.IgnoreNotFound(err) != nil { + return err + } + //return fmt.Errorf("no valid addresses for DNSRecord endpoints. Check allowedAddresses") + continue + } + err = r.ReconcileResource(ctx, &kuadrantdnsv1alpha1.DNSRecord{}, dnsRecord, dnsRecordBasicMutator) if err != nil && !apierrors.IsAlreadyExists(err) { log.Error(err, "ReconcileResource failed to create/update DNSRecord resource") return err } + totalPolicyRecords++ + } + dnsPolicy.Status.TotalRecords = totalPolicyRecords + if !gatewayHasAttachedRoutes { + return ErrNoRoutes } return nil } diff --git a/controllers/dnspolicy_status.go b/controllers/dnspolicy_status.go index b9d0cbc64..6c1147232 100644 --- a/controllers/dnspolicy_status.go +++ b/controllers/dnspolicy_status.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/gateway-api/apis/v1alpha2" @@ -92,9 +93,13 @@ func (r *DNSPolicyReconciler) calculateStatus(ctx context.Context, dnsPolicy *v1 // Copy initial conditions. Otherwise, status will always be updated Conditions: slices.Clone(dnsPolicy.Status.Conditions), ObservedGeneration: dnsPolicy.Status.ObservedGeneration, + TotalRecords: dnsPolicy.Status.TotalRecords, + } + acceptedCond := kuadrant.AcceptedCondition(dnsPolicy, nil) + if !(errors.Is(specErr, ErrNoAddresses) || errors.Is(specErr, ErrNoRoutes)) { + acceptedCond = kuadrant.AcceptedCondition(dnsPolicy, specErr) } - acceptedCond := kuadrant.AcceptedCondition(dnsPolicy, specErr) meta.SetStatusCondition(&newStatus.Conditions, *acceptedCond) // Do not set enforced condition if Accepted condition is false @@ -102,36 +107,53 @@ func (r *DNSPolicyReconciler) calculateStatus(ctx context.Context, dnsPolicy *v1 meta.RemoveStatusCondition(&newStatus.Conditions, string(kuadrant.PolicyConditionEnforced)) return newStatus } + var enforcedCondition = kuadrant.EnforcedCondition(dnsPolicy, nil, true) + recordList, err := r.filteredRecordList(ctx, dnsPolicy) + if err != nil { + enforcedCondition = kuadrant.EnforcedCondition(dnsPolicy, kuadrant.NewErrUnknown("DNSPolicy", err), false) + meta.SetStatusCondition(&newStatus.Conditions, *enforcedCondition) + return newStatus + } - recordsList := &kuadrantdnsv1alpha1.DNSRecordList{} + enforcedCondition = r.enforcedCondition(recordList, dnsPolicy) - var enforcedCondition *metav1.Condition - if err := r.Client().List(ctx, recordsList); err != nil { - enforcedCondition = kuadrant.EnforcedCondition(dnsPolicy, kuadrant.NewErrUnknown(dnsPolicy.Kind(), err), false) - } else { - // leave only records controlled by the policy - recordsList.Items = utils.Filter(recordsList.Items, func(record kuadrantdnsv1alpha1.DNSRecord) bool { - for _, reference := range record.GetOwnerReferences() { - if reference.Controller != nil && *reference.Controller && reference.Name == dnsPolicy.Name && reference.UID == dnsPolicy.UID { - return true - } - } - return false - }) - - enforcedCondition = r.enforcedCondition(recordsList, dnsPolicy) + // add some additional user friendly context + if errors.Is(specErr, ErrNoAddresses) && !strings.Contains(enforcedCondition.Message, ErrNoAddresses.Error()) { + enforcedCondition.Message = fmt.Sprintf("%s : %s", enforcedCondition.Message, ErrNoAddresses.Error()) + } + if errors.Is(specErr, ErrNoRoutes) && !strings.Contains(enforcedCondition.Message, ErrNoRoutes.Error()) { + enforcedCondition.Message = fmt.Sprintf("%s : %s", enforcedCondition.Message, ErrNoRoutes) } meta.SetStatusCondition(&newStatus.Conditions, *enforcedCondition) - propagateRecordConditions(recordsList, newStatus) + propagateRecordConditions(recordList, newStatus) return newStatus } +func (r *DNSPolicyReconciler) filteredRecordList(ctx context.Context, dnsPolicy *v1alpha1.DNSPolicy) (*kuadrantdnsv1alpha1.DNSRecordList, error) { + recordsList := &kuadrantdnsv1alpha1.DNSRecordList{} + if err := r.Client().List(ctx, recordsList, &client.ListOptions{Namespace: dnsPolicy.Namespace}); err != nil { + return nil, err + } + // filter down to records controlled by the policy + recordsList.Items = utils.Filter(recordsList.Items, func(record kuadrantdnsv1alpha1.DNSRecord) bool { + for _, reference := range record.GetOwnerReferences() { + if reference.Controller != nil && *reference.Controller && reference.Name == dnsPolicy.Name && reference.UID == dnsPolicy.UID { + return true + } + } + return false + }) + return recordsList, nil +} + func (r *DNSPolicyReconciler) enforcedCondition(recordsList *kuadrantdnsv1alpha1.DNSRecordList, dnsPolicy *v1alpha1.DNSPolicy) *metav1.Condition { // there are no controlled DNS records present if len(recordsList.Items) == 0 { - return kuadrant.EnforcedCondition(dnsPolicy, kuadrant.NewErrUnknown(dnsPolicy.Kind(), errors.New("policy is not enforced on any DNSRecord: no routes attached for listeners")), false) + cond := kuadrant.EnforcedCondition(dnsPolicy, nil, true) + cond.Message = "DNSPolicy has been successfully enforced : no DNSRecords created based on policy and gateway configuration" + return cond } // filter not ready records @@ -139,8 +161,8 @@ func (r *DNSPolicyReconciler) enforcedCondition(recordsList *kuadrantdnsv1alpha1 return meta.IsStatusConditionFalse(record.Status.Conditions, string(kuadrantdnsv1alpha1.ConditionTypeReady)) }) - // none of the records are ready - if len(notReadyRecords) == len(recordsList.Items) { + // if there are records and none of the records are ready + if len(recordsList.Items) > 0 && len(notReadyRecords) == len(recordsList.Items) { return kuadrant.EnforcedCondition(dnsPolicy, kuadrant.NewErrUnknown(dnsPolicy.Kind(), errors.New("policy is not enforced on any DNSRecord: not a single DNSRecord is ready")), false) } diff --git a/doc/reference/dnspolicy.md b/doc/reference/dnspolicy.md index 742077023..2e60d6172 100644 --- a/doc/reference/dnspolicy.md +++ b/doc/reference/dnspolicy.md @@ -2,6 +2,7 @@ - [DNSPolicy](#DNSPolicy) - [DNSPolicySpec](#dnspolicyspec) + - [excludeAddresses]() - [ProviderRefs](#providerRefs) - [HealthCheckSpec](#healthcheckspec) - [LoadBalancingSpec](#loadbalancingspec) @@ -40,9 +41,16 @@ | `name` | String | Yes | Name of the secret in the same namespace that contains the provider credentials +## ExcludeAddresses +| **Field** | **Type** | **Required** | **Description** | +|------------|----------|:------------:|----------------------------------------------------------------------------------------| +| `excludeAddresses` | []String | No | set of hostname, CIDR or IP Addresses to exclude from the DNS Provider + ## HealthCheckSpec -| **Field** | **Type** | **Required** | **Description** | +| **Field** | **Type** | **Required** | **Description** | +|------------|----------|:------------:|----------------------------------------------------------------------------------------| +| `name` | String | Yes | Name of the secret in the same namespace that contains the provider credentials |--------------------|------------|:------------:|-----------------------------------------------------------------------------------------------------------| | `endpoint` | String | Yes | Endpoint is the path to append to the host to reach the expected health check | | `port` | Number | Yes | Port to connect to the host on | diff --git a/doc/user-guides/dns-excluding-specific-addresses.md b/doc/user-guides/dns-excluding-specific-addresses.md new file mode 100644 index 000000000..6fe2b91c1 --- /dev/null +++ b/doc/user-guides/dns-excluding-specific-addresses.md @@ -0,0 +1,34 @@ +## Excluding specific addresses from being published + +By default DNSPolicy takes all the addresses published in the status of the Gateway it is targeting and use these values in the DNSRecord it publishes to chosen DNS provider. + +There could be cases where you have an address assigned to a gateway that you do not want to publish to a DNS provider, but you still want DNSPolicy to publish records for other addresses. + +To prevent a gateway address being published to the DNS provider, you can set the `excludeAddresses` field in the DNSPolicy resource targeting the gateway. The `excludeAddresses` field can be set to a hostname, an IPAddress or a CIDR. + +Below is an example of a DNSPolicy excluding a hostname: + +``` +apiVersion: kuadrant.io/v1alpha1 +kind: DNSPolicy +metadata: + name: prod-web + namespace: ${DNSPOLICY_NAMESPACE} +spec: + targetRef: + name: prod-web-istio + group: gateway.networking.k8s.io + kind: Gateway + providerRefs: + - name: aws-credentials + loadBalancing: + weight: 120 + geo: EU + defaultGeo: true + excludeAddresses: + - "some.local.domain" +``` + +In the above case `some.local.domain` will not be set up as a CNAME record in the DNS provider. + +**Note**: It is valid to exclude all addresses. However this will result in existing records being removed and no new ones being created. diff --git a/examples/dnspolicy/dnspolicy-exclude-address.yaml b/examples/dnspolicy/dnspolicy-exclude-address.yaml new file mode 100644 index 000000000..64be973a7 --- /dev/null +++ b/examples/dnspolicy/dnspolicy-exclude-address.yaml @@ -0,0 +1,20 @@ +apiVersion: kuadrant.io/v1alpha1 +kind: DNSPolicy +metadata: + name: prod-web + namespace: ${DNSPOLICY_NAMESPACE} +spec: + targetRef: + name: prod-web-istio + group: gateway.networking.k8s.io + kind: Gateway + providerRefs: + - name: aws-credentials + loadBalancing: + weight: 120 + geo: EU + defaultGeo: true + excludeAddresses: + - "10.89.0.0/16" + - "some.local.domain" + - "127.0.0.1" diff --git a/go.mod b/go.mod index 070ea6ed8..a2ef1ae74 100644 --- a/go.mod +++ b/go.mod @@ -16,8 +16,8 @@ require ( github.com/kuadrant/limitador-operator v0.9.0 github.com/kuadrant/policy-machinery v0.2.0 github.com/martinlindhe/base36 v1.1.1 - github.com/onsi/ginkgo/v2 v2.17.2 - github.com/onsi/gomega v1.33.1 + github.com/onsi/ginkgo/v2 v2.20.2 + github.com/onsi/gomega v1.34.1 github.com/prometheus/client_golang v1.19.1 github.com/samber/lo v1.39.0 go.uber.org/zap v1.27.0 @@ -89,7 +89,7 @@ require ( github.com/google/btree v1.1.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 // indirect + github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect @@ -154,17 +154,17 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // indirect go.starlark.net v0.0.0-20240520160348-046347dcd104 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.22.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + golang.org/x/tools v0.24.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect diff --git a/go.sum b/go.sum index 2b9579e9c..d33128aeb 100644 --- a/go.sum +++ b/go.sum @@ -192,8 +192,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= -github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= +github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -341,10 +341,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g= -github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -508,18 +508,18 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -534,8 +534,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -570,8 +570,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -580,8 +580,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -590,8 +590,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -602,14 +602,13 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= diff --git a/tests/common/dnspolicy/dnspolicy_controller_test.go b/tests/common/dnspolicy/dnspolicy_controller_test.go index 92ec7d3a2..df06d5756 100644 --- a/tests/common/dnspolicy/dnspolicy_controller_test.go +++ b/tests/common/dnspolicy/dnspolicy_controller_test.go @@ -481,10 +481,11 @@ var _ = Describe("DNSPolicy controller", func() { }, time.Second*15, time.Second).Should(BeEmpty()) }, testTimeOut) - It("should have accepted and not enforced status", func(ctx SpecContext) { + It("should have accepted and enforced status", func(ctx SpecContext) { Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) g.Expect(err).NotTo(HaveOccurred()) + fmt.Println("conditions ", dnsPolicy.Status.Conditions) g.Expect(dnsPolicy.Status.Conditions).To( ContainElements( MatchFields(IgnoreExtras, Fields{ @@ -495,27 +496,13 @@ var _ = Describe("DNSPolicy controller", func() { }), MatchFields(IgnoreExtras, Fields{ "Type": Equal(string(kuadrant.PolicyConditionEnforced)), - "Status": Equal(metav1.ConditionFalse), - "Reason": Equal(string(kuadrant.PolicyReasonUnknown)), - "Message": Equal("DNSPolicy has encountered some issues: policy is not enforced on any DNSRecord: no routes attached for listeners"), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyReasonEnforced)), + "Message": ContainSubstring("DNSPolicy has been successfully enforced : no DNSRecords created based on policy and gateway configuration : no valid status addresses to use on gateway"), })), ) }, tests.TimeoutMedium, time.Second).Should(Succeed()) }, testTimeOut) - - It("should set gateway back reference", func(ctx SpecContext) { - policyBackRefValue := testNamespace + "/" + dnsPolicy.Name - refs, _ := json.Marshal([]client.ObjectKey{{Name: dnsPolicy.Name, Namespace: testNamespace}}) - policiesBackRefValue := string(refs) - - Eventually(func(g Gomega) { - gw := &gatewayapiv1.Gateway{} - err := k8sClient.Get(ctx, client.ObjectKey{Name: gateway.Name, Namespace: testNamespace}, gw) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(gw.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyDirectReferenceAnnotationName, policyBackRefValue)) - g.Expect(gw.Annotations).To(HaveKeyWithValue(v1alpha1.DNSPolicyBackReferenceAnnotationName, policiesBackRefValue)) - }, tests.TimeoutMedium, time.Second).Should(Succeed()) - }, testTimeOut) }) Context("valid target and valid gateway status", func() { @@ -836,4 +823,256 @@ var _ = Describe("DNSPolicy controller", func() { Expect(err.Error()).To(ContainSubstring("Invalid targetRef.kind. The only supported values are 'Gateway'")) }, testTimeOut) }) + + Context("no attached routes to listeners", func() { + BeforeEach(func(ctx SpecContext) { + gateway = tests.NewGatewayBuilder(tests.GatewayName, gatewayClass.Name, testNamespace). + WithHTTPListener(tests.ListenerNameOne, tests.HostOne(domain)). + WithHTTPListener(tests.ListenerNameWildcard, tests.HostWildcard(domain)). + Gateway + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(tests.GatewayName) + Expect(k8sClient.Create(ctx, gateway)).To(Succeed()) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) + gateway.Status.Addresses = []gatewayapiv1.GatewayStatusAddress{ + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressOne, + }, + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressTwo, + }, + } + gateway.Status.Listeners = []gatewayapiv1.ListenerStatus{ + { + Name: tests.ListenerNameOne, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 0, + Conditions: []metav1.Condition{}, + }, + { + Name: tests.ListenerNameWildcard, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 0, + Conditions: []metav1.Condition{}, + }, + } + g.Expect(k8sClient.Status().Update(ctx, gateway)).To(Succeed()) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + + }) + + It("should have an accpeterd and enforced policy with additional context", func(ctx SpecContext) { + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionTrue), + "Message": ContainSubstring("DNSPolicy has been accepted"), + })), + ) + + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Message": ContainSubstring("DNSPolicy has been successfully enforced : no DNSRecords created based on policy and gateway configuration : no routes attached to any gateway listeners"), + })), + ) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) + }) + + }) + + Context("excludeAddresses from DNS", func() { + BeforeEach(func(ctx SpecContext) { + gateway = tests.NewGatewayBuilder(tests.GatewayName, gatewayClass.Name, testNamespace). + WithHTTPListener(tests.ListenerNameOne, tests.HostOne(domain)). + WithHTTPListener(tests.ListenerNameWildcard, tests.HostWildcard(domain)). + Gateway + Expect(k8sClient.Create(ctx, gateway)).To(Succeed()) + }) + It("should create a DNSPolicy with an invalid CIDR", func(ctx SpecContext) { + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(gateway.Name). + WithExcludeAddresses([]string{"1.1.1.1/345"}) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) + gateway.Status.Addresses = []gatewayapiv1.GatewayStatusAddress{ + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressOne, + }, + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressTwo, + }, + } + gateway.Status.Listeners = []gatewayapiv1.ListenerStatus{ + { + Name: tests.ListenerNameOne, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + { + Name: tests.ListenerNameWildcard, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + } + g.Expect(k8sClient.Status().Update(ctx, gateway)).To(Succeed()) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionFalse), + "Message": ContainSubstring("could not parse the CIDR from the excludeAddresses field"), + })), + ) + }, tests.TimeoutMedium, time.Second).Should(Succeed()) + + }) + + It("should create a DNSPolicy valid exclude addresses", func(ctx SpecContext) { + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(gateway.Name). + WithExcludeAddresses([]string{tests.IPAddressOne}) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) + gateway.Status.Addresses = []gatewayapiv1.GatewayStatusAddress{ + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressOne, + }, + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressTwo, + }, + } + gateway.Status.Listeners = []gatewayapiv1.ListenerStatus{ + { + Name: tests.ListenerNameOne, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + { + Name: tests.ListenerNameWildcard, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + } + g.Expect(k8sClient.Status().Update(ctx, gateway)).To(Succeed()) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal(string(kuadrant.PolicyReasonEnforced)), + "Message": Equal("DNSPolicy has been successfully enforced"), + })), + ) + recordName = fmt.Sprintf("%s-%s", tests.GatewayName, tests.ListenerNameOne) + rec := &kuadrantdnsv1alpha1.DNSRecord{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: recordName, Namespace: testNamespace}, rec)).To(Succeed()) + foundExcluded := false + foundAllowed := false + for _, ep := range rec.Spec.Endpoints { + for _, t := range ep.Targets { + if t == tests.IPAddressOne { + foundExcluded = true + } + if t == tests.IPAddressTwo { + foundAllowed = true + } + } + } + g.Expect(foundExcluded).To(BeFalse()) + g.Expect(foundAllowed).To(BeTrue()) + g.Expect(len(gateway.Status.Listeners)).To(Equal(int(dnsPolicy.Status.TotalRecords))) + + }, tests.TimeoutLong, time.Second).Should(Succeed()) + + }) + It("should not create a DNSRecords if no endpoints due to DNSPolicy exclude addresses", func(ctx SpecContext) { + dnsPolicy = v1alpha1.NewDNSPolicy("test-dns-policy", testNamespace). + WithProviderSecret(*dnsProviderSecret). + WithTargetGateway(gateway.Name). + WithExcludeAddresses([]string{tests.IPAddressOne, tests.IPAddressTwo}) + Expect(k8sClient.Create(ctx, dnsPolicy)).To(Succeed()) + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(gateway), gateway)).To(Succeed()) + gateway.Status.Addresses = []gatewayapiv1.GatewayStatusAddress{ + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressOne, + }, + { + Type: ptr.To(gatewayapiv1.IPAddressType), + Value: tests.IPAddressTwo, + }, + } + gateway.Status.Listeners = []gatewayapiv1.ListenerStatus{ + { + Name: tests.ListenerNameOne, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + { + Name: tests.ListenerNameWildcard, + SupportedKinds: []gatewayapiv1.RouteGroupKind{}, + AttachedRoutes: 1, + Conditions: []metav1.Condition{}, + }, + } + g.Expect(k8sClient.Status().Update(ctx, gateway)).To(Succeed()) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsPolicy), dnsPolicy) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(gatewayapiv1alpha2.PolicyConditionAccepted)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + g.Expect(dnsPolicy.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(kuadrant.PolicyConditionEnforced)), + "Status": Equal(metav1.ConditionTrue), + })), + ) + g.Expect(int(dnsPolicy.Status.TotalRecords)).To(Equal(0)) + }, tests.TimeoutMedium, tests.RetryIntervalMedium).Should(Succeed()) + }) + }) })