bump knftables to v0.0.17

This commit is contained in:
Dan Winship 2024-07-22 17:30:32 -04:00
parent 233bc735b5
commit 4effb05741
8 changed files with 114 additions and 28 deletions

2
go.mod
View File

@ -117,7 +117,7 @@ require (
k8s.io/sample-apiserver v0.0.0 k8s.io/sample-apiserver v0.0.0
k8s.io/system-validators v1.8.0 k8s.io/system-validators v1.8.0
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 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/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/yaml v1.4.0
) )

4
go.sum
View File

@ -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/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 h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 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.17 h1:wGchTyRF/iGTIjd+vRaR1m676HM7jB8soFtyr/148ic=
sigs.k8s.io/knftables v0.0.16/go.mod h1:f/5ZLKYEUPUhVjUCg6l80ACdL7CIIyeL0DxfgojGRTk= 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 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g=
sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0= 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= sigs.k8s.io/kustomize/cmd/config v0.14.1/go.mod h1:Sw1cPsFqh4uYczCWKlidPgMrsffLPCAB+7ytYLlauY4=

2
vendor/modules.txt vendored
View File

@ -1231,7 +1231,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client/proto/client
## explicit; go 1.18 ## explicit; go 1.18
sigs.k8s.io/json sigs.k8s.io/json
sigs.k8s.io/json/internal/golang/encoding/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 ## explicit; go 1.20
sigs.k8s.io/knftables sigs.k8s.io/knftables
# sigs.k8s.io/kustomize/api v0.17.2 # sigs.k8s.io/kustomize/api v0.17.2

View File

@ -1,5 +1,28 @@
# ChangeLog # 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 ## v0.0.16
- Fixed a bug in `Fake.ParseDump()` when using IPv6. (`@npinaeva`) - Fixed a bug in `Fake.ParseDump()` when using IPv6. (`@npinaeva`)

View File

@ -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 using nftables in the way that nftables is supposed to be used (as
opposed to using nftables in a naively-translated-from-iptables way, 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 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.) section on "Possible future changes" below.)
The library is implemented as a wrapper around the `nft` CLI, because The library is implemented as a wrapper around the `nft` CLI, because

27
vendor/sigs.k8s.io/knftables/fake.go generated vendored
View File

