Skip to content

Commit

Permalink
Add support for DNSNameResolver
Browse files Browse the repository at this point in the history
Signed-off-by: arkadeepsen <arsen@redhat.com>
  • Loading branch information
arkadeepsen committed Apr 16, 2024
1 parent f2701d1 commit 7607777
Show file tree
Hide file tree
Showing 145 changed files with 17,519 additions and 207 deletions.
8 changes: 6 additions & 2 deletions dist/templates/k8s.ovn.org_egressfirewalls.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ spec:
dnsName:
description: dnsName is the domain name to allow/deny traffic
to. If this is set, cidrSelector and nodeSelector must
be unset.
pattern: ^([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$
be unset. For a wildcard DNS name, the '*' will match
only one label. Additionally, only a single '*' can be
used at the beginning of the wildcard DNS name. For example,
'*.example.com' will match 'sub1.example.com' but won't
match 'sub2.sub1.example.com'.
pattern: ^(\*\.)?([A-Za-z0-9-]+\.)*[A-Za-z0-9-]+\.?$
type: string
nodeSelector:
description: nodeSelector will allow/deny traffic to the
Expand Down
4 changes: 2 additions & 2 deletions go-controller/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ require (
github.com/mitchellh/copystructure v1.2.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.30.0
github.com/openshift/api v0.0.0-20230807132801-600991d550ac
github.com/openshift/client-go v0.0.0-20230503144108-75015d2347cb
github.com/openshift/api v0.0.0-20231120222239-b86761094ee3
github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a
github.com/ovn-org/libovsdb v0.6.1-0.20240125124854-03f787b1a892
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.18.0
Expand Down
8 changes: 4 additions & 4 deletions go-controller/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,10 @@ github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.m
github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs=
github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE=
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/openshift/api v0.0.0-20230807132801-600991d550ac h1:HqT8MmYGXiUGUW0BjygTGOOvqO2wIsTaG3q8nboJyPY=
github.com/openshift/api v0.0.0-20230807132801-600991d550ac/go.mod h1:yimSGmjsI+XF1mr+AKBs2//fSXIOhhetHGbMlBEfXbs=
github.com/openshift/client-go v0.0.0-20230503144108-75015d2347cb h1:Nij5OnaECrkmcRQMAE9LMbQXPo95aqFnf+12B7SyFVI=
github.com/openshift/client-go v0.0.0-20230503144108-75015d2347cb/go.mod h1:Rhb3moCqeiTuGHAbXBOlwPubUMlOZEkrEWTRjIF3jzs=
github.com/openshift/api v0.0.0-20231120222239-b86761094ee3 h1:nLhV2lbWrJ3E3hx0/97G3ZZvppC67cNwo+CLp7/PAbA=
github.com/openshift/api v0.0.0-20231120222239-b86761094ee3/go.mod h1:qNtV0315F+f8ld52TLtPvrfivZpdimOzTi3kn9IVbtU=
github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a h1:4FVrw8hz0Wb3izbf6JfOEK+pJTYpEvteRR73mCh2g/A=
github.com/openshift/client-go v0.0.0-20231121143148-910ca30a1a9a/go.mod h1:arApQobmOjZqtxw44TwnQdUCH+t9DgZ8geYPFqksHws=
github.com/openshift/custom-resource-status v1.1.2 h1:C3DL44LEbvlbItfd8mT5jWrqPfHnSOQoQf/sypqA6A4=
github.com/openshift/custom-resource-status v1.1.2/go.mod h1:DB/Mf2oTeiAmVVX1gN+NEqweonAPY0TKUwADizj8+ZA=
github.com/ovn-org/libovsdb v0.6.1-0.20240125124854-03f787b1a892 h1:/yg3/z+RH+iDLMxp6FTnmlk5bStK542/Rge5EBjnA9A=
Expand Down
14 changes: 14 additions & 0 deletions go-controller/pkg/clustermanager/clustermanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net"
"sync"

"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/clustermanager/dnsnameresolver"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/clustermanager/egressservice"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/clustermanager/status_manager"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube"
Expand Down Expand Up @@ -38,6 +39,8 @@ type ClusterManager struct {
// The OVN DB setup is handled by egressIPZoneController that runs in ovnkube-controller
eIPC *egressIPClusterController
egressServiceController *egressservice.Controller
// Controller used for maintaining dns name resolver objects
dnsNameResolverController *dnsnameresolver.Controller
// event recorder used to post events to k8s
recorder record.EventRecorder

Expand Down Expand Up @@ -109,6 +112,9 @@ func NewClusterManager(ovnClient *util.OVNClusterManagerClientset, wf *factory.W
return nil, err
}
}
if util.IsDNSNameResolverEnabled() {
cm.dnsNameResolverController = dnsnameresolver.NewController(ovnClient, wf)
}
return cm, nil
}

Expand Down Expand Up @@ -151,6 +157,11 @@ func (cm *ClusterManager) Start(ctx context.Context) error {
return err
}

if util.IsDNSNameResolverEnabled() {
if err := cm.dnsNameResolverController.Start(); err != nil {
return err
}
}
return nil
}

Expand All @@ -169,4 +180,7 @@ func (cm *ClusterManager) Stop() {
cm.egressServiceController.Stop()
}
cm.statusManager.Stop()
if util.IsDNSNameResolverEnabled() {
cm.dnsNameResolverController.Stop()
}
}
292 changes: 292 additions & 0 deletions go-controller/pkg/clustermanager/dnsnameresolver/controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,292 @@
package dnsnameresolver

