integration/plugin/authz: port tests from integration-cli
Signed-off-by: David Sheets <dsheets@docker.com>
This commit is contained in:
parent
c982ee805d
commit
928b0631c9
|
@ -15,7 +15,7 @@ source "$SCRIPTDIR/make/.go-autogen"
|
||||||
|
|
||||||
integration_api_dirs=${TEST_INTEGRATION_DIR:-"$(
|
integration_api_dirs=${TEST_INTEGRATION_DIR:-"$(
|
||||||
find ./integration -type d |
|
find ./integration -type d |
|
||||||
grep -vE '^(./integration$|./integration/util)')"}
|
grep -vE '^(./integration($|/util|/internal|/testdata|/plugin$))')"}
|
||||||
|
|
||||||
run_test_integration() {
|
run_test_integration() {
|
||||||
[[ "$TESTFLAGS" != *-check.f* ]] && run_test_integration_suites
|
[[ "$TESTFLAGS" != *-check.f* ]] && run_test_integration_suites
|
||||||
|
|
|
@ -1,162 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/checker"
|
|
||||||
"github.com/docker/docker/integration-cli/daemon"
|
|
||||||
"github.com/go-check/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
authzPluginName = "riyaz/authz-no-volume-plugin"
|
|
||||||
authzPluginTag = "latest"
|
|
||||||
authzPluginNameWithTag = authzPluginName + ":" + authzPluginTag
|
|
||||||
authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
|
|
||||||
nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
check.Suite(&DockerAuthzV2Suite{
|
|
||||||
ds: &DockerSuite{},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerAuthzV2Suite struct {
|
|
||||||
ds *DockerSuite
|
|
||||||
d *daemon.Daemon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) SetUpTest(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, Network, SameHostDaemon)
|
|
||||||
s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
|
|
||||||
Experimental: testEnv.ExperimentalDaemon(),
|
|
||||||
})
|
|
||||||
s.d.Start(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TearDownTest(c *check.C) {
|
|
||||||
if s.d != nil {
|
|
||||||
s.d.Stop(c)
|
|
||||||
s.ds.TearDownTest(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TestAuthZPluginAllowNonVolumeRequest(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
|
||||||
|
|
||||||
// Install authz plugin
|
|
||||||
_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
|
|
||||||
// because it needs to pull busybox
|
|
||||||
s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
// defer disabling the plugin
|
|
||||||
defer func() {
|
|
||||||
s.d.Restart(c)
|
|
||||||
_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Ensure docker run command and accompanying docker ps are successful
|
|
||||||
_, err = s.d.Cmd("run", "-d", "busybox", "top")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TestAuthZPluginDisable(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
|
||||||
// Install authz plugin
|
|
||||||
_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
|
|
||||||
// because it needs to pull busybox
|
|
||||||
s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
// defer removing the plugin
|
|
||||||
defer func() {
|
|
||||||
s.d.Restart(c)
|
|
||||||
_, err = s.d.Cmd("plugin", "rm", "-f", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
out, err := s.d.Cmd("volume", "create")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
|
|
||||||
// disable the plugin
|
|
||||||
_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
// now test to see if the docker api works.
|
|
||||||
_, err = s.d.Cmd("volume", "create")
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TestAuthZPluginRejectVolumeRequests(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
|
||||||
// Install authz plugin
|
|
||||||
_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
// restart the daemon with the plugin
|
|
||||||
s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
|
|
||||||
|
|
||||||
// defer disabling the plugin
|
|
||||||
defer func() {
|
|
||||||
s.d.Restart(c)
|
|
||||||
_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}()
|
|
||||||
|
|
||||||
out, err := s.d.Cmd("volume", "create")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
|
|
||||||
out, err = s.d.Cmd("volume", "ls")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
|
|
||||||
// The plugin will block the command before it can determine the volume does not exist
|
|
||||||
out, err = s.d.Cmd("volume", "rm", "test")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
|
|
||||||
out, err = s.d.Cmd("volume", "inspect", "test")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
|
|
||||||
out, err = s.d.Cmd("volume", "prune", "-f")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TestAuthZPluginBadManifestFailsDaemonStart(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, IsAmd64, Network)
|
|
||||||
// Install authz plugin with bad manifest
|
|
||||||
_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginBadManifestName)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
// start the daemon with the plugin, it will error
|
|
||||||
c.Assert(s.d.RestartWithError("--authorization-plugin="+authzPluginBadManifestName), check.NotNil)
|
|
||||||
|
|
||||||
// restarting the daemon without requiring the plugin will succeed
|
|
||||||
s.d.Restart(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzV2Suite) TestNonexistentAuthZPluginFailsDaemonStart(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux, Network)
|
|
||||||
// start the daemon with a non-existent authz plugin, it will error
|
|
||||||
c.Assert(s.d.RestartWithError("--authorization-plugin="+nonexistentAuthzPluginName), check.NotNil)
|
|
||||||
|
|
||||||
// restarting the daemon without requiring the plugin will succeed
|
|
||||||
s.d.Start(c)
|
|
||||||
}
|
|
|
@ -1,470 +0,0 @@
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"net"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/docker/docker/integration-cli/checker"
|
|
||||||
"github.com/docker/docker/integration-cli/daemon"
|
|
||||||
"github.com/docker/docker/pkg/authorization"
|
|
||||||
"github.com/docker/docker/pkg/plugins"
|
|
||||||
"github.com/go-check/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testAuthZPlugin = "authzplugin"
|
|
||||||
unauthorizedMessage = "User unauthorized authz plugin"
|
|
||||||
errorMessage = "something went wrong..."
|
|
||||||
containerListAPI = "/containers/json"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
alwaysAllowed = []string{"/_ping", "/info"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
check.Suite(&DockerAuthzSuite{
|
|
||||||
ds: &DockerSuite{},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type DockerAuthzSuite struct {
|
|
||||||
server *httptest.Server
|
|
||||||
ds *DockerSuite
|
|
||||||
d *daemon.Daemon
|
|
||||||
ctrl *authorizationController
|
|
||||||
}
|
|
||||||
|
|
||||||
type authorizationController struct {
|
|
||||||
reqRes authorization.Response // reqRes holds the plugin response to the initial client request
|
|
||||||
resRes authorization.Response // resRes holds the plugin response to the daemon response
|
|
||||||
psRequestCnt int // psRequestCnt counts the number of calls to list container request api
|
|
||||||
psResponseCnt int // psResponseCnt counts the number of calls to list containers response API
|
|
||||||
requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller
|
|
||||||
reqUser string
|
|
||||||
resUser string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) SetUpTest(c *check.C) {
|
|
||||||
testRequires(c, SameHostDaemon)
|
|
||||||
s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
|
|
||||||
Experimental: testEnv.ExperimentalDaemon(),
|
|
||||||
})
|
|
||||||
s.ctrl = &authorizationController{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TearDownTest(c *check.C) {
|
|
||||||
if s.d != nil {
|
|
||||||
s.d.Stop(c)
|
|
||||||
s.ds.TearDownTest(c)
|
|
||||||
s.ctrl = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) SetUpSuite(c *check.C) {
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
s.server = httptest.NewServer(mux)
|
|
||||||
|
|
||||||
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
w.Write(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
authReq := authorization.Request{}
|
|
||||||
err = json.Unmarshal(body, &authReq)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
|
|
||||||
assertAuthHeaders(c, authReq.RequestHeaders)
|
|
||||||
|
|
||||||
// Count only container list api
|
|
||||||
if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
|
|
||||||
s.ctrl.psRequestCnt++
|
|
||||||
}
|
|
||||||
|
|
||||||
s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI)
|
|
||||||
|
|
||||||
reqRes := s.ctrl.reqRes
|
|
||||||
if isAllowed(authReq.RequestURI) {
|
|
||||||
reqRes = authorization.Response{Allow: true}
|
|
||||||
}
|
|
||||||
if reqRes.Err != "" {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(reqRes)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
s.ctrl.reqUser = authReq.User
|
|
||||||
w.Write(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer r.Body.Close()
|
|
||||||
body, err := ioutil.ReadAll(r.Body)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
authReq := authorization.Request{}
|
|
||||||
err = json.Unmarshal(body, &authReq)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
|
|
||||||
assertAuthHeaders(c, authReq.ResponseHeaders)
|
|
||||||
|
|
||||||
// Count only container list api
|
|
||||||
if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
|
|
||||||
s.ctrl.psResponseCnt++
|
|
||||||
}
|
|
||||||
resRes := s.ctrl.resRes
|
|
||||||
if isAllowed(authReq.RequestURI) {
|
|
||||||
resRes = authorization.Response{Allow: true}
|
|
||||||
}
|
|
||||||
if resRes.Err != "" {
|
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(resRes)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
s.ctrl.resUser = authReq.User
|
|
||||||
w.Write(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
|
|
||||||
fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
|
|
||||||
err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for always allowed endpoints to not inhibit test framework functions
|
|
||||||
func isAllowed(reqURI string) bool {
|
|
||||||
for _, endpoint := range alwaysAllowed {
|
|
||||||
if strings.HasSuffix(reqURI, endpoint) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertAuthHeaders validates authentication headers are removed
|
|
||||||
func assertAuthHeaders(c *check.C, headers map[string]string) error {
|
|
||||||
for k := range headers {
|
|
||||||
if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
|
|
||||||
c.Errorf("Found authentication headers in request '%v'", headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertBody asserts that body is removed for non text/json requests
|
|
||||||
func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) {
|
|
||||||
if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
|
|
||||||
//return fmt.Errorf("Body included for authentication endpoint %s", string(body))
|
|
||||||
c.Errorf("Body included for authentication endpoint %s", string(body))
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range headers {
|
|
||||||
if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(body) > 0 {
|
|
||||||
c.Errorf("Body included while it should not (Headers: '%v')", headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TearDownSuite(c *check.C) {
|
|
||||||
if s.server == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.server.Close()
|
|
||||||
|
|
||||||
err := os.RemoveAll("/etc/docker/plugins")
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
|
|
||||||
// start the daemon and load busybox, --net=none build fails otherwise
|
|
||||||
// cause it needs to pull busybox
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
// Ensure command successful
|
|
||||||
out, err := s.d.Cmd("run", "-d", "busybox", "top")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
|
|
||||||
id := strings.TrimSpace(out)
|
|
||||||
assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
|
|
||||||
assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginTls(c *check.C) {
|
|
||||||
|
|
||||||
const testDaemonHTTPSAddr = "tcp://localhost:4271"
|
|
||||||
// start the daemon and load busybox, --net=none build fails otherwise
|
|
||||||
// cause it needs to pull busybox
|
|
||||||
s.d.Start(c,
|
|
||||||
"--authorization-plugin="+testAuthZPlugin,
|
|
||||||
"--tlsverify",
|
|
||||||
"--tlscacert",
|
|
||||||
"fixtures/https/ca.pem",
|
|
||||||
"--tlscert",
|
|
||||||
"fixtures/https/server-cert.pem",
|
|
||||||
"--tlskey",
|
|
||||||
"fixtures/https/server-key.pem",
|
|
||||||
"-H", testDaemonHTTPSAddr)
|
|
||||||
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
|
|
||||||
out, _ := dockerCmd(
|
|
||||||
c,
|
|
||||||
"--tlsverify",
|
|
||||||
"--tlscacert", "fixtures/https/ca.pem",
|
|
||||||
"--tlscert", "fixtures/https/client-cert.pem",
|
|
||||||
"--tlskey", "fixtures/https/client-key.pem",
|
|
||||||
"-H",
|
|
||||||
testDaemonHTTPSAddr,
|
|
||||||
"version",
|
|
||||||
)
|
|
||||||
if !strings.Contains(out, "Server") {
|
|
||||||
c.Fatalf("docker version should return information of server side")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(s.ctrl.reqUser, check.Equals, "client")
|
|
||||||
c.Assert(s.ctrl.resUser, check.Equals, "client")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = false
|
|
||||||
s.ctrl.reqRes.Msg = unauthorizedMessage
|
|
||||||
|
|
||||||
// Ensure command is blocked
|
|
||||||
res, err := s.d.Cmd("ps")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
|
|
||||||
c.Assert(s.ctrl.psResponseCnt, check.Equals, 0)
|
|
||||||
|
|
||||||
// Ensure unauthorized message appears in response
|
|
||||||
c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAuthZPluginAPIDenyResponse validates that when authorization plugin deny the request, the status code is forbidden
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginAPIDenyResponse(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = false
|
|
||||||
s.ctrl.resRes.Msg = unauthorizedMessage
|
|
||||||
|
|
||||||
daemonURL, err := url.Parse(s.d.Sock())
|
|
||||||
|
|
||||||
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
client := httputil.NewClientConn(conn, nil)
|
|
||||||
req, err := http.NewRequest("GET", "/version", nil)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(resp.StatusCode, checker.Equals, http.StatusForbidden)
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = false
|
|
||||||
s.ctrl.resRes.Msg = unauthorizedMessage
|
|
||||||
|
|
||||||
// Ensure command is blocked
|
|
||||||
res, err := s.d.Cmd("ps")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
|
|
||||||
c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
|
|
||||||
|
|
||||||
// Ensure unauthorized message appears in response
|
|
||||||
c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestAuthZPluginAllowEventStream verifies event stream propagates correctly after request pass through by the authorization plugin
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginAllowEventStream(c *check.C) {
|
|
||||||
testRequires(c, DaemonIsLinux)
|
|
||||||
|
|
||||||
// start the daemon and load busybox to avoid pulling busybox from Docker Hub
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
startTime := strconv.FormatInt(daemonTime(c).Unix(), 10)
|
|
||||||
// Add another command to to enable event pipelining
|
|
||||||
eventsCmd := exec.Command(dockerBinary, "--host", s.d.Sock(), "events", "--since", startTime)
|
|
||||||
stdout, err := eventsCmd.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
}
|
|
||||||
|
|
||||||
observer := eventObserver{
|
|
||||||
buffer: new(bytes.Buffer),
|
|
||||||
command: eventsCmd,
|
|
||||||
scanner: bufio.NewScanner(stdout),
|
|
||||||
startTime: startTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = observer.Start()
|
|
||||||
c.Assert(err, checker.IsNil)
|
|
||||||
defer observer.Stop()
|
|
||||||
|
|
||||||
// Create a container and wait for the creation events
|
|
||||||
out, err := s.d.Cmd("run", "-d", "busybox", "top")
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
containerID := strings.TrimSpace(out)
|
|
||||||
c.Assert(s.d.WaitRun(containerID), checker.IsNil)
|
|
||||||
|
|
||||||
events := map[string]chan bool{
|
|
||||||
"create": make(chan bool, 1),
|
|
||||||
"start": make(chan bool, 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
matcher := matchEventLine(containerID, "container", events)
|
|
||||||
processor := processEventMatch(events)
|
|
||||||
go observer.Match(matcher, processor)
|
|
||||||
|
|
||||||
// Ensure all events are received
|
|
||||||
for event, eventChannel := range events {
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
// Fail the test
|
|
||||||
observer.CheckEventError(c, containerID, event, matcher)
|
|
||||||
c.FailNow()
|
|
||||||
case <-eventChannel:
|
|
||||||
// Ignore, event received
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure both events and container endpoints are passed to the authorization plugin
|
|
||||||
assertURIRecorded(c, s.ctrl.requestsURIs, "/events")
|
|
||||||
assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
|
|
||||||
assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", containerID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Err = errorMessage
|
|
||||||
|
|
||||||
// Ensure command is blocked
|
|
||||||
res, err := s.d.Cmd("ps")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
|
|
||||||
c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Err = errorMessage
|
|
||||||
|
|
||||||
// Ensure command is blocked
|
|
||||||
res, err := s.d.Cmd("ps")
|
|
||||||
c.Assert(err, check.NotNil)
|
|
||||||
|
|
||||||
c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginEnsureNoDuplicatePluginRegistration(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
|
|
||||||
out, err := s.d.Cmd("ps")
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
|
|
||||||
// assert plugin is only called once..
|
|
||||||
c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
|
|
||||||
c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginEnsureLoadImportWorking(c *check.C) {
|
|
||||||
s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "test-authz-load-import")
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
defer os.RemoveAll(tmp)
|
|
||||||
|
|
||||||
savedImagePath := filepath.Join(tmp, "save.tar")
|
|
||||||
|
|
||||||
out, err := s.d.Cmd("save", "-o", savedImagePath, "busybox")
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
out, err = s.d.Cmd("load", "--input", savedImagePath)
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
|
|
||||||
exportedImagePath := filepath.Join(tmp, "export.tar")
|
|
||||||
|
|
||||||
out, err = s.d.Cmd("run", "-d", "--name", "testexport", "busybox")
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
out, err = s.d.Cmd("export", "-o", exportedImagePath, "testexport")
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
out, err = s.d.Cmd("import", exportedImagePath)
|
|
||||||
c.Assert(err, check.IsNil, check.Commentf(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *DockerAuthzSuite) TestAuthZPluginHeader(c *check.C) {
|
|
||||||
s.d.Start(c, "--debug", "--authorization-plugin="+testAuthZPlugin)
|
|
||||||
s.ctrl.reqRes.Allow = true
|
|
||||||
s.ctrl.resRes.Allow = true
|
|
||||||
s.d.LoadBusybox(c)
|
|
||||||
|
|
||||||
daemonURL, err := url.Parse(s.d.Sock())
|
|
||||||
|
|
||||||
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
client := httputil.NewClientConn(conn, nil)
|
|
||||||
req, err := http.NewRequest("GET", "/version", nil)
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
c.Assert(resp.Header["Content-Type"][0], checker.Equals, "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin
|
|
||||||
func assertURIRecorded(c *check.C, uris []string, uri string) {
|
|
||||||
var found bool
|
|
||||||
for _, u := range uris {
|
|
||||||
if strings.Contains(u, uri) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
|
|
||||||
VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
|
|
||||||
A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
|
|
||||||
Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
|
|
||||||
QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
|
|
||||||
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
|
|
||||||
MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
|
|
||||||
VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
|
|
||||||
EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
|
|
||||||
0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
|
|
||||||
AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
|
|
||||||
sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
|
|
||||||
HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
|
|
||||||
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
|
|
||||||
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
|
|
||||||
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
|
|
||||||
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
|
|
||||||
hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
|
|
||||||
zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
|
|
||||||
ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
|
|
||||||
Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../integration/testdata/https/ca.pem
|
|
@ -1,73 +0,0 @@
|
||||||
Certificate:
|
|
||||||
Data:
|
|
||||||
Version: 3 (0x2)
|
|
||||||
Serial Number: 3 (0x3)
|
|
||||||
Signature Algorithm: sha1WithRSAEncryption
|
|
||||||
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
|
|
||||||
Validity
|
|
||||||
Not Before: Dec 4 14:17:54 2013 GMT
|
|
||||||
Not After : Dec 2 14:17:54 2023 GMT
|
|
||||||
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
|
|
||||||
Subject Public Key Info:
|
|
||||||
Public Key Algorithm: rsaEncryption
|
|
||||||
Public-Key: (1024 bit)
|
|
||||||
Modulus:
|
|
||||||
00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
|
|
||||||
34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
|
|
||||||
f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
|
|
||||||
b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
|
|
||||||
81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
|
|
||||||
6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
|
|
||||||
aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
|
|
||||||
65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
|
|
||||||
7e:4e:78:7d:0a:9e:8f:42:43
|
|
||||||
Exponent: 65537 (0x10001)
|
|
||||||
X509v3 extensions:
|
|
||||||
X509v3 Basic Constraints:
|
|
||||||
CA:FALSE
|
|
||||||
Netscape Comment:
|
|
||||||
Easy-RSA Generated Certificate
|
|
||||||
X509v3 Subject Key Identifier:
|
|
||||||
DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
|
|
||||||
X509v3 Authority Key Identifier:
|
|
||||||
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
|
|
||||||
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
|
|
||||||
serial:FD:AB:EC:6A:84:27:04:A7
|
|
||||||
|
|
||||||
X509v3 Extended Key Usage:
|
|
||||||
TLS Web Client Authentication
|
|
||||||
X509v3 Key Usage:
|
|
||||||
Digital Signature
|
|
||||||
Signature Algorithm: sha1WithRSAEncryption
|
|
||||||
1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
|
|
||||||
12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
|
|
||||||
1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
|
|
||||||
af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
|
|
||||||
84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
|
|
||||||
f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
|
|
||||||
56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
|
|
||||||
4a:c4
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
|
|
||||||
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
|
|
||||||
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
|
|
||||||
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
|
|
||||||
bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
|
|
||||||
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
|
|
||||||
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
|
|
||||||
ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
|
|
||||||
LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
|
|
||||||
peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
|
|
||||||
Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
|
|
||||||
cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
|
|
||||||
YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
|
|
||||||
HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
|
|
||||||
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
|
|
||||||
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
|
|
||||||
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
|
|
||||||
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
|
|
||||||
hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
|
|
||||||
AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
|
|
||||||
kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
|
|
||||||
aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../integration/testdata/https/client-cert.pem
|
|
@ -1,16 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
|
|
||||||
9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
|
|
||||||
gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
|
|
||||||
93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
|
|
||||||
xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
|
|
||||||
FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
|
|
||||||
OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
|
|
||||||
4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
|
|
||||||
SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
|
|
||||||
iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
|
|
||||||
v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
|
|
||||||
qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
|
|
||||||
qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
|
|
||||||
ksDFuNxAzbhl
|
|
||||||
-----END PRIVATE KEY-----
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../integration/testdata/https/client-key.pem
|
|
@ -1,76 +0,0 @@
|
||||||
Certificate:
|
|
||||||
Data:
|
|
||||||
Version: 3 (0x2)
|
|
||||||
Serial Number: 4 (0x4)
|
|
||||||
Signature Algorithm: sha1WithRSAEncryption
|
|
||||||
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
|
|
||||||
Validity
|
|
||||||
Not Before: Dec 4 15:01:20 2013 GMT
|
|
||||||
Not After : Dec 2 15:01:20 2023 GMT
|
|
||||||
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
|
|
||||||
Subject Public Key Info:
|
|
||||||
Public Key Algorithm: rsaEncryption
|
|
||||||
Public-Key: (1024 bit)
|
|
||||||
Modulus:
|
|
||||||
00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
|
|
||||||
e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
|
|
||||||
67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
|
|
||||||
3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
|
|
||||||
e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
|
|
||||||
3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
|
|
||||||
49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
|
|
||||||
c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
|
|
||||||
a8:05:32:1e:f9:95:09:14:75
|
|
||||||
Exponent: 65537 (0x10001)
|
|
||||||
X509v3 extensions:
|
|
||||||
X509v3 Basic Constraints:
|
|
||||||
CA:FALSE
|
|
||||||
Netscape Cert Type:
|
|
||||||
SSL Server
|
|
||||||
Netscape Comment:
|
|
||||||
Easy-RSA Generated Server Certificate
|
|
||||||
X509v3 Subject Key Identifier:
|
|
||||||
14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
|
|
||||||
X509v3 Authority Key Identifier:
|
|
||||||
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
|
|
||||||
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
|
|
||||||
serial:FD:AB:EC:6A:84:27:04:A7
|
|
||||||
|
|
||||||
X509v3 Extended Key Usage:
|
|
||||||
TLS Web Server Authentication
|
|
||||||
X509v3 Key Usage:
|
|
||||||
Digital Signature, Key Encipherment
|
|
||||||
Signature Algorithm: sha1WithRSAEncryption
|
|
||||||
40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
|
|
||||||
ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
|
|
||||||
23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
|
|
||||||
df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
|
|
||||||
c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
|
|
||||||
9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
|
|
||||||
12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
|
|
||||||
15:42
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
|
|
||||||
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
|
|
||||||
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
|
|
||||||
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
|
|
||||||
bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
|
|
||||||
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
|
|
||||||
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
|
|
||||||
MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
|
|
||||||
aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
|
|
||||||
LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
|
|
||||||
cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
|
|
||||||
M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
|
|
||||||
+EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
|
|
||||||
cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
|
|
||||||
gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
|
|
||||||
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
|
|
||||||
EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
|
|
||||||
EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
|
|
||||||
aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
|
|
||||||
BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
|
|
||||||
zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
|
|
||||||
mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
|
|
||||||
dDBV9m4gmmweCbQMFUI=
|
|
||||||
-----END CERTIFICATE-----
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../integration/testdata/https/server-cert.pem
|
|
@ -1,16 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
|
|
||||||
0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
|
|
||||||
4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
|
|
||||||
lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
|
|
||||||
wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
|
|
||||||
wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
|
|
||||||
IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
|
|
||||||
4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
|
|
||||||
WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
|
|
||||||
+0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
|
|
||||||
HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
|
|
||||||
+tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
|
|
||||||
BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
|
|
||||||
5nCwDu5ZTP+khltg
|
|
||||||
-----END PRIVATE KEY-----
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../integration/testdata/https/server-key.pem
|
|
@ -0,0 +1,128 @@
|
||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateVMemLabel creates a container with the supplied parameters
|
||||||
|
func CreateVMemLabel(client client.APIClient, v []string, memoryMB int, labels map[string]string, img string, cmd []string) (string, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
config := container.Config{
|
||||||
|
Cmd: cmd,
|
||||||
|
Image: img,
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
hostConfig := container.HostConfig{
|
||||||
|
Binds: v,
|
||||||
|
Resources: container.Resources{
|
||||||
|
Memory: int64(memoryMB * 1024 * 1024),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
networkingConfig := networktypes.NetworkingConfig{}
|
||||||
|
name := ""
|
||||||
|
response, err := client.ContainerCreate(ctx, &config, &hostConfig, &networkingConfig, name)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs the command provided in the container image named
|
||||||
|
func Run(client client.APIClient, img string, cmd []string) (string, error) {
|
||||||
|
return RunV(client, []string{}, img, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunV runs the command provided in the container image named with
|
||||||
|
// the equivalent of -v bind mounts
|
||||||
|
func RunV(client client.APIClient, v []string, img string, cmd []string) (string, error) {
|
||||||
|
return RunVMem(client, v, 0, img, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVMem runs the command provided in the container image named with
|
||||||
|
// the equivalent of -v bind mounts and a specified memory limit
|
||||||
|
func RunVMem(client client.APIClient, v []string, memoryMB int, img string, cmd []string) (string, error) {
|
||||||
|
return RunVMemLabel(client, v, memoryMB, map[string]string{}, img, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVLabel runs the command provided in the container image named
|
||||||
|
// with the equivalent of -v bind mounts and the specified labels
|
||||||
|
func RunVLabel(client client.APIClient, v []string, labels map[string]string, img string, cmd []string) (string, error) {
|
||||||
|
return RunVMemLabel(client, v, 0, labels, img, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunVMemLabel runs the command provided in the container image named
|
||||||
|
// with the equivalent of -v bind mounts, a specified memory limit,
|
||||||
|
// and the specified labels
|
||||||
|
func RunVMemLabel(client client.APIClient, v []string, memoryMB int, labels map[string]string, img string, cmd []string) (string, error) {
|
||||||
|
containerID, err := CreateVMemLabel(client, v, memoryMB, labels, img, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := client.ContainerStart(ctx, containerID, types.ContainerStartOptions{}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return containerID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait waits until the named container has exited
|
||||||
|
func Wait(client client.APIClient, container string) (int64, error) {
|
||||||
|
resultC, errC := client.ContainerWait(context.Background(), container, "")
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-resultC:
|
||||||
|
return result.StatusCode, nil
|
||||||
|
case err := <-errC:
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the named container
|
||||||
|
func Stop(client client.APIClient, container string) error {
|
||||||
|
timeout := time.Duration(10) * time.Second
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.ContainerStop(ctx, container, &timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the named container
|
||||||
|
func Start(client client.APIClient, container string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.ContainerStart(ctx, container, types.ContainerStartOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill kills the named container with SIGKILL
|
||||||
|
func Kill(client client.APIClient, container string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.ContainerKill(ctx, container, "KILL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export exports a container's file system as a tarball
|
||||||
|
func Export(client client.APIClient, path, name string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
responseReader, err := client.ContainerExport(ctx, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseReader.Close()
|
||||||
|
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, responseReader)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Save saves an image to a tarball names by path
|
||||||
|
func Save(client client.APIClient, path, image string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
responseReader, err := client.ImageSave(ctx, []string{image})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseReader.Close()
|
||||||
|
file, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
_, err = io.Copy(file, responseReader)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads an image from a tarball named by path
|
||||||
|
func Load(client client.APIClient, path string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
quiet := true
|
||||||
|
ctx := context.Background()
|
||||||
|
response, err := client.ImageLoad(ctx, file, quiet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import imports the contents of a tarball named by path
|
||||||
|
func Import(client client.APIClient, path string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
options := types.ImageImportOptions{}
|
||||||
|
ref := ""
|
||||||
|
source := types.ImageImportSource{
|
||||||
|
Source: file,
|
||||||
|
SourceName: "-",
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
responseReader, err := client.ImageImport(ctx, source, ref, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseReader.Close()
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InstallGrantAllPermissions installs the plugin named and grants it
|
||||||
|
// all permissions it may require
|
||||||
|
func InstallGrantAllPermissions(client client.APIClient, name string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
options := types.PluginInstallOptions{
|
||||||
|
RemoteRef: name,
|
||||||
|
AcceptAllPermissions: true,
|
||||||
|
}
|
||||||
|
responseReader, err := client.PluginInstall(ctx, "", options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer responseReader.Close()
|
||||||
|
// we have to read the response out here because the client API
|
||||||
|
// actually starts a goroutine which we can only be sure has
|
||||||
|
// completed when we get EOF from reading responseBody
|
||||||
|
_, err = ioutil.ReadAll(responseReader)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable enables the named plugin
|
||||||
|
func Enable(client client.APIClient, name string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
options := types.PluginEnableOptions{}
|
||||||
|
return client.PluginEnable(ctx, name, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable disables the named plugin
|
||||||
|
func Disable(client client.APIClient, name string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
options := types.PluginDisableOptions{}
|
||||||
|
return client.PluginDisable(ctx, name, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rm removes the named plugin
|
||||||
|
func Rm(client client.APIClient, name string) error {
|
||||||
|
return remove(client, name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RmF forces the removal of the named plugin
|
||||||
|
func RmF(client client.APIClient, name string) error {
|
||||||
|
return remove(client, name, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove(client client.APIClient, name string, force bool) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
options := types.PluginRemoveOptions{Force: force}
|
||||||
|
return client.PluginRemove(ctx, name, options)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/events"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/internal/test/environment"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time provides the current time on the daemon host
|
||||||
|
func Time(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
|
||||||
|
if testEnv.IsLocalDaemon() {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
info, err := client.Info(ctx)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
|
||||||
|
require.Nil(t, err, "invalid time format in GET /info response")
|
||||||
|
return dt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version provides the version of the daemon
|
||||||
|
func Version(client client.APIClient) (types.Version, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.ServerVersion(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventsSince returns event and error streams since a provided time
|
||||||
|
func EventsSince(client client.APIClient, since string) (<-chan events.Message, <-chan error, func()) {
|
||||||
|
eventOptions := types.EventsOptions{
|
||||||
|
Since: since,
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
events, errs := client.Events(ctx, eventOptions)
|
||||||
|
|
||||||
|
return events, errs, cancel
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package volume
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
volumetypes "github.com/docker/docker/api/types/volume"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create creates a volume using the named driver with the specified options
|
||||||
|
func Create(client client.APIClient, driver string, opts map[string]string) (string, error) {
|
||||||
|
volReq := volumetypes.VolumesCreateBody{
|
||||||
|
Driver: driver,
|
||||||
|
DriverOpts: opts,
|
||||||
|
Name: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
vol, err := client.VolumeCreate(ctx, volReq)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return vol.Name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rm removes the volume named
|
||||||
|
func Rm(client client.APIClient, name string) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.VolumeRemove(ctx, name, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ls lists the volumes available
|
||||||
|
func Ls(client client.APIClient) ([]string, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
volumes, err := client.VolumeList(ctx, filters.Args{})
|
||||||
|
if err != nil {
|
||||||
|
return []string{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := []string{}
|
||||||
|
for _, volume := range volumes.Volumes {
|
||||||
|
names = append(names, volume.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune removes all volumes not used by at least one container
|
||||||
|
func Prune(client client.APIClient) (types.VolumesPruneReport, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
return client.VolumesPrune(ctx, filters.Args{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect retrieves detailed information about the named volume
|
||||||
|
func Inspect(client client.APIClient, name string) (types.Volume, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
return client.VolumeInspect(ctx, name)
|
||||||
|
}
|
|
@ -0,0 +1,370 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package authz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
eventtypes "github.com/docker/docker/api/types/events"
|
||||||
|
"github.com/docker/docker/integration/internal/api/container"
|
||||||
|
"github.com/docker/docker/integration/internal/api/image"
|
||||||
|
"github.com/docker/docker/integration/internal/api/system"
|
||||||
|
"github.com/docker/docker/integration/util/request"
|
||||||
|
"github.com/docker/docker/pkg/authorization"
|
||||||
|
"github.com/gotestyourself/gotestyourself/skip"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
testAuthZPlugin = "authzplugin"
|
||||||
|
unauthorizedMessage = "User unauthorized authz plugin"
|
||||||
|
errorMessage = "something went wrong..."
|
||||||
|
serverVersionAPI = "/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alwaysAllowed = []string{"/_ping", "/info"}
|
||||||
|
ctrl *authorizationController
|
||||||
|
)
|
||||||
|
|
||||||
|
type authorizationController struct {
|
||||||
|
reqRes authorization.Response // reqRes holds the plugin response to the initial client request
|
||||||
|
resRes authorization.Response // resRes holds the plugin response to the daemon response
|
||||||
|
versionReqCount int // versionReqCount counts the number of requests to the server version API endpoint
|
||||||
|
versionResCount int // versionResCount counts the number of responses from the server version API endpoint
|
||||||
|
requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller
|
||||||
|
reqUser string
|
||||||
|
resUser string
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTestV1(t *testing.T) func() {
|
||||||
|
ctrl = &authorizationController{}
|
||||||
|
teardown := setupTest(t)
|
||||||
|
|
||||||
|
err := os.MkdirAll("/etc/docker/plugins", 0755)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
|
||||||
|
err = ioutil.WriteFile(fileName, []byte(server.URL), 0644)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
err := os.RemoveAll("/etc/docker/plugins")
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
ctrl = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for always allowed endpoints to not inhibit test framework functions
|
||||||
|
func isAllowed(reqURI string) bool {
|
||||||
|
for _, endpoint := range alwaysAllowed {
|
||||||
|
if strings.HasSuffix(reqURI, endpoint) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginAllowRequest(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Ensure command successful
|
||||||
|
id, err := container.Run(client, "busybox", []string{"top"})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
|
||||||
|
assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
|
||||||
|
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, 1, ctrl.versionReqCount)
|
||||||
|
require.Equal(t, 1, ctrl.versionResCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginTLS(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
const (
|
||||||
|
testDaemonHTTPSAddr = "tcp://localhost:4271"
|
||||||
|
cacertPath = "../../testdata/https/ca.pem"
|
||||||
|
serverCertPath = "../../testdata/https/server-cert.pem"
|
||||||
|
serverKeyPath = "../../testdata/https/server-key.pem"
|
||||||
|
clientCertPath = "../../testdata/https/client-cert.pem"
|
||||||
|
clientKeyPath = "../../testdata/https/client-key.pem"
|
||||||
|
)
|
||||||
|
|
||||||
|
d.Start(t,
|
||||||
|
"--authorization-plugin="+testAuthZPlugin,
|
||||||
|
"--tlsverify",
|
||||||
|
"--tlscacert", cacertPath,
|
||||||
|
"--tlscert", serverCertPath,
|
||||||
|
"--tlskey", serverKeyPath,
|
||||||
|
"-H", testDaemonHTTPSAddr)
|
||||||
|
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
|
||||||
|
client, err := request.NewTLSAPIClient(t, testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "client", ctrl.reqUser)
|
||||||
|
require.Equal(t, "client", ctrl.resUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginDenyRequest(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
ctrl.reqRes.Allow = false
|
||||||
|
ctrl.reqRes.Msg = unauthorizedMessage
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Ensure command is blocked
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, 1, ctrl.versionReqCount)
|
||||||
|
require.Equal(t, 0, ctrl.versionResCount)
|
||||||
|
|
||||||
|
// Ensure unauthorized message appears in response
|
||||||
|
require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAuthZPluginAPIDenyResponse validates that when authorization
|
||||||
|
// plugin deny the request, the status code is forbidden
|
||||||
|
func TestAuthZPluginAPIDenyResponse(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
ctrl.reqRes.Allow = false
|
||||||
|
ctrl.resRes.Msg = unauthorizedMessage
|
||||||
|
|
||||||
|
daemonURL, err := url.Parse(d.Sock())
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
|
||||||
|
require.Nil(t, err)
|
||||||
|
client := httputil.NewClientConn(conn, nil)
|
||||||
|
req, err := http.NewRequest("GET", "/version", nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginDenyResponse(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = false
|
||||||
|
ctrl.resRes.Msg = unauthorizedMessage
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Ensure command is blocked
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, 1, ctrl.versionReqCount)
|
||||||
|
require.Equal(t, 1, ctrl.versionResCount)
|
||||||
|
|
||||||
|
// Ensure unauthorized message appears in response
|
||||||
|
require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAuthZPluginAllowEventStream verifies event stream propagates
|
||||||
|
// correctly after request pass through by the authorization plugin
|
||||||
|
func TestAuthZPluginAllowEventStream(t *testing.T) {
|
||||||
|
skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
|
||||||
|
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
startTime := strconv.FormatInt(system.Time(t, client, testEnv).Unix(), 10)
|
||||||
|
events, errs, cancel := system.EventsSince(client, startTime)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Create a container and wait for the creation events
|
||||||
|
id, err := container.Run(client, "busybox", []string{"top"})
|
||||||
|
require.Nil(t, err)
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
c, err := client.ContainerInspect(context.Background(), id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
if c.State.Running {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i == 99 {
|
||||||
|
t.Fatal("Container didn't run within 10s")
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
created := false
|
||||||
|
started := false
|
||||||
|
for !created && !started {
|
||||||
|
select {
|
||||||
|
case event := <-events:
|
||||||
|
if event.Type == eventtypes.ContainerEventType && event.Actor.ID == id {
|
||||||
|
if event.Action == "create" {
|
||||||
|
created = true
|
||||||
|
}
|
||||||
|
if event.Action == "start" {
|
||||||
|
started = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case err := <-errs:
|
||||||
|
if err == io.EOF {
|
||||||
|
t.Fatal("premature end of event stream")
|
||||||
|
}
|
||||||
|
require.Nil(t, err)
|
||||||
|
case <-time.After(30 * time.Second):
|
||||||
|
// Fail the test
|
||||||
|
t.Fatal("event stream timeout")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure both events and container endpoints are passed to the
|
||||||
|
// authorization plugin
|
||||||
|
assertURIRecorded(t, ctrl.requestsURIs, "/events")
|
||||||
|
assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
|
||||||
|
assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginErrorResponse(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Err = errorMessage
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Ensure command is blocked
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginErrorRequest(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
ctrl.reqRes.Err = errorMessage
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Ensure command is blocked
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = system.Version(client)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// assert plugin is only called once..
|
||||||
|
require.Equal(t, 1, ctrl.versionReqCount)
|
||||||
|
require.Equal(t, 1, ctrl.versionResCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
tmp, err := ioutil.TempDir("", "test-authz-load-import")
|
||||||
|
require.Nil(t, err)
|
||||||
|
defer os.RemoveAll(tmp)
|
||||||
|
|
||||||
|
savedImagePath := filepath.Join(tmp, "save.tar")
|
||||||
|
|
||||||
|
err = image.Save(client, savedImagePath, "busybox")
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = image.Load(client, savedImagePath)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
exportedImagePath := filepath.Join(tmp, "export.tar")
|
||||||
|
|
||||||
|
id, err := container.Run(client, "busybox", []string{})
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = container.Export(client, exportedImagePath, id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
err = image.Import(client, exportedImagePath)
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginHeader(t *testing.T) {
|
||||||
|
defer setupTestV1(t)()
|
||||||
|
ctrl.reqRes.Allow = true
|
||||||
|
ctrl.resRes.Allow = true
|
||||||
|
d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin)
|
||||||
|
|
||||||
|
daemonURL, err := url.Parse(d.Sock())
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
|
||||||
|
require.Nil(t, err)
|
||||||
|
client := httputil.NewClientConn(conn, nil)
|
||||||
|
req, err := http.NewRequest("GET", "/version", nil)
|
||||||
|
require.Nil(t, err)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.Equal(t, "application/json", resp.Header["Content-Type"][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertURIRecorded verifies that the given URI was sent and recorded
|
||||||
|
// in the authz plugin
|
||||||
|
func assertURIRecorded(t *testing.T, uris []string, uri string) {
|
||||||
|
var found bool
|
||||||
|
for _, u := range uris {
|
||||||
|
if strings.Contains(u, uri) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package authz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/integration/internal/api/container"
|
||||||
|
"github.com/docker/docker/integration/internal/api/plugin"
|
||||||
|
"github.com/docker/docker/integration/internal/api/volume"
|
||||||
|
"github.com/docker/docker/integration/util/requirement"
|
||||||
|
"github.com/gotestyourself/gotestyourself/skip"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
authzPluginName = "riyaz/authz-no-volume-plugin"
|
||||||
|
authzPluginTag = "latest"
|
||||||
|
authzPluginNameWithTag = authzPluginName + ":" + authzPluginTag
|
||||||
|
authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
|
||||||
|
nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestV2(t *testing.T) func() {
|
||||||
|
skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
|
||||||
|
requirement.HasHubConnectivity(t)
|
||||||
|
|
||||||
|
teardown := setupTest(t)
|
||||||
|
|
||||||
|
d.Start(t)
|
||||||
|
|
||||||
|
return teardown
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginV2AllowNonVolumeRequest(t *testing.T) {
|
||||||
|
skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
|
||||||
|
defer setupTestV2(t)()
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Install authz plugin
|
||||||
|
err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
|
||||||
|
require.Nil(t, err)
|
||||||
|
// start the daemon with the plugin and load busybox, --net=none build fails otherwise
|
||||||
|
// because it needs to pull busybox
|
||||||
|
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
|
||||||
|
d.LoadBusybox(t)
|
||||||
|
|
||||||
|
// Ensure docker run command and accompanying docker ps are successful
|
||||||
|
id, err := container.Run(client, "busybox", []string{"top"})
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
_, err = client.ContainerInspect(context.Background(), id)
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginV2Disable(t *testing.T) {
|
||||||
|
skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
|
||||||
|
defer setupTestV2(t)()
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Install authz plugin
|
||||||
|
err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
|
||||||
|
d.LoadBusybox(t)
|
||||||
|
|
||||||
|
_, err = volume.Create(client, "local", map[string]string{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
|
||||||
|
// disable the plugin
|
||||||
|
err = plugin.Disable(client, authzPluginNameWithTag)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// now test to see if the docker api works.
|
||||||
|
_, err = volume.Create(client, "local", map[string]string{})
|
||||||
|
require.Nil(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
|
||||||
|
skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
|
||||||
|
defer setupTestV2(t)()
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Install authz plugin
|
||||||
|
err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// restart the daemon with the plugin
|
||||||
|
d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
|
||||||
|
|
||||||
|
_, err = volume.Create(client, "local", map[string]string{})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
|
||||||
|
_, err = volume.Ls(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
|
||||||
|
// The plugin will block the command before it can determine the volume does not exist
|
||||||
|
err = volume.Rm(client, "test")
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
|
||||||
|
_, err = volume.Inspect(client, "test")
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
|
||||||
|
_, err = volume.Prune(client)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginV2BadManifestFailsDaemonStart(t *testing.T) {
|
||||||
|
skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
|
||||||
|
defer setupTestV2(t)()
|
||||||
|
|
||||||
|
client, err := d.NewClient()
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Install authz plugin with bad manifest
|
||||||
|
err = plugin.InstallGrantAllPermissions(client, authzPluginBadManifestName)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// start the daemon with the plugin, it will error
|
||||||
|
err = d.RestartWithError("--authorization-plugin=" + authzPluginBadManifestName)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
// restarting the daemon without requiring the plugin will succeed
|
||||||
|
d.Start(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthZPluginV2NonexistentFailsDaemonStart(t *testing.T) {
|
||||||
|
defer setupTestV2(t)()
|
||||||
|
|
||||||
|
// start the daemon with a non-existent authz plugin, it will error
|
||||||
|
err := d.RestartWithError("--authorization-plugin=" + nonexistentAuthzPluginName)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
|
||||||
|
// restarting the daemon without requiring the plugin will succeed
|
||||||
|
d.Start(t)
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package authz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/integration-cli/daemon"
|
||||||
|
"github.com/docker/docker/internal/test/environment"
|
||||||
|
"github.com/docker/docker/pkg/authorization"
|
||||||
|
"github.com/docker/docker/pkg/plugins"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testEnv *environment.Execution
|
||||||
|
d *daemon.Daemon
|
||||||
|
server *httptest.Server
|
||||||
|
)
|
||||||
|
|
||||||
|
const dockerdBinary = "dockerd"
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
var err error
|
||||||
|
testEnv, err = environment.New()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
testEnv.Print()
|
||||||
|
setupSuite()
|
||||||
|
exitCode := m.Run()
|
||||||
|
teardownSuite()
|
||||||
|
|
||||||
|
os.Exit(exitCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupTest(t *testing.T) func() {
|
||||||
|
environment.ProtectAll(t, testEnv)
|
||||||
|
|
||||||
|
d = daemon.New(t, "", dockerdBinary, daemon.Config{
|
||||||
|
Experimental: testEnv.DaemonInfo.ExperimentalBuild,
|
||||||
|
})
|
||||||
|
|
||||||
|
return func() {
|
||||||
|
if d != nil {
|
||||||
|
d.Stop(t)
|
||||||
|
}
|
||||||
|
testEnv.Clean(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupSuite() {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
server = httptest.NewServer(mux)
|
||||||
|
|
||||||
|
mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
|
||||||
|
if err != nil {
|
||||||
|
panic("could not marshal json for /Plugin.Activate: " + err.Error())
|
||||||
|
}
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not read body for /AuthZPlugin.AuthZReq: " + err.Error())
|
||||||
|
}
|
||||||
|
authReq := authorization.Request{}
|
||||||
|
err = json.Unmarshal(body, &authReq)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not unmarshal json for /AuthZPlugin.AuthZReq: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertBody(authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
|
||||||
|
assertAuthHeaders(authReq.RequestHeaders)
|
||||||
|
|
||||||
|
// Count only server version api
|
||||||
|
if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
|
||||||
|
ctrl.versionReqCount++
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.requestsURIs = append(ctrl.requestsURIs, authReq.RequestURI)
|
||||||
|
|
||||||
|
reqRes := ctrl.reqRes
|
||||||
|
if isAllowed(authReq.RequestURI) {
|
||||||
|
reqRes = authorization.Response{Allow: true}
|
||||||
|
}
|
||||||
|
if reqRes.Err != "" {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(reqRes)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not marshal json for /AuthZPlugin.AuthZReq: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrl.reqUser = authReq.User
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not read body for /AuthZPlugin.AuthZRes: " + err.Error())
|
||||||
|
}
|
||||||
|
authReq := authorization.Request{}
|
||||||
|
err = json.Unmarshal(body, &authReq)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not unmarshal json for /AuthZPlugin.AuthZRes: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertBody(authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
|
||||||
|
assertAuthHeaders(authReq.ResponseHeaders)
|
||||||
|
|
||||||
|
// Count only server version api
|
||||||
|
if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
|
||||||
|
ctrl.versionResCount++
|
||||||
|
}
|
||||||
|
resRes := ctrl.resRes
|
||||||
|
if isAllowed(authReq.RequestURI) {
|
||||||
|
resRes = authorization.Response{Allow: true}
|
||||||
|
}
|
||||||
|
if resRes.Err != "" {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
b, err := json.Marshal(resRes)
|
||||||
|
if err != nil {
|
||||||
|
panic("could not marshal json for /AuthZPlugin.AuthZRes: " + err.Error())
|
||||||
|
}
|
||||||
|
ctrl.resUser = authReq.User
|
||||||
|
w.Write(b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func teardownSuite() {
|
||||||
|
if server == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
server.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertAuthHeaders validates authentication headers are removed
|
||||||
|
func assertAuthHeaders(headers map[string]string) error {
|
||||||
|
for k := range headers {
|
||||||
|
if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
|
||||||
|
panic(fmt.Sprintf("Found authentication headers in request '%v'", headers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertBody asserts that body is removed for non text/json requests
|
||||||
|
func assertBody(requestURI string, headers map[string]string, body []byte) {
|
||||||
|
if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
|
||||||
|
panic("Body included for authentication endpoint " + string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range headers {
|
||||||
|
if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(body) > 0 {
|
||||||
|
panic(fmt.Sprintf("Body included while it should not (Headers: '%v')", headers))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
|
||||||
|
VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
|
||||||
|
A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
|
||||||
|
Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
|
||||||
|
QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
|
||||||
|
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
|
||||||
|
MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
|
||||||
|
VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
|
||||||
|
EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
|
||||||
|
0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
|
||||||
|
AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
|
||||||
|
sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
|
||||||
|
HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
|
||||||
|
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
|
||||||
|
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
|
||||||
|
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
|
||||||
|
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
|
||||||
|
hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
|
||||||
|
zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
|
||||||
|
ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
|
||||||
|
Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,73 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 3 (0x3)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
|
||||||
|
Validity
|
||||||
|
Not Before: Dec 4 14:17:54 2013 GMT
|
||||||
|
Not After : Dec 2 14:17:54 2023 GMT
|
||||||
|
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (1024 bit)
|
||||||
|
Modulus:
|
||||||
|
00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
|
||||||
|
34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
|
||||||
|
f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
|
||||||
|
b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
|
||||||
|
81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
|
||||||
|
6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
|
||||||
|
aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
|
||||||
|
65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
|
||||||
|
7e:4e:78:7d:0a:9e:8f:42:43
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
Netscape Comment:
|
||||||
|
Easy-RSA Generated Certificate
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
|
||||||
|
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
|
||||||
|
serial:FD:AB:EC:6A:84:27:04:A7
|
||||||
|
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Client Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
|
||||||
|
12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
|
||||||
|
1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
|
||||||
|
af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
|
||||||
|
84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
|
||||||
|
f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
|
||||||
|
56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
|
||||||
|
4a:c4
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
|
||||||
|
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
|
||||||
|
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
|
||||||
|
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
|
||||||
|
bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
|
||||||
|
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
|
||||||
|
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
|
||||||
|
ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
|
||||||
|
LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
|
||||||
|
peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
|
||||||
|
Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
|
||||||
|
cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
|
||||||
|
YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
|
||||||
|
HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
|
||||||
|
q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
|
||||||
|
QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
|
||||||
|
ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
|
||||||
|
Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
|
||||||
|
hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
|
||||||
|
AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
|
||||||
|
kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
|
||||||
|
aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
|
||||||
|
9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
|
||||||
|
gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
|
||||||
|
93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
|
||||||
|
xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
|
||||||
|
FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
|
||||||
|
OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
|
||||||
|
4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
|
||||||
|
SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
|
||||||
|
iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
|
||||||
|
v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
|
||||||
|
qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
|
||||||
|
qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
|
||||||
|
ksDFuNxAzbhl
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -0,0 +1,76 @@
|
||||||
|
Certificate:
|
||||||
|
Data:
|
||||||
|
Version: 3 (0x2)
|
||||||
|
Serial Number: 4 (0x4)
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
|
||||||
|
Validity
|
||||||
|
Not Before: Dec 4 15:01:20 2013 GMT
|
||||||
|
Not After : Dec 2 15:01:20 2023 GMT
|
||||||
|
Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
|
||||||
|
Subject Public Key Info:
|
||||||
|
Public Key Algorithm: rsaEncryption
|
||||||
|
Public-Key: (1024 bit)
|
||||||
|
Modulus:
|
||||||
|
00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
|
||||||
|
e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
|
||||||
|
67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
|
||||||
|
3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
|
||||||
|
e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
|
||||||
|
3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
|
||||||
|
49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
|
||||||
|
c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
|
||||||
|
a8:05:32:1e:f9:95:09:14:75
|
||||||
|
Exponent: 65537 (0x10001)
|
||||||
|
X509v3 extensions:
|
||||||
|
X509v3 Basic Constraints:
|
||||||
|
CA:FALSE
|
||||||
|
Netscape Cert Type:
|
||||||
|
SSL Server
|
||||||
|
Netscape Comment:
|
||||||
|
Easy-RSA Generated Server Certificate
|
||||||
|
X509v3 Subject Key Identifier:
|
||||||
|
14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
|
||||||
|
X509v3 Authority Key Identifier:
|
||||||
|
keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
|
||||||
|
DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
|
||||||
|
serial:FD:AB:EC:6A:84:27:04:A7
|
||||||
|
|
||||||
|
X509v3 Extended Key Usage:
|
||||||
|
TLS Web Server Authentication
|
||||||
|
X509v3 Key Usage:
|
||||||
|
Digital Signature, Key Encipherment
|
||||||
|
Signature Algorithm: sha1WithRSAEncryption
|
||||||
|
40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
|
||||||
|
ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
|
||||||
|
23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
|
||||||
|
df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
|
||||||
|
c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
|
||||||
|
9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
|
||||||
|
12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
|
||||||
|
15:42
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
|
||||||
|
CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
|
||||||
|
cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
|
||||||
|
MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
|
||||||
|
bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
|
||||||
|
EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
|
||||||
|
ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
|
||||||
|
MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
|
||||||
|
aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
|
||||||
|
LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
|
||||||
|
cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
|
||||||
|
M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
|
||||||
|
+EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
|
||||||
|
cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
|
||||||
|
gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
|
||||||
|
BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
|
||||||
|
EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
|
||||||
|
EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
|
||||||
|
aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
|
||||||
|
BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
|
||||||
|
zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
|
||||||
|
mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
|
||||||
|
dDBV9m4gmmweCbQMFUI=
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
|
||||||
|
0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
|
||||||
|
4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
|
||||||
|
lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
|
||||||
|
wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
|
||||||
|
wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
|
||||||
|
IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
|
||||||
|
4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
|
||||||
|
WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
|
||||||
|
+0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
|
||||||
|
HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
|
||||||
|
+tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
|
||||||
|
BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
|
||||||
|
5nCwDu5ZTP+khltg
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -1,9 +1,15 @@
|
||||||
package request
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/go-connections/sockets"
|
||||||
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,3 +19,35 @@ func NewAPIClient(t *testing.T) client.APIClient {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
return clt
|
return clt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTLSAPIClient returns a docker API client configured with the
|
||||||
|
// provided TLS settings
|
||||||
|
func NewTLSAPIClient(t *testing.T, host, cacertPath, certPath, keyPath string) (client.APIClient, error) {
|
||||||
|
opts := tlsconfig.Options{
|
||||||
|
CAFile: cacertPath,
|
||||||
|
CertFile: certPath,
|
||||||
|
KeyFile: keyPath,
|
||||||
|
ExclusiveRootPools: true,
|
||||||
|
}
|
||||||
|
config, err := tlsconfig.Client(opts)
|
||||||
|
require.Nil(t, err)
|
||||||
|
tr := &http.Transport{
|
||||||
|
TLSClientConfig: config,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
|
}
|
||||||
|
proto, addr, _, err := client.ParseHost(host)
|
||||||
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
sockets.ConfigureTransport(tr, proto, addr)
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: tr,
|
||||||
|
CheckRedirect: client.CheckRedirect,
|
||||||
|
}
|
||||||
|
verStr := api.DefaultVersion
|
||||||
|
customHeaders := map[string]string{}
|
||||||
|
return client.NewClient(host, verStr, httpClient, customHeaders)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package requirement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gotestyourself/gotestyourself/skip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasHubConnectivity checks to see if https://hub.docker.com is
|
||||||
|
// accessible from the present environment
|
||||||
|
func HasHubConnectivity(t *testing.T) {
|
||||||
|
// Set a timeout on the GET at 15s
|
||||||
|
var timeout = 15 * time.Second
|
||||||
|
var url = "https://hub.docker.com"
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
|
t.Fatalf("Timeout for GET request on %s", url)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
resp.Body.Close()
|
||||||
|
}
|
||||||
|
skip.IfCondition(t, err != nil)
|
||||||
|
}
|
Loading…
Reference in New Issue