Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package libpod
import (
"fmt"
"net"
"os"
"strings"

"github.com/moby/sys/capability"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
Expand All @@ -17,6 +19,31 @@ import (
"go.podman.io/podman/v6/pkg/rootless"
)

func hasCapNetAdmin() (bool, error) {
currentCaps, err := capability.NewPid2(0)
if err != nil {
return false, err
}
if err = currentCaps.Load(); err != nil {
return false, err
}
return currentCaps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN), nil
}

func rootfulBridgeNetworkPreflight(hasNetAdmin func() (bool, error)) error {
// UID 0 inside a delegated container environment should use rootful
// networking semantics; fail clearly if the environment lacks the
// capability needed to configure bridge networking.
hasCap, err := hasNetAdmin()
if err != nil {
return fmt.Errorf("checking CAP_NET_ADMIN for rootful netavark bridge networking: %w", err)
}
if !hasCap {
return fmt.Errorf("rootful netavark bridge networking requires CAP_NET_ADMIN in the current environment; Podman is running as root but this capability is not available")
}
return nil
}

// Create and configure a new network namespace for a container
func (r *Runtime) configureNetNS(ctr *Container, ctrNS string) (status map[string]types.StatusBlock, rerr error) {
if err := r.exposeMachinePorts(ctr.config.PortMappings); err != nil {
Expand Down Expand Up @@ -45,6 +72,11 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS string) (status map[strin
if len(networks) == 0 {
return nil, nil
}
if os.Geteuid() == 0 && !rootless.IsRootless() {
if err := rootfulBridgeNetworkPreflight(hasCapNetAdmin); err != nil {
return nil, err
}
}

netOpts := ctr.getNetworkOptions(networks)
netStatus, err := r.setUpNetwork(ctrNS, netOpts)
Expand Down
50 changes: 50 additions & 0 deletions libpod/networking_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
package libpod

import (
"errors"
"net"
"reflect"
"strings"
"testing"

"go.podman.io/common/libnetwork/types"
Expand Down Expand Up @@ -212,3 +214,51 @@ func Test_resultToBasicNetworkConfig(t *testing.T) {
})
}
}

func TestRootfulBridgeNetworkPreflight(t *testing.T) {
t.Parallel()

testErr := errors.New("capability read failed")
testCases := []struct {
name string
capNetAdmin bool
capErr error
expectErr string
}{
{
name: "root with CAP_NET_ADMIN is allowed",
capNetAdmin: true,
},
{
name: "root without CAP_NET_ADMIN fails",
expectErr: "requires CAP_NET_ADMIN",
},
{
name: "root capability check error fails",
capErr: testErr,
expectErr: "checking CAP_NET_ADMIN",
},
}

for _, tcl := range testCases {
tc := tcl
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := rootfulBridgeNetworkPreflight(func() (bool, error) {
return tc.capNetAdmin, tc.capErr
})
if tc.expectErr == "" {
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
return
}
if err == nil {
t.Fatalf("expected error containing %q", tc.expectErr)
}
if !strings.Contains(err.Error(), tc.expectErr) {
t.Fatalf("expected error containing %q, got %q", tc.expectErr, err.Error())
}
})
}
}