import (
"context"
"fmt"
"reflect"
"strings"
"sync"
"time"

"github.com/miekg/dns"
ocpnetworkapiv1alpha1 "github.com/openshift/api/network/v1alpha1"
ocpnetworkclientset "github.com/openshift/client-go/network/clientset/versioned"
ocpnetworklisterv1alpha1 "github.com/openshift/client-go/network/listers/network/v1alpha1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/config"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/controller"
egressfirewall "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1"
egressfirewalllister "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/crd/egressfirewall/v1/apis/listers/egressfirewall/v1"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/factory"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types"
"github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util"

kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
"k8s.io/klog/v2"
)

// Controller holds the information of the DNS names and the corresponding
// DNSNameResolver objects. The controller maintains the DNSNameResolver
// objects in the cluster.
type Controller struct {
lock sync.Mutex
ocpNetworkClient ocpnetworkclientset.Interface

// controller for egress firewall
efController controller.Controller
// Lister for egress firewall
efLister egressfirewalllister.EgressFirewallLister
// controller for dns name resolver
dnsController controller.Controller
// Lister for dns name resolver
dnsLister ocpnetworklisterv1alpha1.DNSNameResolverLister

resInfo *resolverInfo
}

// NewController returns an instance of the Controller. The level-driven controller is also
// initialized before returning the Controller intance.
func NewController(ovnClient *util.OVNClusterManagerClientset, wf *factory.WatchFactory) *Controller {
c := &Controller{
ocpNetworkClient: ovnClient.OCPNetworkClient,
resInfo: newResolverInfo(ovnClient.OCPNetworkClient),
}
c.initControllers(wf)
return c
}

// initControllers initializes the controllers for the different resource
// types related to DNSNameResolver.
func (c *Controller) initControllers(watchFactory *factory.WatchFactory) {
efSharedIndexInformer := watchFactory.EgressFirewallInformer().Informer()
c.efLister = watchFactory.EgressFirewallInformer().Lister()
efConfig := &controller.Config[egressfirewall.EgressFirewall]{
RateLimiter: workqueue.NewItemFastSlowRateLimiter(time.Second, 5*time.Second, 5),
Informer: efSharedIndexInformer,
Lister: c.efLister.List,
ObjNeedsUpdate: efNeedsUpdate,
Reconcile: c.reconcileEgressFirewall,
}
c.efController = controller.NewController[egressfirewall.EgressFirewall]("cm-ef-controller", efConfig)

dnsSharedIndexInformer := watchFactory.DNSNameResolverInformer().Informer()
c.dnsLister = ocpnetworklisterv1alpha1.NewDNSNameResolverLister(dnsSharedIndexInformer.GetIndexer())
dnsConfig := &controller.Config[ocpnetworkapiv1alpha1.DNSNameResolver]{
RateLimiter: workqueue.NewItemFastSlowRateLimiter(time.Second, 5*time.Second, 5),
Informer: dnsSharedIndexInformer,
Lister: c.dnsLister.List,
ObjNeedsUpdate: dnsNeedsUpdate,
Reconcile: c.reconcileDNSNameResolver,
InitialSync: c.syncDNSNames,
}
c.dnsController = controller.NewController[ocpnetworkapiv1alpha1.DNSNameResolver]("cm-dns-controller", dnsConfig)
}

