daemon: Improve NetworkingConfig & EndpointSettings validation
So far, only a subset of NetworkingConfig was validated when calling ContainerCreate. Other parameters would be validated when the container was started. And the same goes for EndpointSettings on NetworkConnect. This commit adds two validation steps: 1. Check if the IP addresses set in endpoint's IPAMConfig are valid, when ContainerCreate and ConnectToNetwork is called ; 2. Check if the network allows static IP addresses, only on ConnectToNetwork as we need the libnetwork's Network for that and it might not exist until NetworkAttachment requests are sent to the Swarm leader (which happens only when starting the container) ; Signed-off-by: Albin Kerouanton <albinker@gmail.com>
This commit is contained in:
parent
a33043f0e0
commit
ff503882f7
6 changed files with 50 additions and 52 deletions
|
@ -10038,6 +10038,10 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "No error"
|
description: "No error"
|
||||||
|
400:
|
||||||
|
description: "bad parameter"
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ErrorResponse"
|
||||||
403:
|
403:
|
||||||
description: "Operation not supported for swarm scoped networks"
|
description: "Operation not supported for swarm scoped networks"
|
||||||
schema:
|
schema:
|
||||||
|
|
|
@ -562,39 +562,56 @@ func (daemon *Daemon) allocateNetwork(cfg *config.Config, container *container.C
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasUserDefinedIPAddress returns whether the passed IPAM configuration contains IP address configuration
|
// validateEndpointSettings checks whether the given epConfig is valid. The nw parameter might be nil as a container
|
||||||
func hasUserDefinedIPAddress(ipamConfig *networktypes.EndpointIPAMConfig) bool {
|
// can be created with a reference to a network that don't exist yet. In that case, only partial validation will be
|
||||||
return ipamConfig != nil && (len(ipamConfig.IPv4Address) > 0 || len(ipamConfig.IPv6Address) > 0)
|
// done.
|
||||||
}
|
func validateEndpointSettings(nw *libnetwork.Network, nwName string, epConfig *networktypes.EndpointSettings) error {
|
||||||
|
if epConfig == nil {
|
||||||
// User specified ip address is acceptable only for networks with user specified subnets.
|
|
||||||
func validateNetworkingConfig(n *libnetwork.Network, epConfig *networktypes.EndpointSettings) error {
|
|
||||||
if n == nil || epConfig == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
|
||||||
if hasUserDefinedIPAddress(epConfig.IPAMConfig) && !enableIPOnPredefinedNetwork() {
|
ipamConfig := &networktypes.EndpointIPAMConfig{}
|
||||||
|
if epConfig.IPAMConfig != nil {
|
||||||
|
ipamConfig = epConfig.IPAMConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containertypes.NetworkMode(nwName).IsUserDefined() {
|
||||||
|
hasStaticAddresses := ipamConfig.IPv4Address != "" || ipamConfig.IPv6Address != ""
|
||||||
|
// On Linux, user specified IP address is accepted only by networks with user specified subnets.
|
||||||
|
if hasStaticAddresses && !enableIPOnPredefinedNetwork() {
|
||||||
return runconfig.ErrUnsupportedNetworkAndIP
|
return runconfig.ErrUnsupportedNetworkAndIP
|
||||||
}
|
}
|
||||||
if len(epConfig.Aliases) > 0 && !serviceDiscoveryOnDefaultNetwork() {
|
if len(epConfig.Aliases) > 0 && !serviceDiscoveryOnDefaultNetwork() {
|
||||||
return runconfig.ErrUnsupportedNetworkAndAlias
|
return runconfig.ErrUnsupportedNetworkAndAlias
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !hasUserDefinedIPAddress(epConfig.IPAMConfig) {
|
|
||||||
|
if ipamConfig.IPv4Address != "" {
|
||||||
|
if addr := net.ParseIP(ipamConfig.IPv4Address); addr == nil || addr.To4() == nil || addr.IsUnspecified() {
|
||||||
|
return fmt.Errorf("invalid IPv4 address: %s", ipamConfig.IPv4Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ipamConfig.IPv6Address != "" {
|
||||||
|
if addr := net.ParseIP(ipamConfig.IPv6Address); addr == nil || addr.To4() != nil || addr.IsUnspecified() {
|
||||||
|
return fmt.Errorf("invalid IPv6 address: %s", ipamConfig.IPv6Address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nw == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, nwIPv4Configs, nwIPv6Configs := n.IpamConfig()
|
_, _, nwIPv4Configs, nwIPv6Configs := nw.IpamConfig()
|
||||||
for _, s := range []struct {
|
for _, s := range []struct {
|
||||||
ipConfigured bool
|
ipConfigured bool
|
||||||
subnetConfigs []*libnetwork.IpamConf
|
subnetConfigs []*libnetwork.IpamConf
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
ipConfigured: len(epConfig.IPAMConfig.IPv4Address) > 0,
|
ipConfigured: len(ipamConfig.IPv4Address) > 0,
|
||||||
subnetConfigs: nwIPv4Configs,
|
subnetConfigs: nwIPv4Configs,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ipConfigured: len(epConfig.IPAMConfig.IPv6Address) > 0,
|
ipConfigured: len(ipamConfig.IPv6Address) > 0,
|
||||||
subnetConfigs: nwIPv6Configs,
|
subnetConfigs: nwIPv6Configs,
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
@ -657,7 +674,7 @@ func (daemon *Daemon) updateNetworkConfig(container *container.Container, n *lib
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateNetworkingConfig(n, endpointConfig); err != nil {
|
if err := validateEndpointSettings(n, n.Name(), endpointConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -367,11 +367,13 @@ func isLinkable(child *container.Container) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(aker): remove when we make the default bridge network behave like any other network
|
||||||
func enableIPOnPredefinedNetwork() bool {
|
func enableIPOnPredefinedNetwork() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// serviceDiscoveryOnDefaultNetwork indicates if service discovery is supported on the default network
|
// serviceDiscoveryOnDefaultNetwork indicates if service discovery is supported on the default network
|
||||||
|
// TODO(aker): remove when we make the default bridge network behave like any other network
|
||||||
func serviceDiscoveryOnDefaultNetwork() bool {
|
func serviceDiscoveryOnDefaultNetwork() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -98,7 +97,7 @@ func (daemon *Daemon) containerCreate(ctx context.Context, daemonCfg *configStor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = verifyNetworkingConfig(opts.params.NetworkingConfig)
|
err = daemon.validateNetworkingConfig(opts.params.NetworkingConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err)
|
return containertypes.CreateResponse{Warnings: warnings}, errdefs.InvalidParameter(err)
|
||||||
}
|
}
|
||||||
|
@ -321,8 +320,8 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *containertypes.Config, img *i
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifyNetworkingConfig validates if the nwConfig is valid.
|
// validateNetworkingConfig checks whether a container's NetworkingConfig is valid.
|
||||||
func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
|
func (daemon *Daemon) validateNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
|
||||||
if nwConfig == nil {
|
if nwConfig == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -331,20 +330,14 @@ func verifyNetworkingConfig(nwConfig *networktypes.NetworkingConfig) error {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return fmt.Errorf("no EndpointSettings for %s", k)
|
return fmt.Errorf("no EndpointSettings for %s", k)
|
||||||
}
|
}
|
||||||
if v.IPAMConfig != nil {
|
|
||||||
if v.IPAMConfig.IPv4Address != "" && net.ParseIP(v.IPAMConfig.IPv4Address).To4() == nil {
|
// The referenced network k might not exist when the container is created, so just ignore the error in that case.
|
||||||
return fmt.Errorf("invalid IPv4 address: %s", v.IPAMConfig.IPv4Address)
|
nw, _ := daemon.FindNetwork(k)
|
||||||
}
|
if err := validateEndpointSettings(nw, k, v); err != nil {
|
||||||
if v.IPAMConfig.IPv6Address != "" {
|
return err
|
||||||
n := net.ParseIP(v.IPAMConfig.IPv6Address)
|
|
||||||
// if the address is an invalid network address (ParseIP == nil) or if it is
|
|
||||||
// an IPv4 address (To4() != nil), then it is an invalid IPv6 address
|
|
||||||
if n == nil || n.To4() != nil {
|
|
||||||
return fmt.Errorf("invalid IPv6 address: %s", v.IPAMConfig.IPv6Address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
package daemon // import "github.com/docker/docker/daemon"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"gotest.tools/v3/assert"
|
|
||||||
is "gotest.tools/v3/assert/cmp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test case for 35752
|
|
||||||
func TestVerifyNetworkingConfig(t *testing.T) {
|
|
||||||
name := "mynet"
|
|
||||||
endpoints := make(map[string]*network.EndpointSettings, 1)
|
|
||||||
endpoints[name] = nil
|
|
||||||
nwConfig := &network.NetworkingConfig{
|
|
||||||
EndpointsConfig: endpoints,
|
|
||||||
}
|
|
||||||
err := verifyNetworkingConfig(nwConfig)
|
|
||||||
assert.Check(t, is.Error(err, "no EndpointSettings for mynet"), "should produce an error because no EndpointSettings were passed")
|
|
||||||
}
|
|
|
@ -47,6 +47,9 @@ keywords: "API, Docker, rcli, REST, documentation"
|
||||||
1.44.
|
1.44.
|
||||||
* `POST /containers/create` now accepts multiple `EndpointSettings` in
|
* `POST /containers/create` now accepts multiple `EndpointSettings` in
|
||||||
`NetworkingConfig.EndpointSettings`.
|
`NetworkingConfig.EndpointSettings`.
|
||||||
|
* `POST /containers/create` and `POST /networks/{id}/connect` will now catch
|
||||||
|
validation errors that were previously only returned during `POST /containers/{id}/start`.
|
||||||
|
Note that this change is _unversioned_ and applies to all API versions.
|
||||||
|
|
||||||
## v1.43 API changes
|
## v1.43 API changes
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue