From a241f66712152f66077d27367f397395646afab4 Mon Sep 17 00:00:00 2001 From: Sergei Lukianov Date: Thu, 14 Dec 2023 22:56:25 -0800 Subject: [PATCH] Prevent vpc loopback subnet leakage --- api/agent/v1alpha2/agent_types.go | 1 + .../bases/agent.githedgehog.com_agents.yaml | 2 + docs/api.md | 1 + pkg/agent/dozer/bcm/plan.go | 39 ++++++++++++++++--- pkg/ctrl/agent/agent_ctrl.go | 5 ++- pkg/manager/config/config.go | 4 ++ 6 files changed, 44 insertions(+), 8 deletions(-) diff --git a/api/agent/v1alpha2/agent_types.go b/api/agent/v1alpha2/agent_types.go index 0f4b12c5..f563b2cc 100644 --- a/api/agent/v1alpha2/agent_types.go +++ b/api/agent/v1alpha2/agent_types.go @@ -65,6 +65,7 @@ type AgentSpecConfig struct { CollapsedCore *AgentSpecConfigCollapsedCore `json:"collapsedCore,omitempty"` SpineLeaf *AgentSpecConfigSpineLeaf `json:"spineLeaf,omitempty"` BaseVPCCommunity string `json:"baseVPCCommunity,omitempty"` + VPCLoopbackSubnet string `json:"vpcLoopbackSubnet,omitempty"` } type AgentSpecConfigCollapsedCore struct{} diff --git a/config/crd/bases/agent.githedgehog.com_agents.yaml b/config/crd/bases/agent.githedgehog.com_agents.yaml index 6a7cee9d..4dec8451 100644 --- a/config/crd/bases/agent.githedgehog.com_agents.yaml +++ b/config/crd/bases/agent.githedgehog.com_agents.yaml @@ -96,6 +96,8 @@ spec: type: string spineLeaf: type: object + vpcLoopbackSubnet: + type: string vpcPeeringDisabled: type: boolean type: object diff --git a/docs/api.md b/docs/api.md index 6267e2ad..2a230724 100644 --- a/docs/api.md +++ b/docs/api.md @@ -90,6 +90,7 @@ _Appears in:_ | `collapsedCore` _[AgentSpecConfigCollapsedCore](#agentspecconfigcollapsedcore)_ | | | `spineLeaf` _[AgentSpecConfigSpineLeaf](#agentspecconfigspineleaf)_ | | | `baseVPCCommunity` _string_ | | +| `vpcLoopbackSubnet` _string_ | | #### AgentSpecConfigCollapsedCore diff --git a/pkg/agent/dozer/bcm/plan.go b/pkg/agent/dozer/bcm/plan.go index 15df7168..a4bbf1e8 100644 --- a/pkg/agent/dozer/bcm/plan.go +++ b/pkg/agent/dozer/bcm/plan.go @@ -47,6 +47,7 @@ const ( ROUTE_MAP_BLOCK_EVPN_DEFAULT_REMOTE = "evpn-default-remote-block" ROUTE_MAP_MAX_STATEMENT = 65535 PREFIX_LIST_ANY = "any-prefix" + PREFIX_LIST_VPC_LO = "vpc-lo-prefix" ) func (p *broadcomProcessor) PlanDesiredState(ctx context.Context, agent *agentapi.Agent) (*dozer.Spec, error) { @@ -938,6 +939,18 @@ func ipnsVrfName(ipnsName string) string { } func planVPCs(agent *agentapi.Agent, spec *dozer.Spec) error { + spec.PrefixLists[PREFIX_LIST_VPC_LO] = &dozer.SpecPrefixList{ + Prefixes: map[uint32]*dozer.SpecPrefixListEntry{ + 10: { + Prefix: dozer.SpecPrefixListPrefix{ + Prefix: agent.Spec.Config.VPCLoopbackSubnet, + Le: 32, + }, + Action: dozer.SpecPrefixListActionPermit, + }, + }, + } + for vpcName := range agent.Spec.VPCs { vrfName := vpcVrfName(vpcName) @@ -992,6 +1005,12 @@ func planVPCs(agent *agentapi.Agent, spec *dozer.Spec) error { stampVPCRouteMap := stampVPCRouteMapName(vpcName) spec.RouteMaps[stampVPCRouteMap] = &dozer.SpecRouteMap{ Statements: map[string]*dozer.SpecRouteMapStatement{ + "1": { + Conditions: dozer.SpecRouteMapConditions{ + MatchPrefixList: stringPtr(PREFIX_LIST_VPC_LO), + }, + Result: dozer.SpecRouteMapResultReject, + }, "10": { SetCommunities: []string{vpcComm}, Result: dozer.SpecRouteMapResultAccept, @@ -1025,7 +1044,9 @@ func planVPCs(agent *agentapi.Agent, spec *dozer.Spec) error { dozer.SpecVRFBGPTableConnectionConnected: { ImportPolicies: []string{stampVPCRouteMap}, }, - dozer.SpecVRFBGPTableConnectionStatic: {}, + dozer.SpecVRFBGPTableConnectionStatic: { + ImportPolicies: []string{stampVPCRouteMap}, + }, } spec.VRFs[vrfName].Interfaces[irbIface] = &dozer.SpecVRFInterface{} @@ -1466,7 +1487,7 @@ func planLoopbackWorkaround(agent *agentapi.Agent, spec *dozer.Spec, peeringName return "", "", "", "", errors.Errorf("workaround link port %s for peering %s not found", ports[1], peeringName) } - ip1, ip2, err := vpcWorkaroundIPs("172.30.224.0/19", vlan) // TODO move to config + ip1, ip2, err := vpcWorkaroundIPs(agent, vlan) if err != nil { return "", "", "", "", errors.Wrapf(err, "failed to get workaround IPs for peering") } @@ -1512,21 +1533,27 @@ func getMaxPaths(agent *agentapi.Agent) uint32 { } // TODO test -func vpcWorkaroundIPs(subnet string, vlan uint16) (string, string, error) { - _, ipNet, err := net.ParseCIDR(subnet) +func vpcWorkaroundIPs(agent *agentapi.Agent, vlan uint16) (string, string, error) { + _, ipNet, err := net.ParseCIDR(agent.Spec.Config.VPCLoopbackSubnet) if err != nil { return "", "", err } prefixLen, _ := ipNet.Mask.Size() - if prefixLen < 19 { - return "", "", errors.Errorf("subnet should be at least /19") + if prefixLen > 20 { + return "", "", errors.Errorf("subnet should be at least /20") } ip := ipNet.IP.To4() ip[2] += byte(vlan / 128) ip[3] += byte(vlan % 128 * 2) res1 := ip.String() + ip[3] += 1 + + if !ipNet.Contains(ip) { + return "", "", errors.Errorf("subnet %s is too small for VLAN %d", agent.Spec.Config.VPCLoopbackSubnet, vlan) + } + res2 := ip.String() return res1, res2, nil diff --git a/pkg/ctrl/agent/agent_ctrl.go b/pkg/ctrl/agent/agent_ctrl.go index fa589b42..a8b145ee 100644 --- a/pkg/ctrl/agent/agent_ctrl.go +++ b/pkg/ctrl/agent/agent_ctrl.go @@ -449,8 +449,9 @@ func (r *AgentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl agent.Spec.StatusUpdates = statusUpdates agent.Spec.Config = agentapi.AgentSpecConfig{ - ControlVIP: r.Cfg.ControlVIP, - BaseVPCCommunity: r.Cfg.BaseVPCCommunity, + ControlVIP: r.Cfg.ControlVIP, + BaseVPCCommunity: r.Cfg.BaseVPCCommunity, + VPCLoopbackSubnet: r.Cfg.VPCLoopbackSubnet, } if r.Cfg.FabricMode == config.FabricModeCollapsedCore { agent.Spec.Config.CollapsedCore = &agentapi.AgentSpecConfigCollapsedCore{} diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index efdc8863..552a1234 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -28,6 +28,7 @@ type Fabric struct { DHCPDConfigKey string `json:"dhcpdConfigKey,omitempty"` FabricMode FabricMode `json:"fabricMode,omitempty"` BaseVPCCommunity string `json:"baseVPCCommunity,omitempty"` + VPCLoopbackSubnet string `json:"vpcLoopbackSubnet,omitempty"` reservedSubnets []*net.IPNet } @@ -134,6 +135,9 @@ func Load(basedir string) (*Fabric, error) { if cfg.BaseVPCCommunity == "" { return nil, errors.Errorf("config: baseVPCCommunity is required") } + if cfg.VPCLoopbackSubnet == "" { + return nil, errors.Errorf("config: vpcLoopbackSubnet is required") + } // TODO validate format of all fields