// efNeedsUpdate returns true if an egress firewall object is either added
// or deleted. If an egress firewall is updated, then efNeedsUpdate returns
// true if the .spec of the object is modified.
func efNeedsUpdate(oldObj, newObj *egressfirewall.EgressFirewall) bool {
if oldObj == nil || newObj == nil {
return true
}
return !reflect.DeepEqual(oldObj.Spec, newObj.Spec)
}

// dnsNeedsUpdate returns true if a dns name resolver object is either added
// or deleted. If a dns name resolver is updated, then dnsNeedsUpdate returns
// false.
func dnsNeedsUpdate(oldObj, newObj *ocpnetworkapiv1alpha1.DNSNameResolver) bool {
if oldObj == nil && newObj != nil || oldObj != nil && newObj == nil {
return true
}
return false
}

// Start initializes the handlers for EgressFirewall and DNSNameResolver
// by watching the corresponding resource types.
func (c *Controller) Start() error {
if err := c.efController.Start(1); err != nil {
return fmt.Errorf("unable to start egress firewall controller %w", err)
}
if err := c.dnsController.Start(1); err != nil {
return fmt.Errorf("unable to start dns name resolver controller %w", err)
}
return nil
}

// Stop gracefully stops the controller. The handlers for EgressFirewall
// and DNSNameResolver are removed.
func (c *Controller) Stop() {
c.efController.Stop()
c.dnsController.Stop()
}

// syncDNSNames syncs the existing EgressFirewall and DNSNameResolver objects
// after a restart and updates the dnsNameObj and objDNSName maps. After the
// sync these two maps will only contain the details of the DNS names which
// are used in at least one namespace.
func (c *Controller) syncDNSNames() error {
c.lock.Lock()
defer c.lock.Unlock()

// Fetch the existing DNSNameResolver objects.
dnsNameResolvers, err := c.dnsLister.DNSNameResolvers(config.Kubernetes.OVNConfigNamespace).List(labels.Everything())
if err != nil {
return fmt.Errorf("syncDNSNames unable to get Egress Firewalls: %w", err)
}

dnsNameToResolver := make(map[string]string)
for _, dnsNameResolver := range dnsNameResolvers {
dnsNameToResolver[string(dnsNameResolver.Spec.Name)] = dnsNameResolver.Name
}

// Fetch the existing EgressFirewall objects.
egressFirewalls, err := c.efLister.List(labels.Everything())
if err != nil {
return fmt.Errorf("syncDNSNames unable to get Egress Firewalls: %w", err)
}

namespaceToDNSNames := make(map[string][]string)
for _, egressFirewall := range egressFirewalls {
namespaceToDNSNames[egressFirewall.Namespace] = getDNSNames(egressFirewall)
}

c.resInfo.SyncResolverInfo(dnsNameToResolver, namespaceToDNSNames)

return nil
}

// reconcileEgressFirewall reconciles an EgressFirewall object.
func (c *Controller) reconcileEgressFirewall(key string) error {
c.lock.Lock()
defer c.lock.Unlock()
// Split the key in namespace and name of the corresponding object.
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
klog.Errorf("reconcileEgressFirewall failed to split meta namespace cache key %s for egress firewall: %v", key, err)
return nil
}
// Fetch the egress firewall object using the name and namespace.
ef, err := c.efLister.EgressFirewalls(namespace).Get(name)
if err != nil {
if kerrors.IsNotFound(err) {
// EgressFirewall object was deleted. Delete all the DNSNameResolver
// objects corresponding to the DNS names used in the EgressFirewall
// object.
return c.resInfo.DeleteDNSNamesForNamespace(namespace)
}
return fmt.Errorf("failed to fetch egress firewall %s in namespace %s", name, namespace)
}

// EgressFirewall object was added/updated. Check the DNS names which are
// newly added and create the corresponding DNSNameResolver objects. Also
// check the DNS names which are deleted and delete the corresponding
// DNSNameResolver objects.
return c.resInfo.ModifyDNSNamesForNamespace(getDNSNames(ef), namespace)
}

