Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compatibility for VyOS bare-metal setup #41

Merged
merged 44 commits into from
Feb 15, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3d1fb52
Skip config generation for unused components
ibot3 Dec 30, 2022
15ba2cf
Add nftables `PolicyPrefix`
ibot3 Dec 30, 2022
de689cb
Flush known nftables chains before redefining
ibot3 Dec 30, 2022
07bcc61
Add missing `PolicyPrefix` to `nftablesConfig`
ibot3 Dec 30, 2022
8ea9d33
Add `NftCommand` and `LiveReload` nftables config options
ibot3 Dec 30, 2022
ed69425
Add `getExistingPolicyChains` function to make LiveReload possible an…
ibot3 Dec 30, 2022
eb8699f
Adjust template to prefix policy chains and delete existing policy ch…
ibot3 Dec 30, 2022
6e2359d
Adjust agent script to omit keepalived if not required and to reload …
ibot3 Jan 4, 2023
9d9829c
Make controller independent from openstack
ibot3 Jan 4, 2023
22ba2e0
Add venv to gitignore
ibot3 Jan 4, 2023
2ff3b10
Make l3 port manager exchangeable
ibot3 Jan 4, 2023
7378c66
Add EnableSNAT option
ibot3 Jan 11, 2023
ac798ff
Add missing static port manager and improve validation
ibot3 Jan 11, 2023
d22350f
Update config tests
ibot3 Jan 11, 2023
7508050
Load available l3ports on startup
ibot3 Jan 11, 2023
75d29b1
Change workflow to build images on all branches
ibot3 Jan 11, 2023
fdd032e
Remove useless defaultTrue() function
ibot3 Jan 11, 2023
94c9ef6
Remove duplicate creation log message
ibot3 Jan 12, 2023
367c4b9
Update go to 1.18
ibot3 Jan 12, 2023
57e15dc
Add check if l3 port exists to static port manager
ibot3 Jan 12, 2023
1272cf0
Check if service port id really exists
ibot3 Jan 12, 2023
d876bb0
Fix test and add test for invalid port in annotation
ibot3 Jan 12, 2023
959377d
Add `portmanager.CheckPortExists` method and relocate services with n…
ibot3 Jan 18, 2023
52c2903
Add newline to gitignore
ibot3 Jan 18, 2023
0f55a40
Return error if port-manager implementation is invalid
ibot3 Jan 18, 2023
98a2960
Use config values directly in nftables generator
ibot3 Jan 18, 2023
c0959c7
Fill config with default values before decoding toml file
ibot3 Jan 19, 2023
711d423
Fix setting EnableSNAT to false disables DNAT instead of SNAT
ibot3 Jan 19, 2023
938ed68
Do not initialize ports list of static port manager
ibot3 Jan 19, 2023
515cabd
Add static port manager tests
ibot3 Jan 19, 2023
6e7c299
Split getExistingPolicyChains in three functions and test one
ibot3 Jan 19, 2023
93484c2
Fix wrong argument to nft list chains
ibot3 Jan 19, 2023
835e21e
Only flush/delete chains when live reload is enabled
ibot3 Jan 25, 2023
4a3a13e
Remove TODO
ibot3 Jan 26, 2023
1d54b23
Improve comments
ibot3 Jan 26, 2023
4bec1b5
Require policy-prefix when live-reload is enabled
ibot3 Feb 1, 2023
472ff2d
Use const for port manager config option
ibot3 Feb 1, 2023
ca3ddfd
Rename live-reload to partial-reload
ibot3 Feb 2, 2023
7a11fab
Make comment on `add chain` more clear
ibot3 Feb 2, 2023
a4587e9
Remove unnecessary if statement when setting fwmark default
ibot3 Feb 2, 2023
409e2e2
Use RFC 5737 addresses for tests
ibot3 Feb 2, 2023
eec5104
Raise error if initial `GetAvailablePorts` not working
ibot3 Feb 2, 2023
7e44e75
Restructure MapService function and add some comments
ibot3 Feb 2, 2023
ceb544d
Add default status command for nftables
ibot3 Feb 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/ch-k8s-lbaas-controller
/ch-k8s-lbaas-agent
/vendor
venv
ibot3 marked this conversation as resolved.
Show resolved Hide resolved
25 changes: 17 additions & 8 deletions cmd/ch-k8s-lbaas-agent/ch-k8s-lbaas-agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,23 @@ func main() {
},
}