@ -34,6 +34,10 @@ type Fake struct {
// Table contains the Interface's table. This will be `nil` until you `tx.Add()` // Table contains the Interface's table. This will be `nil` until you `tx.Add()`
// the table. // the table.
Table *FakeTable 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 // 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 // ListRules is part of Interface
func (fake *Fake) ListRules(_ context.Context, chain string) ([]*Rule, error) { func (fake *Fake) ListRules(_ context.Context, chain string) ([]*Rule, error) {
if fake.Table == nil { 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 { rules := []*Rule{}
return nil, notFoundError("no such chain %q", chain) 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 // ListElements is part of Interface
@ -155,6 +169,7 @@ func (fake *Fake) NewTransaction() *Transaction {
// Run is part of Interface // Run is part of Interface
func (fake *Fake) Run(_ context.Context, tx *Transaction) error { func (fake *Fake) Run(_ context.Context, tx *Transaction) error {
fake.LastTransaction = tx
updatedTable, err := fake.run(tx) updatedTable, err := fake.run(tx)
if err == nil { if err == nil {
fake.Table = updatedTable fake.Table = updatedTable
@ -341,7 +356,7 @@ func (fake *Fake) run(tx *Transaction) (*FakeTable, error) {
return nil, fmt.Errorf("unhandled operation %q", op.verb) return nil, fmt.Errorf("unhandled operation %q", op.verb)
} }
case *Element: case *Element:
if len(obj.Value) == 0 { if obj.Set != "" {
existingSet := updatedTable.Sets[obj.Set] existingSet := updatedTable.Sets[obj.Set]
if existingSet == nil { if existingSet == nil {
return nil, notFoundError("no such set %q", obj.Set) return nil, notFoundError("no such set %q", obj.Set)

View File

@ -17,11 +17,13 @@ limitations under the License.
package knftables package knftables
import ( import (
"bytes"
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "strings"
"sync"
) )
// Interface is an interface for running nftables commands against a given family and table. // 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 and no error.
List(ctx context.Context, objectType string) ([]string, 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 // 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 // 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 // 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 { type realNFTables struct {
nftContext nftContext
bufferMutex sync.Mutex
buffer *bytes.Buffer
exec execer exec execer
path string path string
} }
@ -84,8 +90,8 @@ func newInternal(family Family, table string, execer execer) (Interface, error)
family: family, family: family,
table: table, table: table,
}, },
buffer: &bytes.Buffer{},
exec: execer, exec: execer,
} }
nft.path, err = nft.exec.LookPath("nft") nft.path, err = nft.exec.LookPath("nft")
@ -135,34 +141,42 @@ func (nft *realNFTables) NewTransaction() *Transaction {
// Run is part of Interface // Run is part of Interface
func (nft *realNFTables) Run(ctx context.Context, tx *Transaction) error { func (nft *realNFTables) Run(ctx context.Context, tx *Transaction) error {
nft.bufferMutex.Lock()
defer nft.bufferMutex.Unlock()
if tx.err != nil { if tx.err != nil {
return tx.err return tx.err
} }
buf, err := tx.asCommandBuf() nft.buffer.Reset()
err := tx.populateCommandBuf(nft.buffer)
if err != nil { if err != nil {
return err return err
} }
cmd := exec.CommandContext(ctx, nft.path, "-f", "-") cmd := exec.CommandContext(ctx, nft.path, "-f", "-")
cmd.Stdin = buf cmd.Stdin = nft.buffer
_, err = nft.exec.Run(cmd) _, err = nft.exec.Run(cmd)
return err return err
} }
// Check is part of Interface // Check is part of Interface
func (nft *realNFTables) Check(ctx context.Context, tx *Transaction) error { func (nft *realNFTables) Check(ctx context.Context, tx *Transaction) error {
nft.bufferMutex.Lock()
defer nft.bufferMutex.Unlock()
if tx.err != nil { if tx.err != nil {
return tx.err return tx.err
} }
buf, err := tx.asCommandBuf() nft.buffer.Reset()
err := tx.populateCommandBuf(nft.buffer)
if err != nil { if err != nil {
return err return err
} }
cmd := exec.CommandContext(ctx, nft.path, "--check", "-f", "-") cmd := exec.CommandContext(ctx, nft.path, "--check", "-f", "-")
cmd.Stdin = buf cmd.Stdin = nft.buffer
_, err = nft.exec.Run(cmd) _, err = nft.exec.Run(cmd)
return err return err
} }
@ -298,7 +312,13 @@ func (nft *realNFTables) List(ctx context.Context, objectType string) ([]string,
// ListRules is part of Interface // ListRules is part of Interface
func (nft *realNFTables) ListRules(ctx context.Context, chain string) ([]*Rule, error) { 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) out, err := nft.exec.Run(cmd)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to run nft: %w", err) 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)) rules := make([]*Rule, 0, len(jsonRules))
for _, jsonRule := range 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{ rule := &Rule{
Chain: chain, Chain: parentChain,
} }
// handle is written as an integer in nft's output, but json.Unmarshal // 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 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. // converting numeric or "verdict" values to strings.
func parseElementValue(json interface{}) ([]string, error) { func parseElementValue(json interface{}) ([]string, error) {
// json can be: // json can be:
@ -413,6 +437,14 @@ func parseElementValue(json interface{}) ([]string, error) {
// //
// - a single number, e.g. 80 // - 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 // - a concatenation, expressed as an object containing an array of simple
// values: // values:
// { // {
@ -452,6 +484,17 @@ func parseElementValue(json interface{}) ([]string, error) {
} }
} }
return vals, nil 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 { } else if len(val) == 1 {
var verdict string var verdict string
// We just checked that len(val) == 1, so this loop body will only // We just checked that len(val) == 1, so this loop body will only

View File

@ -19,7 +19,6 @@ package knftables
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
) )
// Transaction represents an nftables transaction // Transaction represents an nftables transaction
@ -48,17 +47,16 @@ const (
flushVerb verb = "flush" flushVerb verb = "flush"
) )
// asCommandBuf returns the transaction as an io.Reader that outputs a series of nft commands // populateCommandBuf populates the transaction as series of nft commands to the given bytes.Buffer.
func (tx *Transaction) asCommandBuf() (io.Reader, error) { func (tx *Transaction) populateCommandBuf(buf *bytes.Buffer) error {
if tx.err != nil { if tx.err != nil {
return nil, tx.err return tx.err
} }
buf := &bytes.Buffer{}
for _, op := range tx.operations { for _, op := range tx.operations {
op.obj.writeOperation(op.verb, tx.nftContext, buf) 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 // 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() 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) { func (tx *Transaction) operation(verb verb, obj Object) {
if tx.err != nil { if tx.err != nil {
return return