diff --git a/go.mod b/go.mod index 544d4c295fc..345fc898eb6 100644 --- a/go.mod +++ b/go.mod @@ -117,7 +117,7 @@ require ( k8s.io/sample-apiserver v0.0.0 k8s.io/system-validators v1.8.0 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 - sigs.k8s.io/knftables v0.0.16 + sigs.k8s.io/knftables v0.0.17 sigs.k8s.io/structured-merge-diff/v4 v4.4.1 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index 2dae66d1ecb..7030da830b4 100644 --- a/go.sum +++ b/go.sum @@ -970,8 +970,8 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsA sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/knftables v0.0.16 h1:ZpTfNsjnidgoXdxxzcZLdSctqkpSO3QB3jo3zQ4PXqM= -sigs.k8s.io/knftables v0.0.16/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= +sigs.k8s.io/knftables v0.0.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic= +sigs.k8s.io/knftables v0.0.17/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= sigs.k8s.io/kustomize/cmd/config v0.14.1/go.mod h1:Sw1cPsFqh4uYczCWKlidPgMrsffLPCAB+7ytYLlauY4= diff --git a/vendor/modules.txt b/vendor/modules.txt index 8e49615368b..8a81ddf1a68 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1231,7 +1231,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client ## explicit; go 1.18 sigs.k8s.io/json sigs.k8s.io/json/internal/golang/encoding/json -# sigs.k8s.io/knftables v0.0.16 +# sigs.k8s.io/knftables v0.0.17 ## explicit; go 1.20 sigs.k8s.io/knftables # sigs.k8s.io/kustomize/api v0.17.2 diff --git a/vendor/sigs.k8s.io/knftables/CHANGELOG.md b/vendor/sigs.k8s.io/knftables/CHANGELOG.md index 33bb8ef172c..4f1dc3a3542 100644 --- a/vendor/sigs.k8s.io/knftables/CHANGELOG.md +++ b/vendor/sigs.k8s.io/knftables/CHANGELOG.md @@ -1,5 +1,28 @@ # ChangeLog +## v0.0.17 + +- `ListRules()` now accepts `""` for the chain name, meaning to list + all rules in the table. (`@caseydavenport`) + +- `ListElements()` now handles elements with prefix/CIDR values (e.g., + `"192.168.0.0/16"`; these are represented specially in the JSON + format and the old code didn't handle them). (`@caseydavenport`) + +- Added `NumOperations()` to `Transaction` (which lets you figure out + belatedly whether you added anything to the transaction or not, and + could also be used for metrics). (`@fasaxc`) + +- `knftables.Interface` now reuses the same `bytes.Buffer` for each + call to `nft` rather than constructing a new one each time, saving + time and memory. (`@aroradaman`) + +- Fixed map element deletion in `knftables.Fake` to not mistakenly + require that you fill in the `.Value` of the element. (`@npinaeva`) + +- Added `Fake.LastTransaction`, to retrieve the most-recently-executed + transaction. (`@npinaeva`) + ## v0.0.16 - Fixed a bug in `Fake.ParseDump()` when using IPv6. (`@npinaeva`) diff --git a/vendor/sigs.k8s.io/knftables/README.md b/vendor/sigs.k8s.io/knftables/README.md index c2139b04ea2..794b15bb71d 100644 --- a/vendor/sigs.k8s.io/knftables/README.md +++ b/vendor/sigs.k8s.io/knftables/README.md @@ -7,9 +7,11 @@ specifically focuses on supporting Kubernetes components which are using nftables in the way that nftables is supposed to be used (as opposed to using nftables in a naively-translated-from-iptables way, or using nftables to do totally valid things that aren't the sorts of -things Kubernetes components are likely to need to do). +things Kubernetes components are likely to need to do; see the +"[iptables porting](./docs/iptables-porting.md)" doc for more thoughts +on porting old iptables-based components to nftables.) -It is still under development and is not yet API stable. (See the +knftables is still under development and is not yet API stable. (See the section on "Possible future changes" below.) The library is implemented as a wrapper around the `nft` CLI, because diff --git a/vendor/sigs.k8s.io/knftables/fake.go b/vendor/sigs.k8s.io/knftables/fake.go index 694c9709c87..584c27a540a 100644 --- a/vendor/sigs.k8s.io/knftables/fake.go +++ b/vendor/sigs.k8s.io/knftables/fake.go @@ -34,6 +34,10 @@ type Fake struct { // Table contains the Interface's table. This will be `nil` until you `tx.Add()` // the table. Table *FakeTable + + // LastTransaction is the last transaction passed to Run(). It will remain set until the + // next time Run() is called. (It is not affected by Check().) + LastTransaction *Transaction } // FakeTable wraps Table for the Fake implementation @@ -120,13 +124,23 @@ func (fake *Fake) List(_ context.Context, objectType string) ([]string, error) { // ListRules is part of Interface func (fake *Fake) ListRules(_ context.Context, chain string) ([]*Rule, error) { if fake.Table == nil { - return nil, notFoundError("no such chain %q", chain) + return nil, notFoundError("no such table %q", fake.table) } - ch := fake.Table.Chains[chain] - if ch == nil { - return nil, notFoundError("no such chain %q", chain) + + rules := []*Rule{} + if chain == "" { + // Include all rules across all chains. + for _, ch := range fake.Table.Chains { + rules = append(rules, ch.Rules...) + } + } else { + ch := fake.Table.Chains[chain] + if ch == nil { + return nil, notFoundError("no such chain %q", chain) + } + rules = append(rules, ch.Rules...) } - return ch.Rules, nil + return rules, nil } // ListElements is part of Interface @@ -155,6 +169,7 @@ func (fake *Fake) NewTransaction() *Transaction { // Run is part of Interface func (fake *Fake) Run(_ context.Context, tx *Transaction) error { + fake.LastTransaction = tx updatedTable, err := fake.run(tx) if err == nil { fake.Table = updatedTable @@ -341,7 +356,7 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) { return nil, fmt.Errorf("unhandled operation %q", op.verb) } case *Element: - if len(obj.Value) == 0 { + if obj.Set != "" { existingSet := updatedTable.Sets[obj.Set] if existingSet == nil { return nil, notFoundError("no such set %q", obj.Set) diff --git a/vendor/sigs.k8s.io/knftables/nftables.go b/vendor/sigs.k8s.io/knftables/nftables.go index 7063d292791..8cb343806a3 100644 --- a/vendor/sigs.k8s.io/knftables/nftables.go +++ b/vendor/sigs.k8s.io/knftables/nftables.go @@ -17,11 +17,13 @@ limitations under the License. package knftables import ( + "bytes" "context" "encoding/json" "fmt" "os/exec" "strings" + "sync" ) // Interface is an interface for running nftables commands against a given family and table. @@ -43,7 +45,8 @@ type Interface interface { // list and no error. List(ctx context.Context, objectType string) ([]string, error) - // ListRules returns a list of the rules in a chain, in order. Note that at the + // ListRules returns a list of the rules in a chain, in order. If no chain name is + // specified, then all rules within the table will be returned. Note that at the // present time, the Rule objects will have their `Comment` and `Handle` fields // filled in, but *not* the actual `Rule` field. So this can only be used to find // the handles of rules if they have unique comments to recognize them by, or if @@ -70,6 +73,9 @@ type nftContext struct { type realNFTables struct { nftContext + bufferMutex sync.Mutex + buffer *bytes.Buffer + exec execer path string } @@ -84,8 +90,8 @@ func newInternal(family Family, table string, execer execer) (Interface, error) family: family, table: table, }, - - exec: execer, + buffer: &bytes.Buffer{}, + exec: execer, } nft.path, err = nft.exec.LookPath("nft") @@ -135,34 +141,42 @@ func (nft *realNFTables) NewTransaction() *Transaction { // Run is part of Interface func (nft *realNFTables) Run(ctx context.Context, tx *Transaction) error { + nft.bufferMutex.Lock() + defer nft.bufferMutex.Unlock() + if tx.err != nil { return tx.err } - buf, err := tx.asCommandBuf() + nft.buffer.Reset() + err := tx.populateCommandBuf(nft.buffer) if err != nil { return err } cmd := exec.CommandContext(ctx, nft.path, "-f", "-") - cmd.Stdin = buf + cmd.Stdin = nft.buffer _, err = nft.exec.Run(cmd) return err } // Check is part of Interface func (nft *realNFTables) Check(ctx context.Context, tx *Transaction) error { + nft.bufferMutex.Lock() + defer nft.bufferMutex.Unlock() + if tx.err != nil { return tx.err } - buf, err := tx.asCommandBuf() + nft.buffer.Reset() + err := tx.populateCommandBuf(nft.buffer) if err != nil { return err } cmd := exec.CommandContext(ctx, nft.path, "--check", "-f", "-") - cmd.Stdin = buf + cmd.Stdin = nft.buffer _, err = nft.exec.Run(cmd) return err } @@ -298,7 +312,13 @@ func (nft *realNFTables) List(ctx context.Context, objectType string) ([]string, // ListRules is part of Interface func (nft *realNFTables) ListRules(ctx context.Context, chain string) ([]*Rule, error) { - cmd := exec.CommandContext(ctx, nft.path, "--json", "list", "chain", string(nft.family), nft.table, chain) + // If no chain is given, return all rules from within the table. + var cmd *exec.Cmd + if chain == "" { + cmd = exec.CommandContext(ctx, nft.path, "--json", "list", "table", string(nft.family), nft.table) + } else { + cmd = exec.CommandContext(ctx, nft.path, "--json", "list", "chain", string(nft.family), nft.table, chain) + } out, err := nft.exec.Run(cmd) if err != nil { return nil, fmt.Errorf("failed to run nft: %w", err) @@ -311,8 +331,12 @@ func (nft *realNFTables) ListRules(ctx context.Context, chain string) ([]*Rule, rules := make([]*Rule, 0, len(jsonRules)) for _, jsonRule := range jsonRules { + parentChain, ok := jsonVal[string](jsonRule, "chain") + if !ok { + return nil, fmt.Errorf("unexpected JSON output from nft (rule with no chain)") + } rule := &Rule{ - Chain: chain, + Chain: parentChain, } // handle is written as an integer in nft's output, but json.Unmarshal @@ -404,7 +428,7 @@ func (nft *realNFTables) ListElements(ctx context.Context, objectType, name stri return elements, nil } -// parseElementValue parses a JSON element key/value, handling concatenations, and +// parseElementValue parses a JSON element key/value, handling concatenations, prefixes, and // converting numeric or "verdict" values to strings. func parseElementValue(json interface{}) ([]string, error) { // json can be: @@ -413,6 +437,14 @@ func parseElementValue(json interface{}) ([]string, error) { // // - a single number, e.g. 80 // + // - a prefix, expressed as an object: + // { + // "prefix": { + // "addr": "192.168.0.0", + // "len": 16, + // } + // } + // // - a concatenation, expressed as an object containing an array of simple // values: // { @@ -452,6 +484,17 @@ func parseElementValue(json interface{}) ([]string, error) { } } return vals, nil + } else if prefix, _ := jsonVal[map[string]interface{}](val, "prefix"); prefix != nil { + // For prefix-type elements, return the element in CIDR representation. + addr, ok := jsonVal[string](prefix, "addr") + if !ok { + return nil, fmt.Errorf("could not parse 'addr' value as string: %q", prefix) + } + length, ok := jsonVal[float64](prefix, "len") + if !ok { + return nil, fmt.Errorf("could not parse 'len' value as number: %q", prefix) + } + return []string{fmt.Sprintf("%s/%d", addr, int(length))}, nil } else if len(val) == 1 { var verdict string // We just checked that len(val) == 1, so this loop body will only diff --git a/vendor/sigs.k8s.io/knftables/transaction.go b/vendor/sigs.k8s.io/knftables/transaction.go index 75ea2be5318..3063637ada9 100644 --- a/vendor/sigs.k8s.io/knftables/transaction.go +++ b/vendor/sigs.k8s.io/knftables/transaction.go @@ -19,7 +19,6 @@ package knftables import ( "bytes" "fmt" - "io" ) // Transaction represents an nftables transaction @@ -48,17 +47,16 @@ const ( flushVerb verb = "flush" ) -// asCommandBuf returns the transaction as an io.Reader that outputs a series of nft commands -func (tx *Transaction) asCommandBuf() (io.Reader, error) { +// populateCommandBuf populates the transaction as series of nft commands to the given bytes.Buffer. +func (tx *Transaction) populateCommandBuf(buf *bytes.Buffer) error { if tx.err != nil { - return nil, tx.err + return tx.err } - buf := &bytes.Buffer{} for _, op := range tx.operations { op.obj.writeOperation(op.verb, tx.nftContext, buf) } - return buf, nil + return nil } // String returns the transaction as a string containing the nft commands; if there is @@ -76,6 +74,11 @@ func (tx *Transaction) String() string { return buf.String() } +// NumOperations returns the number of operations queued in the transaction. +func (tx *Transaction) NumOperations() int { + return len(tx.operations) +} + func (tx *Transaction) operation(verb verb, obj Object) { if tx.err != nil { return