keepalivedConfig := &agent.ConfigManager{
Service: fileCfg.Keepalived.Service,
Generator: &agent.KeepalivedConfigGenerator{
VRIDBase: fileCfg.Keepalived.VRIDBase,
VRRPPassword: fileCfg.Keepalived.VRRPPassword,
Interface: fileCfg.Keepalived.Interface,
Priority: fileCfg.Keepalived.Priority,
},
var keepalivedConfig *agent.ConfigManager

if fileCfg.Keepalived.Enabled {
keepalivedConfig = &agent.ConfigManager{
Service: fileCfg.Keepalived.Service,
Generator: &agent.KeepalivedConfigGenerator{
VRIDBase: fileCfg.Keepalived.VRIDBase,
VRRPPassword: fileCfg.Keepalived.VRRPPassword,
Interface: fileCfg.Keepalived.Interface,
Priority: fileCfg.Keepalived.Priority,
},
}
}

// If LiveReload is enabled, reload nftables config directly after start to apply last state
if fileCfg.Nftables.LiveReload {
nftablesConfig.Reload()
}

http.Handle("/v1/apply", &agent.ApplyHandlerv1{
Expand Down
29 changes: 22 additions & 7 deletions cmd/ch-k8s-lbaas-controller/ch-k8s-lbaas-controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package main
import (
"flag"
"fmt"
"github.com/cloudandheat/ch-k8s-lbaas/internal/static"
"net"
"net/http"
"time"
Expand Down Expand Up @@ -71,16 +72,30 @@ func main() {
}
config.FillControllerConfig(&fileCfg)

osClient, err := openstack.NewClient(&fileCfg.OpenStack.Global)
err = config.ValidateControllerConfig(&fileCfg)
if err != nil {
klog.Fatalf("Failed to connect to OpenStack: %s", err.Error())
klog.Fatalf("invalid configuration: %s", err.Error())
}

l3portmanager, err := osClient.NewOpenStackL3PortManager(
&fileCfg.OpenStack.Networking,
)
if err != nil {
klog.Fatalf("Failed to create L3 port manager: %s", err.Error())
var l3portmanager controller.L3PortManager

if fileCfg.PortManager == "openstack" {
osClient, err := openstack.NewClient(&fileCfg.OpenStack.Global)
if err != nil {
klog.Fatalf("Failed to connect to OpenStack: %s", err.Error())
}

l3portmanager, err = osClient.NewOpenStackL3PortManager(
&fileCfg.OpenStack.Networking,
)
if err != nil {
klog.Fatalf("Failed to create openstack L3 port manager: %s", err.Error())
}
} else if fileCfg.PortManager == "static" {
l3portmanager, err = static.NewStaticL3PortManager(&fileCfg.Static)
if err != nil {
klog.Fatalf("Failed to create static L3 port manager: %s", err.Error())
}
}

agentController, err := controller.NewHTTPAgentController(fileCfg.Agents)
Expand Down
28 changes: 17 additions & 11 deletions internal/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,21 +289,27 @@ func (h *ApplyHandlerv1) ProcessRequest(lbcfg *model.LoadBalancer) (int, string)
return 400, err.Error() // Bad Request
}

changed, err := h.KeepalivedConfig.WriteWithRollback(lbcfg)
if err != nil {
msg := fmt.Sprintf("Failed to apply keepalived config: %s", err.Error())
klog.Error(msg)
return 500, msg
keepalivedChanged, nftablesChanged := false, false

if h.KeepalivedConfig != nil {
keepalivedChanged, err = h.KeepalivedConfig.WriteWithRollback(lbcfg)
if err != nil {
msg := fmt.Sprintf("Failed to apply keepalived config: %s", err.Error())
klog.Error(msg)
return 500, msg
}
}

changed, err = h.NftablesConfig.WriteWithRollback(lbcfg)
if err != nil {
msg := fmt.Sprintf("Failed to apply nftables config: %s", err.Error())
klog.Error(msg)
return 500, msg
if h.NftablesConfig != nil {
nftablesChanged, err = h.NftablesConfig.WriteWithRollback(lbcfg)
if err != nil {
msg := fmt.Sprintf("Failed to apply nftables config: %s", err.Error())
klog.Error(msg)
return 500, msg
}
}

if changed {
if keepalivedChanged || nftablesChanged {
klog.Infof("Applied configuration update: %#v", lbcfg)
}

Expand Down
98 changes: 90 additions & 8 deletions internal/agent/nftables_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
package agent

import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"sort"
"strconv"
"strings"
"text/template"

"k8s.io/klog"

corev1 "k8s.io/api/core/v1"

"github.com/cloudandheat/ch-k8s-lbaas/internal/config"
Expand All @@ -32,43 +37,54 @@ import (
var (
nftablesTemplate = template.Must(template.New("nftables.conf").Parse(`
{{ $cfg := . }}

flush chain ip {{ .NATTableName }} {{ .NATPreroutingChainName }}
flush chain ip {{ .NATTableName }} {{ .NATPostroutingChainName }}
flush chain {{ .FilterTableType }} {{ .FilterTableName }} {{ .FilterForwardChainName }}

# The "add chain" command is a workaround to make sure that the chain exists before deleting it.
{{- range $chain := $cfg.ExistingPolicyChains }}
add chain {{ $cfg.FilterTableType }} {{ $cfg.FilterTableName }} {{ $chain }}
delete chain {{ $cfg.FilterTableType }} {{ $cfg.FilterTableName }} {{ $chain }}
{{- end }}

table {{ .FilterTableType }} {{ .FilterTableName }} {
chain {{ .FilterForwardChainName }} {
{{- range $dest := $cfg.PolicyAssignments }}
ct mark {{ $cfg.FWMarkBits | printf "0x%x" }} and {{ $cfg.FWMarkMask | printf "0x%x" }} ip daddr {{ $dest.Address }} goto POD-{{ $dest.Address }};
ct mark {{ $cfg.FWMarkBits | printf "0x%x" }} and {{ $cfg.FWMarkMask | printf "0x%x" }} ip daddr {{ $dest.Address }} goto {{ $cfg.PolicyPrefix }}POD-{{ $dest.Address }};
{{- end }}
ct mark {{ $cfg.FWMarkBits | printf "0x%x" }} and {{ $cfg.FWMarkMask | printf "0x%x" }} accept;
}

# Using uppercase POD to prevent collisions with policy names like 'pod-x.x.x.x'
{{- range $pod := $cfg.PolicyAssignments }}
chain POD-{{ $pod.Address }} {
chain {{ $cfg.PolicyPrefix }}POD-{{ $pod.Address }} {
{{- range $pol := $pod.NetworkPolicies }}
jump {{ $pol }};
jump {{ $cfg.PolicyPrefix }}{{ $pol }};
{{- end }}
drop;
}
{{- end }}

# Using uppercase RULE and CIDR to prevent collisions with policy names like 'x-rule-y-cidr-z'
{{- range $policy := $cfg.NetworkPolicies }}
chain {{ $policy.Name }} {
chain {{ $cfg.PolicyPrefix }}{{ $policy.Name }} {
{{- range $ruleIndex, $ingressRule := $policy.IngressRuleChains }}
jump {{ $policy.Name }}-RULE{{ $ruleIndex }};
jump {{ $cfg.PolicyPrefix }}{{ $policy.Name }}-RULE{{ $ruleIndex }};
{{- end }}
}

{{- range $ruleIndex, $ingressRule := $policy.IngressRuleChains }}
chain {{ $policy.Name }}-RULE{{ $ruleIndex }} {
chain {{ $cfg.PolicyPrefix }}{{ $policy.Name }}-RULE{{ $ruleIndex }} {
{{- range $entryIndex, $entry := $ingressRule.Entries }}
{{ $entry.SaddrMatch.Match }} {{ $entry.PortMatch }} {{- if ne ($entry.SaddrMatch.Except | len) 0 }} jump {{ $policy.Name }}-RULE{{ $ruleIndex }}-CIDR{{ $entryIndex }} {{- else }} accept {{- end }};
{{ $entry.SaddrMatch.Match }} {{ $entry.PortMatch }} {{- if ne ($entry.SaddrMatch.Except | len) 0 }} jump {{ $cfg.PolicyPrefix }}{{ $policy.Name }}-RULE{{ $ruleIndex }}-CIDR{{ $entryIndex }} {{- else }} accept {{- end }};

{{- end }}
}

{{- range $entryIndex, $entry := $ingressRule.Entries }}
{{- if ne ($entry.SaddrMatch.Except | len) 0 }}
chain {{ $policy.Name }}-RULE{{ $ruleIndex }}-CIDR{{ $entryIndex }} {
chain {{ $cfg.PolicyPrefix }}{{ $policy.Name }}-RULE{{ $ruleIndex }}-CIDR{{ $entryIndex }} {
{{- range $addr := $entry.SaddrMatch.Except }}
ip saddr {{ $addr }} return;
{{- end}}
Expand Down Expand Up @@ -151,17 +167,33 @@ type nftablesConfig struct {
NATTableName string
NATPostroutingChainName string
NATPreroutingChainName string
PolicyPrefix string
ibot3 marked this conversation as resolved.
Show resolved Hide resolved
FWMarkBits uint32
FWMarkMask uint32
Forwards []nftablesForward
NetworkPolicies map[string]networkPolicy
PolicyAssignments []policyAssignment
ExistingPolicyChains []string
}

type NftablesGenerator struct {
Cfg config.Nftables
}

type nftablesChainListResultChain struct {
Family string `json:"family"`
Table string `json:"table"`
Name string `json:"name"`
}

type nftablesChainListResultEntry struct {
Chain nftablesChainListResultChain `json:"chain,omitempty"`
}

type nftablesChainListResult struct {
Nftables []nftablesChainListResultEntry `json:"nftables"`
}

// Takes a slice of strings and produces a list ready to be used in an nftables rule
// eg. []string{"1", "2", "3"} => "{1,2,3}"
func makeNftablesList(items []string) (string, error) {
Expand Down Expand Up @@ -337,6 +369,45 @@ func mapProtocol(k8sproto corev1.Protocol) (string, error) {
}
}

// Return all chain names of type `filterTableType` in table `filterTableName` that start with `policyPrefix`.
// Uses the `nftCommand`.
func getExistingPolicyChains(nftCommand []string, filterTableName string, filterTableType string, policyPrefix string) ([]string, error) {
// Prepare "list chains" command to get all chains of type filterTableType
cmd := append(nftCommand, "-j", "list", "chains", filterTableType)

klog.V(4).Infof("executing command: %#v", cmd)

cmdObj := exec.Command(cmd[0], cmd[1:]...)
cmdObj.Stderr = os.Stderr

out, err := cmdObj.Output()
if err != nil {
return []string{}, fmt.Errorf("failed to get exiting policy chains via %#v: %s", cmd, err.Error())
}

// Parse result from JSON
var result nftablesChainListResult
err = json.Unmarshal(out, &result)

if err != nil {
return []string{}, fmt.Errorf("could not parse existing policy json: %s", err.Error())
}

var existingChains []string

// Iterate over all returned chains and check if the conditions are met
for _, resultEntry := range result.Nftables {
if resultEntry.Chain.Family == filterTableType &&
resultEntry.Chain.Table == filterTableName &&
strings.HasPrefix(resultEntry.Chain.Name, policyPrefix) {
// Append chain name to list
existingChains = append(existingChains, resultEntry.Chain.Name)
}
}

return existingChains, nil
}

// Generates a config suitable for nftablesTemplate from a LoadBalancer model
func (g *NftablesGenerator) GenerateStructuredConfig(m *model.LoadBalancer) (*nftablesConfig, error) {
result := &nftablesConfig{
Expand All @@ -346,11 +417,13 @@ func (g *NftablesGenerator) GenerateStructuredConfig(m *model.LoadBalancer) (*nf
NATTableName: g.Cfg.NATTableName,
NATPostroutingChainName: g.Cfg.NATPostroutingChainName,
NATPreroutingChainName: g.Cfg.NATPreroutingChainName,
PolicyPrefix: g.Cfg.PolicyPrefix,
FWMarkBits: g.Cfg.FWMarkBits,
FWMarkMask: g.Cfg.FWMarkMask,
Forwards: []nftablesForward{},
NetworkPolicies: map[string]networkPolicy{},
PolicyAssignments: []policyAssignment{},
ExistingPolicyChains: []string{},
}

for _, ingress := range m.Ingress {
Expand Down Expand Up @@ -396,6 +469,15 @@ func (g *NftablesGenerator) GenerateStructuredConfig(m *model.LoadBalancer) (*nf
result.NetworkPolicies[policy.Name] = policy
}

if g.Cfg.LiveReload {
// When live reload is enabled, get all existing policy chain names to delete them in the template
result.ExistingPolicyChains, err = getExistingPolicyChains(
g.Cfg.NftCommand,
result.FilterTableName,
ibot3 marked this conversation as resolved.
Show resolved Hide resolved
result.FilterTableType,
result.PolicyPrefix)
}

return result, nil
}

Expand Down
Loading