// reconcileDNSNameResolver reconciles a DNSNameResolver object. If an object
// was deleted, but it was not supposed to, then it is recreated. If an object
// is created, but it was not supposed to, then it is deleted.
func (c *Controller) reconcileDNSNameResolver(key string) error {
c.lock.Lock()
defer c.lock.Unlock()
// Split the key in namespace and name of the corresponding object.
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
klog.Errorf("reconcileDNSNameResolver failed to split meta namespace cache key %s for dns name resolver: %v", key, err)
return nil
}
// Fetch the dns name resolver object using the name and namespace.
resolverObj, err := c.dnsLister.DNSNameResolvers(namespace).Get(name)
if err != nil {
if kerrors.IsNotFound(err) {
// DNSNameResolver object was deleted. If it is not supposed to be deleted,
// recreate it.

// Check if the DNSNameResolver object should exist. If so, then get the
// corresponding DNS name.
dnsName, found := c.resInfo.GetDNSNameForResolver(name)
if !found {
return nil
}

// Recreate the DNSNameResolver object.
klog.Warningf("Recreating deleted dns name resolver object %s for dns name %s", name, dnsName)
return createDNSNameResolver(c.ocpNetworkClient, name, dnsName)

}
return fmt.Errorf("reconcileDNSNameResolver failed to fetch dns name resolver %s in namespace %s", name, namespace)
}

// DNSNameResolver object was added/updated. If it is not supposed to exist,
// delete the object.

// Check if the DNSNameResolver object matches the DNS name.
dnsName := string(resolverObj.Spec.Name)
if !c.resInfo.IsDNSNameMatchingResolverName(dnsName, resolverObj.Name) {
// Delete the DNSNameResolver object.
klog.Warningf("Deleting additional dns name resolver object %s for dns name %s", resolverObj.Name, dnsName)
return deleteDNSNameResolver(c.ocpNetworkClient, resolverObj.Name)
}
return nil
}

// createDNSNameResolver creates a DNSNameResolver object for the DNS name
// and adds the status, if any, to the object. The error, if any, encountered
// during the object creation is returned.
func createDNSNameResolver(ocpNetworkClient ocpnetworkclientset.Interface, objName, dnsName string) error {
dnsNameResolverObj := &ocpnetworkapiv1alpha1.DNSNameResolver{
ObjectMeta: metav1.ObjectMeta{
Name: objName,
Namespace: config.Kubernetes.OVNConfigNamespace,
},
Spec: ocpnetworkapiv1alpha1.DNSNameResolverSpec{
Name: ocpnetworkapiv1alpha1.DNSName(dnsName),
},
}
_, err := ocpNetworkClient.NetworkV1alpha1().DNSNameResolvers(config.Kubernetes.OVNConfigNamespace).
Create(context.TODO(), dnsNameResolverObj, metav1.CreateOptions{})

return err

}

// deleteDNSNameResolver deletes a DNSNameResolver object and if an error
// is encountered, which is not IsNotFound, then it is returned.
func deleteDNSNameResolver(ocpNetworkClient ocpnetworkclientset.Interface, objName string) error {
err := ocpNetworkClient.NetworkV1alpha1().DNSNameResolvers(config.Kubernetes.OVNConfigNamespace).
Delete(context.TODO(), objName, metav1.DeleteOptions{})
if err != nil && !kerrors.IsNotFound(err) {
return err
}
return nil
}

// getDNSNames iterates through the egress firewall rules and returns the DNS
// names present in them after validating the rules.
func getDNSNames(ef *egressfirewall.EgressFirewall) []string {
var dnsNameSlice []string
for i, egressFirewallRule := range ef.Spec.Egress {
if i > types.EgressFirewallStartPriority-types.MinimumReservedEgressFirewallPriority {
klog.Warningf("egressFirewall for namespace %s has too many rules, the rest will be ignored", ef.Namespace)
break
}

// Validate egress firewall rule destination and get the DNS name
// if used in the rule.
_, dnsName, _, _, err := util.ValidateAndGetEgressFirewallDestination(egressFirewallRule.To)
if err != nil {
return []string{}
}

if dnsName != "" {
dnsNameSlice = append(dnsNameSlice, strings.ToLower(dns.Fqdn(dnsName)))
}
}

return dnsNameSlice
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package dnsnameresolver

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestDNSNameResolverController(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Cluster Manager DNS Name Resolver Controller Suite")
}
Loading

0 comments on commit 7607777

Please sign in to comment.