2018-11-04 19:52:26 -05:00
package daemon // import "github.com/docker/docker/integration/daemon"
import (
2021-07-16 03:33:00 -04:00
"context"
"fmt"
"net/http"
"net/http/httptest"
2018-11-04 19:52:26 -05:00
"os"
"os/exec"
"path/filepath"
"runtime"
2021-08-31 08:13:30 -04:00
"strings"
"syscall"
2018-11-04 19:52:26 -05:00
"testing"
2021-07-16 03:33:00 -04:00
"github.com/docker/docker/api/types"
2021-06-07 07:50:00 -04:00
"github.com/docker/docker/daemon/config"
2018-11-04 19:52:26 -05:00
"github.com/docker/docker/testutil/daemon"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
2021-07-16 03:33:00 -04:00
"gotest.tools/v3/env"
2021-06-07 07:50:00 -04:00
"gotest.tools/v3/skip"
2018-11-04 19:52:26 -05:00
)
func TestConfigDaemonLibtrustID ( t * testing . T ) {
2021-09-09 07:43:26 -04:00
skip . If ( t , runtime . GOOS == "windows" )
2018-11-04 19:52:26 -05:00
d := daemon . New ( t )
defer d . Stop ( t )
trustKey := filepath . Join ( d . RootDir ( ) , "key.json" )
2021-08-24 06:10:50 -04:00
err := os . WriteFile ( trustKey , [ ] byte ( ` { "crv":"P-256","d":"dm28PH4Z4EbyUN8L0bPonAciAQa1QJmmyYd876mnypY","kid":"WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB","kty":"EC","x":"Mh5-JINSjaa_EZdXDttri255Z5fbCEOTQIZjAcScFTk","y":"eUyuAjfxevb07hCCpvi4Zi334Dy4GDWQvEToGEX4exQ"} ` ) , 0644 )
2018-11-04 19:52:26 -05:00
assert . NilError ( t , err )
config := filepath . Join ( d . RootDir ( ) , "daemon.json" )
2021-08-24 06:10:50 -04:00
err = os . WriteFile ( config , [ ] byte ( ` { "deprecated-key-path": " ` + trustKey + ` "} ` ) , 0644 )
2018-11-04 19:52:26 -05:00
assert . NilError ( t , err )
d . Start ( t , "--config-file" , config )
info := d . Info ( t )
assert . Equal ( t , info . ID , "WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB" )
}
func TestDaemonConfigValidation ( t * testing . T ) {
2021-09-09 07:43:26 -04:00
skip . If ( t , runtime . GOOS == "windows" )
2018-11-04 19:52:26 -05:00
d := daemon . New ( t )
dockerBinary , err := d . BinaryPath ( )
assert . NilError ( t , err )
params := [ ] string { "--validate" , "--config-file" }
dest := os . Getenv ( "DOCKER_INTEGRATION_DAEMON_DEST" )
if dest == "" {
dest = os . Getenv ( "DEST" )
}
testdata := filepath . Join ( dest , ".." , ".." , "integration" , "daemon" , "testdata" )
const (
validOut = "configuration OK"
failedOut = "unable to configure the Docker daemon with file"
)
tests := [ ] struct {
name string
args [ ] string
expectedOut string
} {
{
name : "config with no content" ,
args : append ( params , filepath . Join ( testdata , "empty-config-1.json" ) ) ,
expectedOut : validOut ,
} ,
{
name : "config with {}" ,
args : append ( params , filepath . Join ( testdata , "empty-config-2.json" ) ) ,
expectedOut : validOut ,
} ,
{
name : "invalid config" ,
args : append ( params , filepath . Join ( testdata , "invalid-config-1.json" ) ) ,
expectedOut : failedOut ,
} ,
{
name : "malformed config" ,
args : append ( params , filepath . Join ( testdata , "malformed-config.json" ) ) ,
expectedOut : failedOut ,
} ,
{
name : "valid config" ,
args : append ( params , filepath . Join ( testdata , "valid-config-1.json" ) ) ,
expectedOut : validOut ,
} ,
}
for _ , tc := range tests {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
t . Parallel ( )
cmd := exec . Command ( dockerBinary , tc . args ... )
out , err := cmd . CombinedOutput ( )
assert . Check ( t , is . Contains ( string ( out ) , tc . expectedOut ) )
if tc . expectedOut == failedOut {
assert . ErrorContains ( t , err , "" , "expected an error, but got none" )
} else {
assert . NilError ( t , err )
}
} )
}
}
2021-06-07 07:50:00 -04:00
func TestConfigDaemonSeccompProfiles ( t * testing . T ) {
2021-09-09 07:43:26 -04:00
skip . If ( t , runtime . GOOS == "windows" )
2021-06-07 07:50:00 -04:00
d := daemon . New ( t )
defer d . Stop ( t )
tests := [ ] struct {
doc string
profile string
expectedProfile string
} {
{
doc : "empty profile set" ,
profile : "" ,
expectedProfile : config . SeccompProfileDefault ,
} ,
2021-06-07 08:25:52 -04:00
{
doc : "default profile" ,
profile : config . SeccompProfileDefault ,
expectedProfile : config . SeccompProfileDefault ,
} ,
2021-06-07 07:50:00 -04:00
{
doc : "unconfined profile" ,
profile : config . SeccompProfileUnconfined ,
expectedProfile : config . SeccompProfileUnconfined ,
} ,
}
for _ , tc := range tests {
tc := tc
t . Run ( tc . doc , func ( t * testing . T ) {
d . Start ( t , "--seccomp-profile=" + tc . profile )
info := d . Info ( t )
assert . Assert ( t , is . Contains ( info . SecurityOptions , "name=seccomp,profile=" + tc . expectedProfile ) )
d . Stop ( t )
cfg := filepath . Join ( d . RootDir ( ) , "daemon.json" )
2021-08-24 06:10:50 -04:00
err := os . WriteFile ( cfg , [ ] byte ( ` { "seccomp-profile": " ` + tc . profile + ` "} ` ) , 0644 )
2021-06-07 07:50:00 -04:00
assert . NilError ( t , err )
d . Start ( t , "--config-file" , cfg )
info = d . Info ( t )
assert . Assert ( t , is . Contains ( info . SecurityOptions , "name=seccomp,profile=" + tc . expectedProfile ) )
d . Stop ( t )
} )
}
}
2021-07-16 03:33:00 -04:00
func TestDaemonProxy ( t * testing . T ) {
skip . If ( t , runtime . GOOS == "windows" , "cannot start multiple daemons on windows" )
2021-09-13 03:40:01 -04:00
skip . If ( t , os . Getenv ( "DOCKER_ROOTLESS" ) != "" , "cannot connect to localhost proxy in rootless environment" )
2021-07-16 03:33:00 -04:00
var received string
proxyServer := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
received = r . Host
w . Header ( ) . Set ( "Content-Type" , "application/json" )
_ , _ = w . Write ( [ ] byte ( "OK" ) )
} ) )
defer proxyServer . Close ( )
2021-08-31 08:13:30 -04:00
const userPass = "myuser:mypassword@"
2021-07-16 03:33:00 -04:00
// Configure proxy through env-vars
t . Run ( "environment variables" , func ( t * testing . T ) {
defer env . Patch ( t , "HTTP_PROXY" , proxyServer . URL ) ( )
defer env . Patch ( t , "HTTPS_PROXY" , proxyServer . URL ) ( )
defer env . Patch ( t , "NO_PROXY" , "example.com" ) ( )
d := daemon . New ( t )
c := d . NewClientT ( t )
defer func ( ) { _ = c . Close ( ) } ( )
ctx := context . Background ( )
d . Start ( t )
_ , err := c . ImagePull ( ctx , "example.org:5000/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5000" )
// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
_ , err = c . ImagePull ( ctx , "example.com/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5000" , "should not have used proxy" )
info := d . Info ( t )
assert . Equal ( t , info . HTTPProxy , proxyServer . URL )
assert . Equal ( t , info . HTTPSProxy , proxyServer . URL )
assert . Equal ( t , info . NoProxy , "example.com" )
d . Stop ( t )
} )
// Configure proxy through command-line flags
t . Run ( "command-line options" , func ( t * testing . T ) {
2021-08-31 08:13:30 -04:00
defer env . Patch ( t , "HTTP_PROXY" , "http://" + userPass + "from-env-http.invalid" ) ( )
defer env . Patch ( t , "http_proxy" , "http://" + userPass + "from-env-http.invalid" ) ( )
defer env . Patch ( t , "HTTPS_PROXY" , "https://" + userPass + "myuser:mypassword@from-env-https.invalid" ) ( )
defer env . Patch ( t , "https_proxy" , "https://" + userPass + "myuser:mypassword@from-env-https.invalid" ) ( )
2021-07-16 03:33:00 -04:00
defer env . Patch ( t , "NO_PROXY" , "ignore.invalid" ) ( )
defer env . Patch ( t , "no_proxy" , "ignore.invalid" ) ( )
d := daemon . New ( t )
d . Start ( t , "--http-proxy" , proxyServer . URL , "--https-proxy" , proxyServer . URL , "--no-proxy" , "example.com" )
logs , err := d . ReadLogFile ( )
assert . NilError ( t , err )
assert . Assert ( t , is . Contains ( string ( logs ) , "overriding existing proxy variable with value from configuration" ) )
for _ , v := range [ ] string { "http_proxy" , "HTTP_PROXY" , "https_proxy" , "HTTPS_PROXY" , "no_proxy" , "NO_PROXY" } {
assert . Assert ( t , is . Contains ( string ( logs ) , "name=" + v ) )
2021-08-31 08:13:30 -04:00
assert . Assert ( t , ! strings . Contains ( string ( logs ) , userPass ) , "logs should not contain the non-sanitized proxy URL: %s" , string ( logs ) )
2021-07-16 03:33:00 -04:00
}
c := d . NewClientT ( t )
defer func ( ) { _ = c . Close ( ) } ( )
ctx := context . Background ( )
_ , err = c . ImagePull ( ctx , "example.org:5001/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5001" )
// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
_ , err = c . ImagePull ( ctx , "example.com/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5001" , "should not have used proxy" )
info := d . Info ( t )
assert . Equal ( t , info . HTTPProxy , proxyServer . URL )
assert . Equal ( t , info . HTTPSProxy , proxyServer . URL )
assert . Equal ( t , info . NoProxy , "example.com" )
d . Stop ( t )
} )
// Configure proxy through configuration file
t . Run ( "configuration file" , func ( t * testing . T ) {
2021-08-31 08:13:30 -04:00
defer env . Patch ( t , "HTTP_PROXY" , "http://" + userPass + "from-env-http.invalid" ) ( )
defer env . Patch ( t , "http_proxy" , "http://" + userPass + "from-env-http.invalid" ) ( )
defer env . Patch ( t , "HTTPS_PROXY" , "https://" + userPass + "myuser:mypassword@from-env-https.invalid" ) ( )
defer env . Patch ( t , "https_proxy" , "https://" + userPass + "myuser:mypassword@from-env-https.invalid" ) ( )
2021-07-16 03:33:00 -04:00
defer env . Patch ( t , "NO_PROXY" , "ignore.invalid" ) ( )
defer env . Patch ( t , "no_proxy" , "ignore.invalid" ) ( )
d := daemon . New ( t )
c := d . NewClientT ( t )
defer func ( ) { _ = c . Close ( ) } ( )
ctx := context . Background ( )
configFile := filepath . Join ( d . RootDir ( ) , "daemon.json" )
daemon/config: move proxy settings to "proxies" struct within daemon.json
This is a follow-up to 427c7cc5f86364466c7173e8ca59b97c3876471d, which added
proxy-configuration options ("http-proxy", "https-proxy", "no-proxy") to the
dockerd cli and in `daemon.json`.
While working on documentation changes for this feature, I realised that those
options won't be "next" to each-other when formatting the daemon.json JSON, for
example using `jq` (which sorts the fields alphabetically). As it's possible that
additional proxy configuration options are added in future, I considered that
grouping these options in a struct within the JSON may help setting these options,
as well as discovering related options.
This patch introduces a "proxies" field in the JSON, which includes the
"http-proxy", "https-proxy", "no-proxy" options.
Conflict detection continues to work as before; with this patch applied:
mkdir -p /etc/docker/
echo '{"proxies":{"http-proxy":"http-config", "https-proxy":"https-config", "no-proxy": "no-proxy-config"}}' > /etc/docker/daemon.json
dockerd --http-proxy=http-flag --https-proxy=https-flag --no-proxy=no-proxy-flag --validate
unable to configure the Docker daemon with file /etc/docker/daemon.json:
the following directives are specified both as a flag and in the configuration file:
http-proxy: (from flag: http-flag, from file: http-config),
https-proxy: (from flag: https-flag, from file: https-config),
no-proxy: (from flag: no-proxy-flag, from file: no-proxy-config)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-04-02 10:03:39 -04:00
configJSON := fmt . Sprintf ( ` { "proxies": { "http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}} ` , proxyServer . URL )
2021-07-16 03:33:00 -04:00
assert . NilError ( t , os . WriteFile ( configFile , [ ] byte ( configJSON ) , 0644 ) )
d . Start ( t , "--config-file" , configFile )
logs , err := d . ReadLogFile ( )
assert . NilError ( t , err )
assert . Assert ( t , is . Contains ( string ( logs ) , "overriding existing proxy variable with value from configuration" ) )
for _ , v := range [ ] string { "http_proxy" , "HTTP_PROXY" , "https_proxy" , "HTTPS_PROXY" , "no_proxy" , "NO_PROXY" } {
assert . Assert ( t , is . Contains ( string ( logs ) , "name=" + v ) )
2021-08-31 08:13:30 -04:00
assert . Assert ( t , ! strings . Contains ( string ( logs ) , userPass ) , "logs should not contain the non-sanitized proxy URL: %s" , string ( logs ) )
2021-07-16 03:33:00 -04:00
}
_ , err = c . ImagePull ( ctx , "example.org:5002/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5002" )
// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
_ , err = c . ImagePull ( ctx , "example.com/some/image:latest" , types . ImagePullOptions { } )
assert . ErrorContains ( t , err , "" , "pulling should have failed" )
assert . Equal ( t , received , "example.org:5002" , "should not have used proxy" )
info := d . Info ( t )
assert . Equal ( t , info . HTTPProxy , proxyServer . URL )
assert . Equal ( t , info . HTTPSProxy , proxyServer . URL )
assert . Equal ( t , info . NoProxy , "example.com" )
d . Stop ( t )
} )
// Conflicting options (passed both through command-line options and config file)
t . Run ( "conflicting options" , func ( t * testing . T ) {
const (
2021-08-31 08:13:30 -04:00
proxyRawURL = "https://" + userPass + "example.org"
proxyURL = "https://xxxxx:xxxxx@example.org"
2021-07-16 03:33:00 -04:00
)
d := daemon . New ( t )
configFile := filepath . Join ( d . RootDir ( ) , "daemon.json" )
daemon/config: move proxy settings to "proxies" struct within daemon.json
This is a follow-up to 427c7cc5f86364466c7173e8ca59b97c3876471d, which added
proxy-configuration options ("http-proxy", "https-proxy", "no-proxy") to the
dockerd cli and in `daemon.json`.
While working on documentation changes for this feature, I realised that those
options won't be "next" to each-other when formatting the daemon.json JSON, for
example using `jq` (which sorts the fields alphabetically). As it's possible that
additional proxy configuration options are added in future, I considered that
grouping these options in a struct within the JSON may help setting these options,
as well as discovering related options.
This patch introduces a "proxies" field in the JSON, which includes the
"http-proxy", "https-proxy", "no-proxy" options.
Conflict detection continues to work as before; with this patch applied:
mkdir -p /etc/docker/
echo '{"proxies":{"http-proxy":"http-config", "https-proxy":"https-config", "no-proxy": "no-proxy-config"}}' > /etc/docker/daemon.json
dockerd --http-proxy=http-flag --https-proxy=https-flag --no-proxy=no-proxy-flag --validate
unable to configure the Docker daemon with file /etc/docker/daemon.json:
the following directives are specified both as a flag and in the configuration file:
http-proxy: (from flag: http-flag, from file: http-config),
https-proxy: (from flag: https-flag, from file: https-config),
no-proxy: (from flag: no-proxy-flag, from file: no-proxy-config)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-04-02 10:03:39 -04:00
configJSON := fmt . Sprintf ( ` { "proxies": { "http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}} ` , proxyRawURL )
2021-07-16 03:33:00 -04:00
assert . NilError ( t , os . WriteFile ( configFile , [ ] byte ( configJSON ) , 0644 ) )
err := d . StartWithError ( "--http-proxy" , proxyRawURL , "--https-proxy" , proxyRawURL , "--no-proxy" , "example.com" , "--config-file" , configFile , "--validate" )
assert . ErrorContains ( t , err , "daemon exited during startup" )
logs , err := d . ReadLogFile ( )
assert . NilError ( t , err )
expected := fmt . Sprintf (
` the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com) ` ,
2021-08-31 08:13:30 -04:00
proxyURL ,
2021-07-16 03:33:00 -04:00
)
assert . Assert ( t , is . Contains ( string ( logs ) , expected ) )
} )
2021-08-31 08:13:30 -04:00
// Make sure values are sanitized when reloading the daemon-config
t . Run ( "reload sanitized" , func ( t * testing . T ) {
const (
proxyRawURL = "https://" + userPass + "example.org"
proxyURL = "https://xxxxx:xxxxx@example.org"
)
d := daemon . New ( t )
d . Start ( t , "--http-proxy" , proxyRawURL , "--https-proxy" , proxyRawURL , "--no-proxy" , "example.com" )
defer d . Stop ( t )
err := d . Signal ( syscall . SIGHUP )
assert . NilError ( t , err )
logs , err := d . ReadLogFile ( )
assert . NilError ( t , err )
// FIXME: there appears to ba a race condition, which causes ReadLogFile
// to not contain the full logs after signaling the daemon to reload,
// causing the test to fail here. As a workaround, check if we
// received the "reloaded" message after signaling, and only then
// check that it's sanitized properly. For more details on this
// issue, see https://github.com/moby/moby/pull/42835/files#r713120315
if ! strings . Contains ( string ( logs ) , "Reloaded configuration:" ) {
t . Skip ( "Skipping test, because we did not find 'Reloaded configuration' in the logs" )
}
assert . Assert ( t , is . Contains ( string ( logs ) , proxyURL ) )
assert . Assert ( t , ! strings . Contains ( string ( logs ) , userPass ) , "logs should not contain the non-sanitized proxy URL: %s" , string ( logs ) )
} )
2021-07-16 03:33:00 -04:00
}