Refactor: enhance blocklist management with modular NFTables blocklist implementation, adding support for rule reloading and element replacements
This commit is contained in:
@@ -8,14 +8,15 @@ import (
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/entity"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/repository"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type newBlocklist func(name string) (block.Blocklist, error)
|
||||
|
||||
type Blocklist interface {
|
||||
NftReload(newBlocklist newBlocklist) error
|
||||
Names() []string
|
||||
NftReload(blocks map[string]block.Blocklist) error
|
||||
Run()
|
||||
Close() error
|
||||
}
|
||||
@@ -54,34 +55,38 @@ func New(config Config, ctx context.Context, logger log.Logger) (Blocklist, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *blocklist) NftReload(newBlocklist newBlocklist) error {
|
||||
b.logger.Debug("Reload blocklist")
|
||||
func (b *blocklist) Names() []string {
|
||||
names := []string{}
|
||||
for _, source := range b.Sources {
|
||||
b.logger.Debug(fmt.Sprintf("Reload blocklist from %s", source.Name))
|
||||
if source.Name == "" {
|
||||
continue
|
||||
if source.Name != "" {
|
||||
names = append(names, source.Name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
nftBlocklist, err := newBlocklist("blocklist_" + source.Name)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to create blocklist: %s", err))
|
||||
continue
|
||||
}
|
||||
func (b *blocklist) NftReload(blocks map[string]block.Blocklist) error {
|
||||
b.logger.Debug("Reload blocklist")
|
||||
|
||||
b.mu.Lock()
|
||||
b.nftBlocklists[source.Name] = nftBlocklist
|
||||
b.mu.Unlock()
|
||||
b.mu.Lock()
|
||||
b.nftBlocklists = blocks
|
||||
b.mu.Unlock()
|
||||
|
||||
if listEntity, err := b.blocklistRepository.Get(source.Name); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist %s: %s", source.Name, err))
|
||||
} else if listEntity.IsFresh(source.Interval) {
|
||||
if err := nftBlocklist.ReplaceElementsIPv4(listEntity.IPsV4); len(listEntity.IPsV4) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv4): %s", err))
|
||||
}
|
||||
|
||||
if err := nftBlocklist.ReplaceElementsIPv6(listEntity.IPsV6); len(listEntity.IPsV6) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv6): %s", err))
|
||||
for _, source := range b.Sources {
|
||||
if nftBlocklist, ok := b.nftBlocklists[source.Name]; ok {
|
||||
if listEntity, err := b.blocklistRepository.Get(source.Name); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist %s: %s", source.Name, err))
|
||||
} else if listEntity.IsFresh(source.Interval) {
|
||||
if err := nftBlocklist.ReplaceElementsIPv4(listEntity.IPsV4); len(listEntity.IPsV4) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv4): %s", err))
|
||||
}
|
||||
|
||||
if err := nftBlocklist.ReplaceElementsIPv6(listEntity.IPsV6); len(listEntity.IPsV6) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv6): %s", err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.logger.Error(fmt.Sprintf("NFTables sets blocklist %s not found", source.Name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package blocklist
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
|
||||
type FalseBlocklist struct {
|
||||
}
|
||||
|
||||
@@ -7,7 +9,11 @@ func NewFalseBlocklist() Blocklist {
|
||||
return &FalseBlocklist{}
|
||||
}
|
||||
|
||||
func (b *FalseBlocklist) NftReload(_ newBlocklist) error {
|
||||
func (b *FalseBlocklist) Names() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (b *FalseBlocklist) NftReload(_ map[string]block.Blocklist) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
)
|
||||
|
||||
type Blocklist interface {
|
||||
// ReplaceElementsIPv4 Replacing IP addresses.
|
||||
ReplaceElementsIPv4(ips []string) error
|
||||
|
||||
// ReplaceElementsIPv6 Replacing IP addresses.
|
||||
ReplaceElementsIPv6(ips []string) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error
|
||||
}
|
||||
|
||||
type blocklist struct {
|
||||
listIPv4 List
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewBlocklist(nft nftFirewall.NFT, builder nft.BatchBuilder, family family.Type, table string, name string) (Blocklist, error) {
|
||||
params := "type ipv4_addr; flags interval; auto-merge;"
|
||||
listName := name + "_ip4"
|
||||
listIPv4, err := newList(nft, builder, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr; flags interval; auto-merge;"
|
||||
listName = name + "_ip6"
|
||||
listIPv6, err := newList(nft, builder, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blocklist{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElementsIPv4(ips []string) error {
|
||||
return l.listIPv4.ReplaceElements(ips)
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElementsIPv6(ips []string) error {
|
||||
return l.listIPv6.ReplaceElements(ips)
|
||||
}
|
||||
|
||||
func (l *blocklist) AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error {
|
||||
rule := "ip saddr @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rule = "ip6 saddr @" + l.listIPv6.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -15,6 +15,7 @@ type List interface {
|
||||
AddBatchElement(builder nft.BatchBuilder, element string) error
|
||||
DeleteElement(element string) error
|
||||
ReplaceElements(elements []string) error
|
||||
ReplaceElementsWithSaveNFTFile(elements []string, pathFile string) error
|
||||
}
|
||||
|
||||
type list struct {
|
||||
@@ -72,6 +73,18 @@ func (l *list) DeleteElement(element string) error {
|
||||
}
|
||||
|
||||
func (l *list) ReplaceElements(elements []string) error {
|
||||
return l.replaceElements(elements, func(builder nft.BatchBuilder) error {
|
||||
return l.nft.RunBatch(builder)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *list) ReplaceElementsWithSaveNFTFile(elements []string, pathFile string) error {
|
||||
return l.replaceElements(elements, func(builder nft.BatchBuilder) error {
|
||||
return l.nft.RunBatchAndMoveFile(builder, pathFile)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *list) replaceElements(elements []string, run func(builder nft.BatchBuilder) error) error {
|
||||
batchBuilder, err := l.nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -85,7 +98,7 @@ func (l *list) ReplaceElements(elements []string) error {
|
||||
}
|
||||
|
||||
if len(elements) == 0 {
|
||||
return nil
|
||||
return run(batchBuilder)
|
||||
}
|
||||
|
||||
command := []string{
|
||||
@@ -93,5 +106,9 @@ func (l *list) ReplaceElements(elements []string) error {
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", strings.Join(elements, ",")),
|
||||
}
|
||||
return batchBuilder.Command().Run(command...)
|
||||
if err := batchBuilder.Command().Run(command...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return run(batchBuilder)
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ type Table interface {
|
||||
type BlockList interface {
|
||||
ListIP() block.ListIP
|
||||
ListIPWithPort() block.ListIPWithPort
|
||||
Blocks() map[string]block.Blocklist
|
||||
}
|
||||
|
||||
type table struct {
|
||||
@@ -38,12 +39,14 @@ func (t *table) BlockList() BlockList {
|
||||
type blockList struct {
|
||||
listIP block.ListIP
|
||||
listIPWithPort block.ListIPWithPort
|
||||
blocks map[string]block.Blocklist
|
||||
}
|
||||
|
||||
func NewBlockList(listIP block.ListIP, listIPWithPort block.ListIPWithPort) BlockList {
|
||||
func NewBlockList(listIP block.ListIP, listIPWithPort block.ListIPWithPort, blocks map[string]block.Blocklist) BlockList {
|
||||
return &blockList{
|
||||
listIP: listIP,
|
||||
listIPWithPort: listIPWithPort,
|
||||
blocks: blocks,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,3 +57,7 @@ func (b *blockList) ListIP() block.ListIP {
|
||||
func (b *blockList) ListIPWithPort() block.ListIPWithPort {
|
||||
return b.listIPWithPort
|
||||
}
|
||||
|
||||
func (b *blockList) Blocks() map[string]block.Blocklist {
|
||||
return b.blocks
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
nftTable "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/table"
|
||||
)
|
||||
|
||||
func (r *reload) blockList(builder nft.BatchBuilder, beforeLocalInput chain.Chain) (nftTable.BlockList, error) {
|
||||
func (r *reload) blockList(builder nft.BatchBuilder, beforeLocalInput chain.Chain, blocks map[string]block.Blocklist) (nftTable.BlockList, error) {
|
||||
listBlockedIP, err := block.NewListIP(r.nft, builder, r.table.family, r.table.name, "blocked_ip")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -26,5 +28,28 @@ func (r *reload) blockList(builder nft.BatchBuilder, beforeLocalInput chain.Chai
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nftTable.NewBlockList(listBlockedIP, listBlockedIPWithPort), nil
|
||||
return nftTable.NewBlockList(listBlockedIP, listBlockedIPWithPort, blocks), nil
|
||||
}
|
||||
|
||||
func (r *reload) moduleBlockList(builder nft.BatchBuilder, afterLocalInput chain.Chain, blockListNames []string) (blocks map[string]block.Blocklist, err error) {
|
||||
r.logger.Debug("Reload blocklist")
|
||||
blocks = make(map[string]block.Blocklist)
|
||||
for _, blockListName := range blockListNames {
|
||||
if blockListName == "" {
|
||||
continue
|
||||
}
|
||||
r.logger.Debug(fmt.Sprintf("Reload blocklist from %s", blockListName))
|
||||
blockList, err := block.NewBlocklist(r.nft, builder, r.table.family, r.table.name, "blocklist_"+blockListName)
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to create blocklist: %s", err))
|
||||
continue
|
||||
}
|
||||
if err := blockList.AddRuleToChain(afterLocalInput.AddRule, "drop"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule to chain: %s", err))
|
||||
continue
|
||||
}
|
||||
blocks[blockListName] = blockList
|
||||
}
|
||||
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
|
||||
)
|
||||
|
||||
func (r *reload) input(builder nft.BatchBuilder, packetfilter chain.PacketFilter) (nftTable.BlockList, error) {
|
||||
func (r *reload) input(builder nft.BatchBuilder, packetfilter chain.PacketFilter, blockListNames []string) (nftTable.BlockList, error) {
|
||||
r.logger.Debug("Reloading input chain")
|
||||
|
||||
batchInput, err := chain.NewBatchInput(
|
||||
@@ -81,7 +81,12 @@ func (r *reload) input(builder nft.BatchBuilder, packetfilter chain.PacketFilter
|
||||
}
|
||||
}
|
||||
|
||||
return r.blockList(builder, beforeLocalInput)
|
||||
blocks, err := r.moduleBlockList(builder, afterLocalInput, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.blockList(builder, beforeLocalInput, blocks)
|
||||
}
|
||||
|
||||
func (r *reload) reloadInputDnsNs(batchInput chain.Chain) error {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
type Reload interface {
|
||||
Run() (dataTable.Table, error)
|
||||
Run(blockListNames []string) (dataTable.Table, error)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
@@ -43,7 +43,7 @@ func New(nft nftFirewall.NFT, logger log.Logger, config *config.Config) Reload {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reload) Run() (dataTable.Table, error) {
|
||||
func (r *reload) Run(blockListNames []string) (dataTable.Table, error) {
|
||||
var dockerChains firewall.NFTDockerChains
|
||||
if r.config.Options.DockerSupport {
|
||||
dockerChains = firewall.NewNFTChains(r.nft, r.table.family, r.table.name)
|
||||
@@ -67,7 +67,7 @@ func (r *reload) Run() (dataTable.Table, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockList, err := r.input(batchBuilder, packetFilter)
|
||||
blockList, err := r.input(batchBuilder, packetFilter, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user