mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-20 16:13:59 +08:00
642 lines
23 KiB
Go
642 lines
23 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/app/model"
|
|
"github.com/1Panel-dev/1Panel/agent/app/repo"
|
|
"github.com/1Panel-dev/1Panel/agent/constant"
|
|
"github.com/1Panel-dev/1Panel/agent/global"
|
|
agenti18n "github.com/1Panel-dev/1Panel/agent/i18n"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/docker"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/docker_guard"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/filter"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/firewall/lifecycle"
|
|
containertypes "github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/system"
|
|
"github.com/docker/docker/client"
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
const (
|
|
dockerGuardComposeProjectLabel = "com.docker.compose.project"
|
|
dockerGuardComposeCreatedBy = "createdBy"
|
|
)
|
|
|
|
type dockerGuardRuntime = docker_guard.Runtime
|
|
|
|
type DockerPortGuardService struct {
|
|
policies repo.IDockerPortGuardRepo
|
|
runtime dockerGuardRuntime
|
|
runtimeForBackend func(string) dockerGuardRuntime
|
|
client func() (*client.Client, error)
|
|
version func(string) string
|
|
}
|
|
|
|
var (
|
|
dockerPortGuardServiceMu sync.Mutex
|
|
dockerPortGuardSyncMu sync.RWMutex
|
|
dockerPortGuardSyncErr error
|
|
ErrDockerGuardInvalid = docker_guard.ErrInvalidPolicy
|
|
ErrDockerUnavailable = docker.ErrUnavailable
|
|
)
|
|
|
|
type IDockerPortGuardService interface {
|
|
LoadOverview(context.Context) (dto.DockerPortGuardList, error)
|
|
Operate(context.Context, dto.DockerPortGuardOperation) error
|
|
DeletePolicies(context.Context, dto.DockerPortGuardPolicyBatchDelete) error
|
|
UpsertPolicies(context.Context, dto.DockerPortGuardPolicyBatch) error
|
|
Reconcile(context.Context) error
|
|
}
|
|
|
|
func NewIDockerPortGuardService() IDockerPortGuardService {
|
|
return newDockerPortGuardService()
|
|
}
|
|
|
|
func newDockerPortGuardService() *DockerPortGuardService {
|
|
return &DockerPortGuardService{
|
|
policies: repo.NewIDockerPortGuardRepo(),
|
|
client: docker.NewDockerClient,
|
|
version: dockerFirewallVersion,
|
|
}
|
|
}
|
|
|
|
func ReconcileDockerPortGuard(ctx context.Context) error {
|
|
if global.DB == nil {
|
|
return nil
|
|
}
|
|
return NewIDockerPortGuardService().Reconcile(ctx)
|
|
}
|
|
|
|
func ReconcileDockerPortGuardBestEffort(ctx context.Context) {
|
|
if err := ReconcileDockerPortGuard(ctx); err != nil {
|
|
global.LOG.Warnf("reconcile Docker port guard failed, err: %v", err)
|
|
}
|
|
}
|
|
|
|
func (s *DockerPortGuardService) LoadOverview(ctx context.Context) (dto.DockerPortGuardList, error) {
|
|
selectedBackend := selectedDockerFirewallBackend("")
|
|
base := s.runtimeStatus(s.guardRuntime(selectedBackend), selectedBackend)
|
|
base.Version = s.loadFirewallVersion(selectedBackend)
|
|
policies, err := s.policies.List(ctx)
|
|
if err != nil {
|
|
return dto.DockerPortGuardList{}, err
|
|
}
|
|
cli, err := s.client()
|
|
if err != nil {
|
|
base.Message = agenti18n.Get("ErrDockerFailed")
|
|
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}, nil
|
|
}
|
|
defer cli.Close()
|
|
info, err := cli.Info(ctx)
|
|
if err != nil {
|
|
base.Message = agenti18n.Get("ErrDockerFailed")
|
|
return dto.DockerPortGuardList{Base: base, Containers: []dto.DockerPortGuardContainer{}, OrphanPolicies: dockerGuardPolicyEndpoints(policies)}, nil
|
|
}
|
|
base.Backend = selectedDockerFirewallBackend(dockerFirewallBackend(info))
|
|
base = s.runtimeStatus(s.guardRuntime(base.Backend), base.Backend)
|
|
base.Version = s.loadFirewallVersion(base.Backend)
|
|
if reconcileErr := lastDockerPortGuardReconcileError(); reconcileErr != nil {
|
|
base.Message = reconcileErr.Error()
|
|
markDockerGuardReconcileFailure(&base, reconcileErr)
|
|
}
|
|
endpoints, err := discoverDockerEndpoints(ctx, cli)
|
|
if err != nil {
|
|
return dto.DockerPortGuardList{}, err
|
|
}
|
|
endpoints, orphanPolicies := matchDockerGuardPolicies(base, policies, endpoints)
|
|
sort.Slice(endpoints, func(i, j int) bool {
|
|
return guardEndpointKey(endpoints[i].Family, endpoints[i].HostIP, endpoints[i].HostPort, endpoints[i].Protocol) < guardEndpointKey(endpoints[j].Family, endpoints[j].HostIP, endpoints[j].HostPort, endpoints[j].Protocol)
|
|
})
|
|
sort.Slice(orphanPolicies, func(i, j int) bool {
|
|
return guardEndpointKey(orphanPolicies[i].Family, orphanPolicies[i].HostIP, orphanPolicies[i].HostPort, orphanPolicies[i].Protocol) < guardEndpointKey(orphanPolicies[j].Family, orphanPolicies[j].HostIP, orphanPolicies[j].HostPort, orphanPolicies[j].Protocol)
|
|
})
|
|
return dto.DockerPortGuardList{Base: base, Containers: groupDockerGuardContainers(endpoints), OrphanPolicies: orphanPolicies}, nil
|
|
}
|
|
|
|
func matchDockerGuardPolicies(
|
|
base dto.DockerPortGuardBase,
|
|
policies []model.DockerPortGuardPolicy,
|
|
endpoints []dto.DockerPortGuardEndpoint,
|
|
) ([]dto.DockerPortGuardEndpoint, []dto.DockerPortGuardEndpoint) {
|
|
byEndpoint := make(map[string]model.DockerPortGuardPolicy, len(policies))
|
|
for _, policy := range policies {
|
|
byEndpoint[guardEndpointKey(policy.Family, policy.HostIP, policy.HostPort, policy.Protocol)] = policy
|
|
}
|
|
for i := range endpoints {
|
|
key := guardEndpointKey(endpoints[i].Family, endpoints[i].HostIP, endpoints[i].HostPort, endpoints[i].Protocol)
|
|
policy, ok := byEndpoint[key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
endpoints[i].PolicyUUID, endpoints[i].Mode, endpoints[i].Sources = policy.UUID, policy.Mode, docker_guard.DecodeSources(policy.Sources)
|
|
endpoints[i].Description = policy.Description
|
|
endpoints[i].Effective = (policy.Family == docker_guard.FamilyIPv4 && base.IPv4.Effective) || (policy.Family == docker_guard.FamilyIPv6 && base.IPv6.Effective)
|
|
delete(byEndpoint, key)
|
|
}
|
|
orphanPolicies := make([]dto.DockerPortGuardEndpoint, 0, len(byEndpoint))
|
|
for _, policy := range byEndpoint {
|
|
orphanPolicies = append(orphanPolicies, dto.DockerPortGuardEndpoint{
|
|
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
|
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources), Description: policy.Description,
|
|
})
|
|
}
|
|
return endpoints, orphanPolicies
|
|
}
|
|
|
|
func (s *DockerPortGuardService) Operate(ctx context.Context, request dto.DockerPortGuardOperation) error {
|
|
dockerPortGuardServiceMu.Lock()
|
|
defer dockerPortGuardServiceMu.Unlock()
|
|
switch request.Operation {
|
|
case "initialize":
|
|
runtime, backend, err := s.runtimeForDocker(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
policies, err := s.runtimePolicies(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := runtime.Initialize(policies); err != nil {
|
|
recordDockerPortGuardReconcileError(err)
|
|
return err
|
|
}
|
|
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerBackendKey, backend); err != nil {
|
|
return err
|
|
}
|
|
if err := settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable); err != nil {
|
|
return err
|
|
}
|
|
recordDockerPortGuardReconcileError(nil)
|
|
return nil
|
|
case "bind":
|
|
runtime, _, err := s.runtimeForDocker(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := runtime.Bind(); err != nil {
|
|
return err
|
|
}
|
|
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusEnable)
|
|
case "unbind":
|
|
var err error
|
|
if s.runtime != nil {
|
|
err = s.runtime.Unbind()
|
|
} else {
|
|
err = errors.Join(docker_guard.NewManager().Unbind(), docker_guard.NewNftablesManager().Unbind())
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return settingRepo.UpdateOrCreate(constant.FirewallDockerPortGuardStatusKey, constant.StatusDisable)
|
|
default:
|
|
return fmt.Errorf("unsupported Docker port guard operation: %s", request.Operation)
|
|
}
|
|
}
|
|
|
|
func (s *DockerPortGuardService) DeletePolicies(ctx context.Context, request dto.DockerPortGuardPolicyBatchDelete) error {
|
|
dockerPortGuardServiceMu.Lock()
|
|
defer dockerPortGuardServiceMu.Unlock()
|
|
uuids, err := docker_guard.NormalizePolicyUUIDs(request.UUIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := s.policies.DeleteBatch(ctx, uuids); err != nil {
|
|
return err
|
|
}
|
|
return s.reconcileLocked(ctx)
|
|
}
|
|
|
|
func (s *DockerPortGuardService) UpsertPolicies(ctx context.Context, request dto.DockerPortGuardPolicyBatch) error {
|
|
dockerPortGuardServiceMu.Lock()
|
|
defer dockerPortGuardServiceMu.Unlock()
|
|
policies := make([]model.DockerPortGuardPolicy, 0, len(request.Endpoints))
|
|
seen := make(map[string]struct{}, len(request.Endpoints))
|
|
for _, endpoint := range request.Endpoints {
|
|
normalized, err := docker_guard.NormalizePolicy(docker_guard.Policy{
|
|
Family: endpoint.Family, HostIP: endpoint.HostIP, HostPort: endpoint.HostPort,
|
|
Protocol: endpoint.Protocol, Mode: request.Mode, Sources: request.Sources,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
key := guardEndpointKey(normalized.Family, normalized.HostIP, normalized.HostPort, normalized.Protocol)
|
|
if _, exists := seen[key]; exists {
|
|
continue
|
|
}
|
|
seen[key] = struct{}{}
|
|
encoded, _ := json.Marshal(normalized.Sources)
|
|
policies = append(policies, model.DockerPortGuardPolicy{
|
|
UUID: uuid.NewString(), Family: normalized.Family, HostIP: normalized.HostIP,
|
|
HostPort: normalized.HostPort, Protocol: normalized.Protocol, Mode: normalized.Mode,
|
|
Sources: string(encoded), Description: strings.TrimSpace(request.Description),
|
|
})
|
|
}
|
|
if err := s.policies.UpsertBatch(ctx, policies); err != nil {
|
|
return err
|
|
}
|
|
return s.reconcileLocked(ctx)
|
|
}
|
|
|
|
func (s *DockerPortGuardService) Reconcile(ctx context.Context) error {
|
|
dockerPortGuardServiceMu.Lock()
|
|
defer dockerPortGuardServiceMu.Unlock()
|
|
return s.reconcileLocked(ctx)
|
|
}
|
|
|
|
func (s *DockerPortGuardService) loadRuleSyncCandidates(
|
|
ctx context.Context,
|
|
request dto.FirewallRuleSyncRequest,
|
|
) (string, []model.DockerPortGuardPolicy, dockerGuardRuntime, error) {
|
|
targetProvider, err := databaseRuleSyncTarget(request, "Docker")
|
|
if err != nil {
|
|
return "", nil, nil, err
|
|
}
|
|
target := string(targetProvider)
|
|
selected, err := s.selectedRuleSyncBackend(ctx)
|
|
if err != nil {
|
|
return "", nil, nil, err
|
|
}
|
|
if target != selected {
|
|
return "", nil, nil, fmt.Errorf(
|
|
"%w: selected Docker firewall backend is %s, requested target is %s",
|
|
filter.ErrProviderUnavailable, selected, target,
|
|
)
|
|
}
|
|
policies, err := s.policies.List(ctx)
|
|
if err != nil {
|
|
return "", nil, nil, err
|
|
}
|
|
return target, policies, s.guardRuntime(target), nil
|
|
}
|
|
|
|
func (s *DockerPortGuardService) selectedRuleSyncBackend(ctx context.Context) (string, error) {
|
|
if global.DB != nil {
|
|
selected, _ := settingRepo.GetValueByKey(constant.FirewallDockerBackendKey)
|
|
selected = strings.ToLower(strings.TrimSpace(selected))
|
|
if selected == constant.FirewallProviderIptables || selected == constant.FirewallProviderNftables {
|
|
return selected, nil
|
|
}
|
|
}
|
|
if s.client == nil {
|
|
return "", fmt.Errorf("%w: Docker firewall backend is unavailable", ErrDockerUnavailable)
|
|
}
|
|
cli, err := s.client()
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: %v", ErrDockerUnavailable, err)
|
|
}
|
|
defer cli.Close()
|
|
info, err := cli.Info(ctx)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: %v", ErrDockerUnavailable, err)
|
|
}
|
|
return selectedDockerFirewallBackend(dockerFirewallBackend(info)), nil
|
|
}
|
|
|
|
func dockerGuardPoliciesFromModels(policies []model.DockerPortGuardPolicy) []docker_guard.Policy {
|
|
result := make([]docker_guard.Policy, 0, len(policies))
|
|
for _, policy := range policies {
|
|
result = append(result, dockerGuardPolicyFromModel(policy))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func dockerGuardPolicyFromModel(policy model.DockerPortGuardPolicy) docker_guard.Policy {
|
|
return docker_guard.Policy{
|
|
UUID: policy.UUID, Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort,
|
|
Protocol: policy.Protocol, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources),
|
|
}
|
|
}
|
|
|
|
func dockerGuardRuleSyncDTO(policy model.DockerPortGuardPolicy) *dto.DockerPortGuardEndpoint {
|
|
return &dto.DockerPortGuardEndpoint{
|
|
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
|
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources), Description: policy.Description,
|
|
}
|
|
}
|
|
|
|
func dockerGuardRuntimeRuleSyncDTO(policy docker_guard.Policy) *dto.DockerPortGuardEndpoint {
|
|
return &dto.DockerPortGuardEndpoint{
|
|
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
|
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: append([]string(nil), policy.Sources...),
|
|
}
|
|
}
|
|
|
|
func (s *DockerPortGuardService) reconcileLocked(ctx context.Context) (err error) {
|
|
defer func() { recordDockerPortGuardReconcileError(err) }()
|
|
persistedEnabled, err := dockerPortGuardPersistedEnabled()
|
|
if err != nil {
|
|
return fmt.Errorf("load Docker port guard persisted status: %w", err)
|
|
}
|
|
initialized, err := s.anyRuntimeInitialized()
|
|
if err != nil {
|
|
return &docker_guard.FamilyError{Family: docker_guard.FamilyIPv4, Err: fmt.Errorf("inspect initialization: %w", err)}
|
|
}
|
|
if !initialized && !persistedEnabled {
|
|
return nil
|
|
}
|
|
runtime, _, err := s.runtimeForDocker(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
initialized, err = runtime.Initialized(docker_guard.FamilyIPv4)
|
|
if err != nil {
|
|
return &docker_guard.FamilyError{Family: docker_guard.FamilyIPv4, Err: fmt.Errorf("inspect initialization: %w", err)}
|
|
}
|
|
if !initialized && !persistedEnabled {
|
|
return nil
|
|
}
|
|
policies, err := s.runtimePolicies(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !initialized {
|
|
err = runtime.Initialize(policies)
|
|
} else {
|
|
err = runtime.Reconcile(policies)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func dockerPortGuardPersistedEnabled() (bool, error) {
|
|
status, err := settingRepo.GetValueByKey(constant.FirewallDockerPortGuardStatusKey)
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return false, nil
|
|
}
|
|
return status == constant.StatusEnable, err
|
|
}
|
|
|
|
func (s *DockerPortGuardService) anyRuntimeInitialized() (bool, error) {
|
|
if s.runtime != nil {
|
|
return s.runtime.Initialized(docker_guard.FamilyIPv4)
|
|
}
|
|
for _, runtime := range []dockerGuardRuntime{docker_guard.NewManager(), docker_guard.NewNftablesManager()} {
|
|
initialized, err := runtime.Initialized(docker_guard.FamilyIPv4)
|
|
if err != nil || initialized {
|
|
return initialized, err
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func recordDockerPortGuardReconcileError(err error) {
|
|
dockerPortGuardSyncMu.Lock()
|
|
dockerPortGuardSyncErr = err
|
|
dockerPortGuardSyncMu.Unlock()
|
|
}
|
|
|
|
func lastDockerPortGuardReconcileError() error {
|
|
dockerPortGuardSyncMu.RLock()
|
|
defer dockerPortGuardSyncMu.RUnlock()
|
|
return dockerPortGuardSyncErr
|
|
}
|
|
|
|
func markDockerGuardReconcileFailure(base *dto.DockerPortGuardBase, err error) {
|
|
var familyErr *docker_guard.FamilyError
|
|
if errors.As(err, &familyErr) {
|
|
markDockerGuardFamilyNotEffective(base, familyErr.Family)
|
|
if familyErr.Family == docker_guard.FamilyIPv4 {
|
|
markDockerGuardFamilyNotEffective(base, docker_guard.FamilyIPv6)
|
|
}
|
|
return
|
|
}
|
|
markDockerGuardFamilyNotEffective(base, docker_guard.FamilyIPv4)
|
|
markDockerGuardFamilyNotEffective(base, docker_guard.FamilyIPv6)
|
|
}
|
|
|
|
func markDockerGuardFamilyNotEffective(base *dto.DockerPortGuardBase, family string) {
|
|
var status *dto.DockerPortGuardFamilyStatus
|
|
switch family {
|
|
case docker_guard.FamilyIPv4:
|
|
status = &base.IPv4
|
|
case docker_guard.FamilyIPv6:
|
|
status = &base.IPv6
|
|
default:
|
|
return
|
|
}
|
|
if !status.Initialized {
|
|
return
|
|
}
|
|
status.State = docker_guard.StatusNotEffective
|
|
status.Reason = docker_guard.ReasonInspectFailed
|
|
status.Effective = false
|
|
}
|
|
|
|
func (s *DockerPortGuardService) runtimePolicies(ctx context.Context) ([]docker_guard.Policy, error) {
|
|
stored, err := s.policies.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
policies := make([]docker_guard.Policy, 0, len(stored))
|
|
for _, policy := range stored {
|
|
policies = append(policies, docker_guard.Policy{UUID: policy.UUID, Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources)})
|
|
}
|
|
return policies, nil
|
|
}
|
|
|
|
func (s *DockerPortGuardService) runtimeStatus(runtime dockerGuardRuntime, backend string) dto.DockerPortGuardBase {
|
|
ipv4 := runtime.Status(docker_guard.FamilyIPv4)
|
|
ipv6 := runtime.Status(docker_guard.FamilyIPv6)
|
|
return dto.DockerPortGuardBase{
|
|
Name: dockerFirewallDisplayName(backend),
|
|
Backend: backend,
|
|
IsExist: ipv4.Reason != docker_guard.ReasonCommandMissing || ipv6.Reason != docker_guard.ReasonCommandMissing,
|
|
Initialized: ipv4.Initialized || ipv6.Initialized,
|
|
Bound: ipv4.Bound || ipv6.Bound,
|
|
IPv4: dto.DockerPortGuardFamilyStatus{State: ipv4.State, Reason: ipv4.Reason, Initialized: ipv4.Initialized, Bound: ipv4.Bound, Effective: ipv4.Effective},
|
|
IPv6: dto.DockerPortGuardFamilyStatus{State: ipv6.State, Reason: ipv6.Reason, Initialized: ipv6.Initialized, Bound: ipv6.Bound, Effective: ipv6.Effective},
|
|
}
|
|
}
|
|
|
|
func (s *DockerPortGuardService) guardRuntime(backend string) dockerGuardRuntime {
|
|
if s.runtimeForBackend != nil {
|
|
return s.runtimeForBackend(backend)
|
|
}
|
|
if s.runtime != nil {
|
|
return s.runtime
|
|
}
|
|
return docker_guard.NewRuntime(backend)
|
|
}
|
|
|
|
func (s *DockerPortGuardService) runtimeForDocker(ctx context.Context) (dockerGuardRuntime, string, error) {
|
|
if s.runtime != nil {
|
|
return s.runtime, selectedDockerFirewallBackend(constant.FirewallProviderIptables), nil
|
|
}
|
|
cli, err := s.client()
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("%w: %v", ErrDockerUnavailable, err)
|
|
}
|
|
defer cli.Close()
|
|
info, err := cli.Info(ctx)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("%w: %v", ErrDockerUnavailable, err)
|
|
}
|
|
backend := selectedDockerFirewallBackend(dockerFirewallBackend(info))
|
|
if backend != constant.FirewallProviderIptables && backend != constant.FirewallProviderNftables {
|
|
return nil, backend, fmt.Errorf("Docker firewall backend %q is not supported", backend)
|
|
}
|
|
return s.guardRuntime(backend), backend, nil
|
|
}
|
|
|
|
func dockerFirewallBackend(info system.Info) string {
|
|
if info.FirewallBackend == nil || info.FirewallBackend.Driver == "" {
|
|
return constant.FirewallProviderIptables
|
|
}
|
|
return strings.ToLower(info.FirewallBackend.Driver)
|
|
}
|
|
|
|
func dockerFirewallDisplayName(backend string) string {
|
|
switch strings.ToLower(strings.TrimSpace(backend)) {
|
|
case constant.FirewallProviderNftables:
|
|
return "nftables-docker"
|
|
default:
|
|
return "iptables-docker"
|
|
}
|
|
}
|
|
|
|
func (s *DockerPortGuardService) loadFirewallVersion(backend string) string {
|
|
if s.version == nil {
|
|
return "-"
|
|
}
|
|
return s.version(backend)
|
|
}
|
|
|
|
func dockerFirewallVersion(backend string) string {
|
|
client, err := lifecycle.NewClientFor(backend)
|
|
if err != nil {
|
|
return "-"
|
|
}
|
|
version, err := client.Version()
|
|
if err != nil || strings.TrimSpace(version) == "" {
|
|
return "-"
|
|
}
|
|
return version
|
|
}
|
|
|
|
func discoverDockerEndpoints(ctx context.Context, cli *client.Client) ([]dto.DockerPortGuardEndpoint, error) {
|
|
containers, err := cli.ContainerList(ctx, containertypes.ListOptions{All: true})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
endpoints := make([]dto.DockerPortGuardEndpoint, 0)
|
|
for _, item := range containers {
|
|
name := strings.TrimPrefix(firstGuardString(item.Names), "/")
|
|
compose := item.Labels[dockerGuardComposeProjectLabel]
|
|
application := ""
|
|
if created, ok := item.Labels[dockerGuardComposeCreatedBy]; ok && created == "Apps" {
|
|
application = compose
|
|
}
|
|
for _, port := range item.Ports {
|
|
if port.PublicPort == 0 || (port.Type != "tcp" && port.Type != "udp") {
|
|
continue
|
|
}
|
|
family := docker_guard.FamilyIPv4
|
|
hostIP := port.IP
|
|
if addr, err := netip.ParseAddr(hostIP); err == nil && addr.Is6() {
|
|
family = docker_guard.FamilyIPv6
|
|
} else if hostIP == "" {
|
|
hostIP = "0.0.0.0"
|
|
}
|
|
endpoints = append(endpoints, dto.DockerPortGuardEndpoint{Family: family, HostIP: hostIP, HostPort: port.PublicPort, Protocol: port.Type, ContainerID: item.ID, ContainerName: name, ContainerPort: port.PrivatePort, Compose: compose, Application: application, Sources: []string{}})
|
|
}
|
|
}
|
|
return endpoints, nil
|
|
}
|
|
|
|
func dockerGuardPolicyEndpoints(policies []model.DockerPortGuardPolicy) []dto.DockerPortGuardEndpoint {
|
|
endpoints := make([]dto.DockerPortGuardEndpoint, 0, len(policies))
|
|
for _, policy := range policies {
|
|
endpoints = append(endpoints, dto.DockerPortGuardEndpoint{
|
|
Family: policy.Family, HostIP: policy.HostIP, HostPort: policy.HostPort, Protocol: policy.Protocol,
|
|
PolicyUUID: policy.UUID, Mode: policy.Mode, Sources: docker_guard.DecodeSources(policy.Sources),
|
|
Description: policy.Description,
|
|
})
|
|
}
|
|
return endpoints
|
|
}
|
|
|
|
func groupDockerGuardContainers(endpoints []dto.DockerPortGuardEndpoint) []dto.DockerPortGuardContainer {
|
|
containers := make(map[string]*dto.DockerPortGuardContainer)
|
|
order := make([]string, 0)
|
|
for _, endpoint := range endpoints {
|
|
key := endpoint.ContainerID
|
|
if key == "" {
|
|
key = "__orphan__"
|
|
}
|
|
container, ok := containers[key]
|
|
if !ok {
|
|
container = &dto.DockerPortGuardContainer{
|
|
Key: key, Name: endpoint.ContainerName, Compose: endpoint.Compose,
|
|
Application: endpoint.Application, Endpoints: []dto.DockerPortGuardEndpoint{},
|
|
}
|
|
containers[key] = container
|
|
order = append(order, key)
|
|
}
|
|
container.Endpoints = append(container.Endpoints, endpoint)
|
|
}
|
|
sort.Slice(order, func(i, j int) bool {
|
|
return containers[order[i]].Name < containers[order[j]].Name
|
|
})
|
|
|
|
result := make([]dto.DockerPortGuardContainer, 0, len(order))
|
|
for _, key := range order {
|
|
container := containers[key]
|
|
items := make([]docker.PortRangeItem, 0, len(container.Endpoints))
|
|
for i, endpoint := range container.Endpoints {
|
|
sources := append([]string(nil), endpoint.Sources...)
|
|
sort.Strings(sources)
|
|
policyKey := fmt.Sprintf("%t|%s|%s|%t|%s", endpoint.PolicyUUID != "", endpoint.Mode, strings.Join(sources, ","), endpoint.Effective, endpoint.Description)
|
|
items = append(items, docker.PortRangeItem{
|
|
Key: endpoint.Family + "|" + endpoint.HostIP + "|" + endpoint.Protocol + "|" + policyKey,
|
|
PublicPort: endpoint.HostPort, PrivatePort: endpoint.ContainerPort,
|
|
HasPrivatePort: endpoint.ContainerPort != 0, Position: i,
|
|
})
|
|
}
|
|
container.PortGroups = make([]dto.DockerPortGuardPortGroup, 0, len(items))
|
|
for _, portRange := range docker.MergePortRanges(items) {
|
|
start := container.Endpoints[portRange.Start.Position]
|
|
address := start.HostIP
|
|
if strings.Contains(address, ":") {
|
|
address = "[" + address + "]"
|
|
}
|
|
ports := fmt.Sprintf("%d", portRange.Start.PublicPort)
|
|
if portRange.Start.PublicPort != portRange.End.PublicPort {
|
|
ports = fmt.Sprintf("%d-%d", portRange.Start.PublicPort, portRange.End.PublicPort)
|
|
}
|
|
container.PortGroups = append(container.PortGroups, dto.DockerPortGuardPortGroup{
|
|
Key: fmt.Sprintf("%s|%d-%d", portRange.Start.Key, portRange.Start.PublicPort, portRange.End.PublicPort),
|
|
Label: fmt.Sprintf("%s:%s/%s", address, ports, start.Protocol), Endpoint: start,
|
|
Endpoints: func() []dto.DockerPortGuardEndpoint {
|
|
members := make([]dto.DockerPortGuardEndpoint, 0, len(portRange.Items))
|
|
for _, item := range portRange.Items {
|
|
members = append(members, container.Endpoints[item.Position])
|
|
}
|
|
return members
|
|
}(),
|
|
})
|
|
}
|
|
result = append(result, *container)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func guardEndpointKey(family, hostIP string, hostPort uint16, protocol string) string {
|
|
return fmt.Sprintf("%s|%s|%d|%s", family, hostIP, hostPort, protocol)
|
|
}
|
|
|
|
func firstGuardString(values []string) string {
|
|
if len(values) == 0 {
|
|
return ""
|
|
}
|
|
return values[0]
|
|
}
|