diff --git a/.github/workflows/pr_tests.yml b/.github/workflows/pr_tests.yml index fb5a6894..56827099 100644 --- a/.github/workflows/pr_tests.yml +++ b/.github/workflows/pr_tests.yml @@ -18,10 +18,11 @@ jobs: - uses: actions/checkout@v3 - name: Build tests run: cd src && go test -c rol/tests -o ./tests/rol.test - - name: Setup CAP_NET_ADMIN for compiled tests + - name: Setup CAP_NET_RAW and CAP_NET_ADMIN for compiled tests if: matrix.os == 'ubuntu-22.04' run: | sudo setcap cap_net_admin+ep ./src/tests/rol.test + sudo setcap "cap_net_raw+ep cap_net_admin+ep" /usr/sbin/xtables-nft-multi - name: Run tests run: | cd src/tests && ./rol.test -test.v diff --git a/docs/plantuml/controllers/HostNetworkTrafficGinController.puml b/docs/plantuml/controllers/HostNetworkTrafficGinController.puml new file mode 100644 index 00000000..8fbd15a5 --- /dev/null +++ b/docs/plantuml/controllers/HostNetworkTrafficGinController.puml @@ -0,0 +1,41 @@ +@startuml + +!include ../services/HostNetworkService.puml +remove HostNetworkBridgeDto +remove HostNetworkBridgeUpdateDto +remove HostNetworkBridgeCreateDto +remove HostNetworkBridgeBaseDto +remove HostNetworkVlanDto +remove HostNetworkVlanUpdateDto +remove HostNetworkVlanCreateDto + +package controllers { + class HostNetworkTrafficGinController { + -service *services.HostNetworkService + -- + -logger *logrus.Logger + -- + +GetTableRules(ctx *gin.Context) + -- + +Create(ctx *gin.Context) + -- + +Delete(ctx *gin.Context) + } + + note left of HostNetworkTrafficGinController::GetTableRules + Get selected netfilter table rules + end note + + note left of HostNetworkTrafficGinController::Create + Create new traffic rule in specified table + end note + + note left of HostNetworkTrafficGinController::Delete + Delete netfilter traffic rule in specified table + end note + + + HostNetworkService -up- HostNetworkTrafficGinController::service +} + +@enduml diff --git a/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleBaseDto.puml b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleBaseDto.puml new file mode 100644 index 00000000..c9f5af28 --- /dev/null +++ b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleBaseDto.puml @@ -0,0 +1,16 @@ +@startuml + +package dtos { + class HostNetworkTrafficRuleBaseDto { + +Chain string + -- + +Target string + -- + +Source string + -- + +Destination string + } +} + + +@enduml \ No newline at end of file diff --git a/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleCreateDto.puml b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleCreateDto.puml new file mode 100644 index 00000000..16306f7d --- /dev/null +++ b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleCreateDto.puml @@ -0,0 +1,10 @@ +@startuml +!include HostNetworkTrafficRuleBaseDto.puml + +package dtos { + class HostNetworkTrafficRuleCreateDto { + } + + HostNetworkTrafficRuleCreateDto --* HostNetworkTrafficRuleBaseDto +} +@enduml \ No newline at end of file diff --git a/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDeleteDto.puml b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDeleteDto.puml new file mode 100644 index 00000000..14445734 --- /dev/null +++ b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDeleteDto.puml @@ -0,0 +1,10 @@ +@startuml +!include HostNetworkTrafficRuleBaseDto.puml + +package dtos { + class HostNetworkTrafficRuleDeleteDto { + } + + HostNetworkTrafficRuleDeleteDto --* HostNetworkTrafficRuleBaseDto +} +@enduml \ No newline at end of file diff --git a/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDto.puml b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDto.puml new file mode 100644 index 00000000..f608e332 --- /dev/null +++ b/docs/plantuml/dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDto.puml @@ -0,0 +1,12 @@ +@startuml + +!include HostNetworkTrafficRuleBaseDto.puml + +package dtos { + class HostNetworkTrafficRuleDto { + } + + HostNetworkTrafficRuleDto --* HostNetworkTrafficRuleBaseDto +} + +@enduml diff --git a/docs/plantuml/entities/HostNetworkConfig.puml b/docs/plantuml/entities/HostNetworkConfig.puml index 3488c14e..f6b0d3ca 100644 --- a/docs/plantuml/entities/HostNetworkConfig.puml +++ b/docs/plantuml/entities/HostNetworkConfig.puml @@ -3,6 +3,7 @@ !include HostNetworkDevice.puml !include HostNetworkVlan.puml !include HostNetworkBridge.puml +!include HostNetworkTrafficRule.puml package domain { class HostNetworkConfig { @@ -11,11 +12,42 @@ package domain { +Vlans []HostNetworkVlan -- +Bridges []HostNetworkBridge + -- + +TrafficRules TrafficRules + } + + class TrafficRules { + +Filter []HostNetworkTrafficRule + -- + +NAT []HostNetworkTrafficRule + -- + +Mangle []HostNetworkTrafficRule + -- + +Raw []HostNetworkTrafficRule + -- + +Security []HostNetworkTrafficRule } HostNetworkConfig::Devices -- HostNetworkDevice HostNetworkConfig::Vlans -- HostNetworkVlan HostNetworkConfig::Bridges -- HostNetworkBridge + HostNetworkConfig::TrafficRules -- TrafficRules + + note as NetworkTrafficRuleNote + Since traffic rules are stored in the .yaml config + we need to split all netfilter tables into class fields + end note + + TrafficRules .l[hidden]. NetworkTrafficRuleNote + + + TrafficRules::Filter .. NetworkTrafficRuleNote + TrafficRules::NAT .. NetworkTrafficRuleNote + TrafficRules::Mangle .. NetworkTrafficRuleNote + TrafficRules::Raw .. NetworkTrafficRuleNote + TrafficRules::Security .. NetworkTrafficRuleNote + + NetworkTrafficRuleNote ..> HostNetworkTrafficRule } @enduml diff --git a/docs/plantuml/entities/HostNetworkTrafficRule.puml b/docs/plantuml/entities/HostNetworkTrafficRule.puml new file mode 100644 index 00000000..bc4d14e5 --- /dev/null +++ b/docs/plantuml/entities/HostNetworkTrafficRule.puml @@ -0,0 +1,15 @@ +@startuml Project + +package domain { + class HostNetworkTrafficRule { + Chain string + -- + Target string + -- + Source string + -- + Destination string + } +} + +@enduml diff --git a/docs/plantuml/interfaces/IHostNetworkManager.puml b/docs/plantuml/interfaces/IHostNetworkManager.puml index f3ed2b90..6b5daa50 100644 --- a/docs/plantuml/interfaces/IHostNetworkManager.puml +++ b/docs/plantuml/interfaces/IHostNetworkManager.puml @@ -24,6 +24,14 @@ package app { -- +AddrDelete(linkName string, addr net.IPNet) error -- + +CreateTrafficRule(table string, rule HostNetworkTrafficRule) (HostNetworkTrafficRule, error) + -- + +DeleteTrafficRule(table string, rule HostNetworkTrafficRule) error + -- + +GetChainRules(table string, chain string) ([]HostNetworkTrafficRule, error) + -- + +GetTableRules(table string) ([]HostNetworkTrafficRule, error) + -- +SaveConfiguration() error -- +RestoreFromBackup() error @@ -73,6 +81,22 @@ package app { Delete ip address for network interface end note + note left of IHostNetworkManager::CreateTrafficRule + Create netfilter traffic rule for specified table + end note + + note left of IHostNetworkManager::DeleteTrafficRule + Delete netfilter traffic rule in specified table + end note + + note left of IHostNetworkManager::GetChainRules + Get selected netfilter chain rules at specified table + end note + + note left of IHostNetworkManager::GetTableRules + Get specified netfilter table rules + end note + note left of IHostNetworkManager::SaveConfiguration Save current host network configuration to the storage end note diff --git a/docs/plantuml/services/HostNetworkService.puml b/docs/plantuml/services/HostNetworkService.puml index e61ed551..3da916a6 100644 --- a/docs/plantuml/services/HostNetworkService.puml +++ b/docs/plantuml/services/HostNetworkService.puml @@ -6,6 +6,9 @@ !include ../dto/HostNetworkBridge/HostNetworkBridgeDto.puml !include ../dto/HostNetworkBridge/HostNetworkBridgeCreateDto.puml !include ../dto/HostNetworkBridge/HostNetworkBridgeUpdateDto.puml +!include ../dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDto.puml +!include ../dto/HostNetworkTrafficRule/HostNetworkTrafficRuleCreateDto.puml +!include ../dto/HostNetworkTrafficRule/HostNetworkTrafficRuleDeleteDto.puml !include ../managers/HostNetworkManager.puml @@ -32,6 +35,14 @@ package app { +UpdateBridge(bridgeName string, updateDto dtos.HostNetworkBridgeUpdateDto) (dtos.HostNetworkBridgeDto, error) -- +DeleteBridge(bridgeName string) error + -- + +CreateTrafficRule(table string, ruleDto dtos.HostNetworkTrafficRuleCreateDto) (dtos.HostNetworkTrafficRuleDto, error) + -- + +DeleteTrafficRule(table string, ruleDto dtos.HostNetworkTrafficRuleDeleteDto) error + -- + +GetChainRules(table string, chain string) ([]dtos.HostNetworkTrafficRuleDto, error) + -- + +GetTableRules(table string) ([]dtos.HostNetworkTrafficRuleDto, error) } HostNetworkService::manager -- HostNetworkManager @@ -74,6 +85,22 @@ package app { note right of HostNetworkService::DeleteBridge Delete bridge interface from host end note + + note right of HostNetworkService::CreateTrafficRule + Create netfilter traffic rule for specified table + end note + + note right of HostNetworkService::DeleteTrafficRule + Delete netfilter traffic rule in specified table + end note + + note right of HostNetworkService::GetChainRules + Get selected netfilter chain rules at specified table + end note + + note right of HostNetworkService::GetTableRules + Get specified netfilter table rules + end note } @enduml diff --git a/src/app/interfaces/IHostNetworkManager.go b/src/app/interfaces/IHostNetworkManager.go index 84350d17..316b128c 100644 --- a/src/app/interfaces/IHostNetworkManager.go +++ b/src/app/interfaces/IHostNetworkManager.go @@ -1,6 +1,9 @@ package interfaces -import "net" +import ( + "net" + "rol/domain" +) //IHostNetworkManager is an interface for network manager type IHostNetworkManager interface { @@ -80,6 +83,40 @@ type IHostNetworkManager interface { //Return: // error - if an error occurs, otherwise nil AddrDelete(linkName string, addr net.IPNet) error + //CreateTrafficRule Create netfilter traffic rule for specified table + // + //Params: + // table - table to create a rule + // rule - rule entity + //Return: + // domain.HostNetworkTrafficRule - new traffic rule + // error - if an error occurs, otherwise nil + CreateTrafficRule(table string, rule domain.HostNetworkTrafficRule) (domain.HostNetworkTrafficRule, error) + //DeleteTrafficRule Delete netfilter traffic rule in specified table + // + //Params: + // table - table to delete a rule + // rule - rule entity + //Return: + // error - if an error occurs, otherwise nil + DeleteTrafficRule(table string, rule domain.HostNetworkTrafficRule) error + //GetChainRules Get selected netfilter chain rules at specified table + // + //Params: + // table - table to get a rules + // chain - chain where we get the rules + //Return: + // []domain.HostNetworkTrafficRule - slice of rules + // error - if an error occurs, otherwise nil + GetChainRules(table string, chain string) ([]domain.HostNetworkTrafficRule, error) + //GetTableRules Get specified netfilter table rules + // + //Params: + // table - table to get a rules + //Return: + // []domain.HostNetworkTrafficRule - slice of rules + // error - if an error occurs, otherwise nil + GetTableRules(table string) ([]domain.HostNetworkTrafficRule, error) //SaveConfiguration save current host network configuration to the configuration storage // //Return: diff --git a/src/app/mappers/NetworkTrafficRuleMapper.go b/src/app/mappers/NetworkTrafficRuleMapper.go new file mode 100644 index 00000000..8707493f --- /dev/null +++ b/src/app/mappers/NetworkTrafficRuleMapper.go @@ -0,0 +1,38 @@ +package mappers + +import ( + "github.com/coreos/go-iptables/iptables" + "rol/domain" + "rol/dtos" +) + +//MapStatToTrafficRule map iptables.Stat to domain.HostNetworkTrafficRule +func MapStatToTrafficRule(stat iptables.Stat, rule *domain.HostNetworkTrafficRule) { + rule.Source = stat.Source.String() + rule.Destination = stat.Destination.String() + rule.Action = stat.Target +} + +//MapHostNetworkTrafficRuleEntityToDto map HostNetworkTrafficRule entity to dto +func MapHostNetworkTrafficRuleEntityToDto(entity domain.HostNetworkTrafficRule, dto *dtos.HostNetworkTrafficRuleDto) { + dto.Chain = entity.Chain + dto.Action = entity.Action + dto.Source = entity.Source + dto.Destination = entity.Destination +} + +//MapHostNetworkTrafficRuleCreateDtoToEntity map HostNetworkTrafficRuleCreateDto dto to entity +func MapHostNetworkTrafficRuleCreateDtoToEntity(dto dtos.HostNetworkTrafficRuleCreateDto, entity *domain.HostNetworkTrafficRule) { + entity.Chain = dto.Chain + entity.Action = dto.Action + entity.Source = dto.Source + entity.Destination = dto.Destination +} + +//MapHostNetworkTrafficRuleDeleteDtoToEntity map HostNetworkTrafficRuleDeleteDto dto to entity +func MapHostNetworkTrafficRuleDeleteDtoToEntity(dto dtos.HostNetworkTrafficRuleDeleteDto, entity *domain.HostNetworkTrafficRule) { + entity.Chain = dto.Chain + entity.Action = dto.Action + entity.Source = dto.Source + entity.Destination = dto.Destination +} diff --git a/src/app/services/HostNetworkServiceTraffic.go b/src/app/services/HostNetworkServiceTraffic.go new file mode 100644 index 00000000..a188ba6b --- /dev/null +++ b/src/app/services/HostNetworkServiceTraffic.go @@ -0,0 +1,84 @@ +package services + +import ( + "rol/app/errors" + "rol/app/mappers" + "rol/domain" + "rol/dtos" +) + +//CreateTrafficRule Create netfilter traffic rule for specified table +// +//Params: +// table - table to create a rule +// rule - rule entity +//Return: +// domain.NetworkTrafficRule - new traffic rule +// error - if an error occurs, otherwise nil +func (h *HostNetworkService) CreateTrafficRule(table string, ruleDto dtos.HostNetworkTrafficRuleCreateDto) (dtos.HostNetworkTrafficRuleDto, error) { + var rule domain.HostNetworkTrafficRule + var out dtos.HostNetworkTrafficRuleDto + mappers.MapHostNetworkTrafficRuleCreateDtoToEntity(ruleDto, &rule) + newRule, err := h.manager.CreateTrafficRule(table, rule) + if err != nil { + return dtos.HostNetworkTrafficRuleDto{}, errors.Internal.Wrap(err, "host network manager failed to create traffic rule") + } + mappers.MapHostNetworkTrafficRuleEntityToDto(newRule, &out) + return out, nil +} + +//DeleteTrafficRule Delete netfilter traffic rule in specified table +// +//Params: +// table - table to delete a rule +// rule - rule entity +//Return: +// error - if an error occurs, otherwise nil +func (h *HostNetworkService) DeleteTrafficRule(table string, ruleDto dtos.HostNetworkTrafficRuleDeleteDto) error { + var rule domain.HostNetworkTrafficRule + mappers.MapHostNetworkTrafficRuleDeleteDtoToEntity(ruleDto, &rule) + return h.manager.DeleteTrafficRule(table, rule) +} + +//GetChainRules Get selected netfilter chain rules at specified table +// +//Params: +// table - table to get a rules +// chain - chain where we get the rules +//Return: +// []domain.NetworkTrafficRule - slice of rules +// error - if an error occurs, otherwise nil +func (h *HostNetworkService) GetChainRules(table string, chain string) ([]dtos.HostNetworkTrafficRuleDto, error) { + var out []dtos.HostNetworkTrafficRuleDto + rules, err := h.manager.GetChainRules(table, chain) + if err != nil { + return nil, errors.Internal.Wrap(err, "host network manager failed to get chain rule") + } + for _, rule := range rules { + var dto dtos.HostNetworkTrafficRuleDto + mappers.MapHostNetworkTrafficRuleEntityToDto(rule, &dto) + out = append(out, dto) + } + return out, nil +} + +//GetTableRules Get specified netfilter table rules +// +//Params: +// table - table to get a rules +//Return: +// []domain.NetworkTrafficRule - slice of rules +// error - if an error occurs, otherwise nil +func (h *HostNetworkService) GetTableRules(table string) ([]dtos.HostNetworkTrafficRuleDto, error) { + var out []dtos.HostNetworkTrafficRuleDto + rules, err := h.manager.GetTableRules(table) + if err != nil { + return nil, errors.Internal.Wrap(err, "host network manager failed to get table rule") + } + for _, rule := range rules { + var dto dtos.HostNetworkTrafficRuleDto + mappers.MapHostNetworkTrafficRuleEntityToDto(rule, &dto) + out = append(out, dto) + } + return out, nil +} diff --git a/src/domain/HostNetworkConfig.go b/src/domain/HostNetworkConfig.go index 271fe8b2..1019e661 100644 --- a/src/domain/HostNetworkConfig.go +++ b/src/domain/HostNetworkConfig.go @@ -8,4 +8,20 @@ type HostNetworkConfig struct { Vlans []HostNetworkVlan //Bridges slice of HostNetworkBridge Bridges []HostNetworkBridge + //TrafficRules netfilter traffic rules struct + TrafficRules TrafficRules +} + +//TrafficRules is a struct for netfilter rules separated by tables +type TrafficRules struct { + //Filter 'filter' table rules + Filter []HostNetworkTrafficRule + //NAT 'nat' table rules + NAT []HostNetworkTrafficRule + //Mangle 'mangle' table rules + Mangle []HostNetworkTrafficRule + //Raw 'raw' table rules + Raw []HostNetworkTrafficRule + //Security 'security' table rules + Security []HostNetworkTrafficRule } diff --git a/src/domain/HostNetworkTrafficRule.go b/src/domain/HostNetworkTrafficRule.go new file mode 100644 index 00000000..46f53ea3 --- /dev/null +++ b/src/domain/HostNetworkTrafficRule.go @@ -0,0 +1,13 @@ +package domain + +//HostNetworkTrafficRule netfilter traffic rule +type HostNetworkTrafficRule struct { + //Chain rule chain + Chain string + //Action rule action like ACCEPT, MASQUERADE, DROP, etc. + Action string + //Source packets source + Source string + //Destination packets destination + Destination string +} diff --git a/src/dtos/HostNetworkTrafficRuleBaseDto.go b/src/dtos/HostNetworkTrafficRuleBaseDto.go new file mode 100644 index 00000000..8ec0e3e3 --- /dev/null +++ b/src/dtos/HostNetworkTrafficRuleBaseDto.go @@ -0,0 +1,13 @@ +package dtos + +//HostNetworkTrafficRuleBaseDto base dto for host network traffic rule +type HostNetworkTrafficRuleBaseDto struct { + //Chain rule chain + Chain string + //Action rule action like ACCEPT, MASQUERADE, DROP, etc. + Action string + //Source packets source + Source string + //Destination packets destination + Destination string +} diff --git a/src/dtos/HostNetworkTrafficRuleCreateDto.go b/src/dtos/HostNetworkTrafficRuleCreateDto.go new file mode 100644 index 00000000..61128fa6 --- /dev/null +++ b/src/dtos/HostNetworkTrafficRuleCreateDto.go @@ -0,0 +1,6 @@ +package dtos + +//HostNetworkTrafficRuleCreateDto create dto for host network traffic rule +type HostNetworkTrafficRuleCreateDto struct { + HostNetworkTrafficRuleBaseDto +} diff --git a/src/dtos/HostNetworkTrafficRuleDeleteDto.go b/src/dtos/HostNetworkTrafficRuleDeleteDto.go new file mode 100644 index 00000000..59ccf516 --- /dev/null +++ b/src/dtos/HostNetworkTrafficRuleDeleteDto.go @@ -0,0 +1,6 @@ +package dtos + +//HostNetworkTrafficRuleDeleteDto delete dto for host network traffic rule +type HostNetworkTrafficRuleDeleteDto struct { + HostNetworkTrafficRuleBaseDto +} diff --git a/src/dtos/HostNetworkTrafficRuleDto.go b/src/dtos/HostNetworkTrafficRuleDto.go new file mode 100644 index 00000000..15fe55a5 --- /dev/null +++ b/src/dtos/HostNetworkTrafficRuleDto.go @@ -0,0 +1,6 @@ +package dtos + +//HostNetworkTrafficRuleDto dto for host network traffic rule entity +type HostNetworkTrafficRuleDto struct { + HostNetworkTrafficRuleBaseDto +} diff --git a/src/go.mod b/src/go.mod index bbb76419..5830fa7d 100644 --- a/src/go.mod +++ b/src/go.mod @@ -5,6 +5,7 @@ go 1.18 require ( github.com/Azure/go-asynctask v1.1.1 github.com/coredhcp/coredhcp v0.0.0-20220602152301-a2552c5c1b7a + github.com/coreos/go-iptables v0.6.0 github.com/gin-gonic/gin v1.7.7 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/insei/coredhcp v0.0.1 diff --git a/src/go.sum b/src/go.sum index 6f0273c7..a6f5f65b 100644 --- a/src/go.sum +++ b/src/go.sum @@ -43,6 +43,8 @@ github.com/coredhcp/coredhcp v0.0.0-20220602152301-a2552c5c1b7a h1:v7KPOqzuidxzs github.com/coredhcp/coredhcp v0.0.0-20220602152301-a2552c5c1b7a/go.mod h1:8eZF6Wd11nVtN5u8TaUUIDB5wC7u439e98vpxagG44Q= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk= +github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= diff --git a/src/infrastructure/HostNetworkManager_linux.go b/src/infrastructure/HostNetworkManager_linux.go index 675bd792..176cadac 100644 --- a/src/infrastructure/HostNetworkManager_linux.go +++ b/src/infrastructure/HostNetworkManager_linux.go @@ -2,10 +2,13 @@ package infrastructure import ( "fmt" + "github.com/coreos/go-iptables/iptables" "github.com/vishvananda/netlink" "net" "rol/app/errors" "rol/app/interfaces" + "rol/app/mappers" + "rol/app/utils" "rol/domain" "strings" ) @@ -13,18 +16,32 @@ import ( //HostNetworkManager is a struct for network manager type HostNetworkManager struct { configStorage interfaces.IHostNetworkConfigStorage + iptables *iptables.IPTables hasUnsavedChanges bool } +var netfilterTables = []string{ + "filter", + "nat", + "mangle", + "raw", + "security", +} + //NewHostNetworkManager constructor for HostNetworkManager func NewHostNetworkManager(configStorage interfaces.IHostNetworkConfigStorage) (interfaces.IHostNetworkManager, error) { + ipTables, err := iptables.New() + if err != nil { + return nil, errors.Internal.Wrap(err, "error getting iptables instance") + } hostNetworkManager := &HostNetworkManager{ configStorage: configStorage, + iptables: ipTables, hasUnsavedChanges: true, // we set this flag for calling reset changes function at start, for apply configuration from storage } //if it's a first time, we need to save config based on current configuration - _, err := configStorage.GetConfig() + _, err = configStorage.GetConfig() if err != nil && !errors.As(err, errors.Internal) { err = hostNetworkManager.SaveConfiguration() if err != nil { @@ -361,6 +378,129 @@ func (h *HostNetworkManager) AddrDelete(linkName string, addr net.IPNet) error { return nil } +func (h *HostNetworkManager) parseRule(rule domain.HostNetworkTrafficRule) []string { + var rulespec []string + if rule.Source != "" { + rulespec = append(rulespec, "-s") + rulespec = append(rulespec, rule.Source) + } + if rule.Destination != "" { + rulespec = append(rulespec, "-d") + rulespec = append(rulespec, rule.Destination) + } + if rule.Action != "" { + rulespec = append(rulespec, "-j") + rulespec = append(rulespec, rule.Action) + } + return rulespec +} + +//CreateTrafficRule Create netfilter traffic rule for specified table +// +//Params: +// table - table to create a rule +// rule - rule entity +//Return: +// domain.HostNetworkTrafficRule - new traffic rule +// error - if an error occurs, otherwise nil +func (h *HostNetworkManager) CreateTrafficRule(table string, rule domain.HostNetworkTrafficRule) (domain.HostNetworkTrafficRule, error) { + rulespec := h.parseRule(rule) + err := h.iptables.AppendUnique(table, rule.Chain, rulespec...) + if err != nil { + return domain.HostNetworkTrafficRule{}, errors.Internal.Wrap(err, "failed to create traffic rule") + } + return rule, nil +} + +//DeleteTrafficRule Delete netfilter traffic rule in specified table +// +//Params: +// table - table to delete a rule +// rule - rule entity +//Return: +// error - if an error occurs, otherwise nil +func (h *HostNetworkManager) DeleteTrafficRule(table string, rule domain.HostNetworkTrafficRule) error { + rulespec := h.parseRule(rule) + exist, err := h.iptables.Exists(table, rule.Chain, rulespec...) + if err != nil { + return errors.Internal.Wrap(err, "failed to check existence of traffic rule") + } + if exist { + err = h.iptables.Delete(table, rule.Chain, rulespec...) + if err != nil { + return errors.Internal.Wrap(err, "failed to delete traffic rule") + } + return nil + } + return errors.NotFound.New("traffic rule not found") +} + +//GetChainRules Get selected netfilter chain rules at specified table +// +//Params: +// table - table to get a rules +// chain - chain where we get the rules +//Return: +// []domain.HostNetworkTrafficRule - slice of rules +// error - if an error occurs, otherwise nil +func (h *HostNetworkManager) GetChainRules(table string, chain string) ([]domain.HostNetworkTrafficRule, error) { + var rules []domain.HostNetworkTrafficRule + + list, err := h.iptables.Stats(table, chain) + if err != nil { + return nil, errors.Internal.Wrap(err, "failed to get list of traffic rules") + } + for _, l := range list { + stat, err := h.iptables.ParseStat(l) + if err != nil { + return nil, errors.Internal.Wrap(err, "failed to parse traffic rule to stat struct") + } + rule := &domain.HostNetworkTrafficRule{Chain: chain} + mappers.MapStatToTrafficRule(stat, rule) + rules = append(rules, *rule) + } + return rules, err +} + +func (h *HostNetworkManager) trimExclamationMarkInStat(slice []string) (out []string) { + for _, element := range slice { + out = append(out, strings.Trim(element, "!")) + } + return +} + +//GetTableRules Get specified netfilter table rules +// +//Params: +// table - table to get a rules +//Return: +// []domain.HostNetworkTrafficRule - slice of rules +// error - if an error occurs, otherwise nil +func (h *HostNetworkManager) GetTableRules(table string) ([]domain.HostNetworkTrafficRule, error) { + var rules []domain.HostNetworkTrafficRule + + chains, err := h.iptables.ListChains(table) + if err != nil { + return nil, errors.Internal.Wrap(err, "failed to get list table chains") + } + for _, chain := range chains { + list, err := h.iptables.Stats(table, chain) + if err != nil { + return nil, errors.Internal.Wrap(err, "failed to get list of traffic rules") + } + for _, l := range list { + stat, err := h.iptables.ParseStat(h.trimExclamationMarkInStat(l)) + if err != nil { + return nil, errors.Internal.Wrap(err, "failed to parse traffic rule to stat struct") + } + rule := &domain.HostNetworkTrafficRule{Chain: chain} + mappers.MapStatToTrafficRule(stat, rule) + rules = append(rules, *rule) + } + } + return rules, nil +} + //SaveConfiguration save current host network configuration to the configuration storage //Save previous config file to .back file // @@ -381,6 +521,14 @@ func (h *HostNetworkManager) SaveConfiguration() error { config.Bridges = append(config.Bridges, inter.(domain.HostNetworkBridge)) } } + for _, table := range netfilterTables { + rules, err := h.GetTableRules(table) + if err != nil { + return errors.Internal.Wrap(err, "failed to get table rules") + } + + h.setTrafficRulesConfigField(table, rules, &config) + } err = h.configStorage.SaveConfig(config) if err != nil { return errors.Internal.Wrap(err, "failed to save host network config to storage") @@ -594,6 +742,71 @@ func (h *HostNetworkManager) loadBridgeConfiguration(config domain.HostNetworkCo return nil } +func (h *HostNetworkManager) setTrafficRulesConfigField(table string, rule []domain.HostNetworkTrafficRule, config *domain.HostNetworkConfig) { + switch table { + case "filter": + config.TrafficRules.Filter = rule + case "nat": + config.TrafficRules.NAT = rule + case "mangle": + config.TrafficRules.Mangle = rule + case "raw": + config.TrafficRules.Raw = rule + case "security": + config.TrafficRules.Security = rule + } + return +} + +func (h *HostNetworkManager) getConfigField(table string, config domain.HostNetworkConfig) []domain.HostNetworkTrafficRule { + switch table { + case "filter": + return config.TrafficRules.Filter + case "nat": + return config.TrafficRules.NAT + case "mangle": + return config.TrafficRules.Mangle + case "raw": + return config.TrafficRules.Raw + case "security": + return config.TrafficRules.Security + default: + return nil + } +} + +func (h *HostNetworkManager) loadTrafficConfiguration(config domain.HostNetworkConfig) error { + for _, table := range netfilterTables { + rules, err := h.GetTableRules(table) + if err != nil { + return errors.Internal.Wrap(err, "failed to get table rules") + } + + configField := h.getConfigField(table, config) + if configField == nil { + return errors.Internal.New("failed to get config field") + } + + for _, rule := range configField { + if !utils.SliceContainsElement(rules, rule) { + _, err = h.CreateTrafficRule(table, rule) + if err != nil { + return errors.Internal.New("error when creating traffic rule") + } + } + } + for _, rule := range rules { + if !utils.SliceContainsElement(configField, rule) { + err = h.DeleteTrafficRule(table, rule) + if err != nil { + return errors.Internal.New("error when deleting traffic rule") + } + } + } + } + return nil +} + func (h *HostNetworkManager) loadConfiguration(config domain.HostNetworkConfig) error { err := h.loadVlanConfiguration(config) if err != nil { @@ -603,6 +816,10 @@ func (h *HostNetworkManager) loadConfiguration(config domain.HostNetworkConfig) if err != nil { return errors.Internal.Wrap(err, "error loading bridge configuration") } + err = h.loadTrafficConfiguration(config) + if err != nil { + return errors.Internal.Wrap(err, "error loading host network traffic configuration") + } h.hasUnsavedChanges = false return nil } diff --git a/src/webapi/controllers/HostNetworkTrafficGinController.go b/src/webapi/controllers/HostNetworkTrafficGinController.go new file mode 100644 index 00000000..acfc6589 --- /dev/null +++ b/src/webapi/controllers/HostNetworkTrafficGinController.go @@ -0,0 +1,113 @@ +package controllers + +import ( + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "rol/app/services" + "rol/dtos" + "rol/webapi" +) + +//HostNetworkTrafficGinController host network bridge API controller +type HostNetworkTrafficGinController struct { + service *services.HostNetworkService + logger *logrus.Logger +} + +//NewHostNetworkTrafficGinController host network traffic controller constructor. Parameters pass through DI +// +//Params: +// trafficService - bridge service +// log - logrus logger +//Return: +// *HostNetworkBridgeController - instance of host network bridge controller +func NewHostNetworkTrafficGinController(trafficService *services.HostNetworkService, log *logrus.Logger) *HostNetworkTrafficGinController { + return &HostNetworkTrafficGinController{ + service: trafficService, + logger: log, + } +} + +//RegisterHostNetworkTrafficGinController registers controller for getting host network bridges via api +func RegisterHostNetworkTrafficGinController(controller *HostNetworkTrafficGinController, server *webapi.GinHTTPServer) { + groupRoute := server.Engine.Group("/api/v1") + + groupRoute.GET("/host/network/traffic/:table", controller.GetTableRules) + groupRoute.POST("/host/network/traffic/:table/", controller.Create) + groupRoute.DELETE("/host/network/traffic/:table/", controller.Delete) +} + +//GetTableRules get selected netfilter table rules +// +//Params: +// ctx - gin context +// +// @Summary Get selected netfilter table rules +// @version 1.0 +// @Tags host +// @Accept json +// @Produce json +// @param table path string true "Table name" +// @Success 200 {object} []dtos.HostNetworkTrafficRuleDto +// @Failure 500 "Internal Server Error" +// @router /host/network/traffic/{table} [get] +func (h *HostNetworkTrafficGinController) GetTableRules(ctx *gin.Context) { + table := ctx.Param("table") + rules, err := h.service.GetTableRules(table) + handleWithData(ctx, err, rules) +} + +//Create new traffic rule in specified table +// +//Params: +// ctx - gin context +// +// @Summary Create new traffic rule in specified table +// @version 1.0 +// @Tags host +// @Accept json +// @Produce json +// @param table path string true "Table name" +// @param request body dtos.HostNetworkTrafficRuleCreateDto true "Host traffic rule fields" +// @Success 200 {object} dtos.HostNetworkTrafficRuleDto +// @Failure 400 {object} dtos.ValidationErrorDto +// @Failure 500 "Internal Server Error" +// @router /host/network/traffic/{table} [post] +func (h *HostNetworkTrafficGinController) Create(ctx *gin.Context) { + table := ctx.Param("table") + reqDto, err := getRequestDtoAndRestoreBody[dtos.HostNetworkTrafficRuleCreateDto](ctx) + if err != nil { + abortWithStatusByErrorType(ctx, err) + return + } + + ruleDto, err := h.service.CreateTrafficRule(table, reqDto) + handleWithData(ctx, err, ruleDto) +} + +//Delete netfilter traffic rule in specified table +// +//Params: +// ctx - gin context +// +// @Summary Delete netfilter traffic rule in specified table +// @version 1.0 +// @Tags host +// @Accept json +// @Produce json +// @param table path string true "Table name" +// @param request body dtos.HostNetworkTrafficRuleDeleteDto true "Host traffic rule fields" +// @Success 204 "OK, but No Content" +// @Failure 404 "Not Found" +// @Failure 500 "Internal Server Error" +// @router /host/network/traffic/{table} [delete] +func (h *HostNetworkTrafficGinController) Delete(ctx *gin.Context) { + table := ctx.Param("table") + reqDto, err := getRequestDtoAndRestoreBody[dtos.HostNetworkTrafficRuleDeleteDto](ctx) + if err != nil { + abortWithStatusByErrorType(ctx, err) + return + } + err = h.service.DeleteTrafficRule(table, reqDto) + handle(ctx, err) +}