integration/plugin/authz: port tests from integration-cli

Signed-off-by: David Sheets <dsheets@docker.com>
This commit is contained in:
David Sheets 2017-09-22 14:05:56 +01:00
parent c982ee805d
commit 928b0631c9
23 changed files with 1338 additions and 837 deletions

View File

@ -15,7 +15,7 @@ source "$SCRIPTDIR/make/.go-autogen"
integration_api_dirs=${TEST_INTEGRATION_DIR:-"$(
find ./integration -type d |
grep -vE '^(./integration$|./integration/util)')"}
grep -vE '^(./integration($|/util|/internal|/testdata|/plugin$))')"}
run_test_integration() {
[[ "$TESTFLAGS" != *-check.f* ]] && run_test_integration_suites

View File

@ -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)
}

View File

@ -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, ","))
}
}

View File

@ -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-----

View File

@ -0,0 +1 @@
../../../integration/testdata/https/ca.pem

View File

@ -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-----

View File

@ -0,0 +1 @@
../../../integration/testdata/https/client-cert.pem

View File

@ -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-----

View File

@ -0,0 +1 @@
../../../integration/testdata/https/client-key.pem

View File

@ -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-----

View File

@ -0,0 +1 @@
../../../integration/testdata/https/server-cert.pem

View File

@ -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-----

View File

@ -0,0 +1 @@
../../../integration/testdata/https/server-key.pem

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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, ","))
}
}

View File

@ -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)
}

View File

@ -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))
}
}

23
integration/testdata/https/ca.pem vendored Normal file
View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -1,9 +1,15 @@
package request
import (
"net"
"net/http"
"testing"
"time"
"github.com/docker/docker/api"
"github.com/docker/docker/client"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/stretchr/testify/require"
)
@ -13,3 +19,35 @@ func NewAPIClient(t *testing.T) client.APIClient {
require.NoError(t, err)
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)
}

View File

@ -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)
}