Files
moby/integration/plugin/authz/main_test.go
Brian Goff e8dc902781 Wire up tests to support otel tracing
Integration tests will now configure clients to propagate traces as well
as create spans for all tests.

Some extra changes were needed (or desired for trace propagation) in the
test helpers to pass through tracing spans via context.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2023-09-07 18:38:22 +00:00

208 lines
5.4 KiB
Go

//go:build !windows
package authz // import "github.com/docker/docker/integration/plugin/authz"
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"github.com/docker/docker/pkg/authorization"
"github.com/docker/docker/pkg/plugins"
"github.com/docker/docker/testutil"
"github.com/docker/docker/testutil/daemon"
"github.com/docker/docker/testutil/environment"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"gotest.tools/v3/skip"
)
var (
testEnv *environment.Execution
d *daemon.Daemon
server *httptest.Server
baseContext context.Context
)
func TestMain(m *testing.M) {
shutdown := testutil.ConfigureTracing()
ctx, span := otel.Tracer("").Start(context.Background(), "integration/plugin/authz.TestMain")
baseContext = ctx
var err error
testEnv, err = environment.New(ctx)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.End()
shutdown(ctx)
panic(err)
}
err = environment.EnsureFrozenImagesLinux(ctx, testEnv)
if err != nil {
span.SetStatus(codes.Error, err.Error())
span.End()
shutdown(ctx)
panic(err)
}
testEnv.Print()
setupSuite()
exitCode := m.Run()
teardownSuite()
if exitCode != 0 {
span.SetAttributes(attribute.Int("exit", exitCode))
span.SetStatus(codes.Error, "m.Run() exited with non-zero exit code")
}
shutdown(ctx)
os.Exit(exitCode)
}
func setupTest(t *testing.T) context.Context {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
skip.If(t, testEnv.IsRootless, "rootless mode has different view of localhost")
ctx := testutil.StartSpan(baseContext, t)
environment.ProtectAll(ctx, t, testEnv)
d = daemon.New(t, daemon.WithExperimental())
t.Cleanup(func() {
if d != nil {
d.Stop(t)
}
testEnv.Clean(ctx, t)
})
return ctx
}
func setupSuite() {
mux := http.NewServeMux()
server = httptest.NewServer(otelhttp.NewHandler(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 := io.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 := io.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))
}
}