Merge pull request #21897 from calavera/remove_authorization_refs_from_api

Move middleware to interfaces.
This commit is contained in:
Alexander Morozov 2016-04-12 14:08:36 -07:00
commit 9438572fc0
12 changed files with 218 additions and 155 deletions

View File

@ -2,10 +2,8 @@ package server
import ( import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/server/middleware" "github.com/docker/docker/api/server/middleware"
"github.com/docker/docker/pkg/authorization"
) )
// handleWithGlobalMiddlwares wraps the handler function for a request with // handleWithGlobalMiddlwares wraps the handler function for a request with
@ -14,27 +12,13 @@ import (
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc { func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
next := handler next := handler
handleVersion := middleware.NewVersionMiddleware(s.cfg.Version, api.DefaultVersion, api.MinVersion) for _, m := range s.middlewares {
next = handleVersion(next) next = m.WrapHandler(next)
if s.cfg.EnableCors {
handleCORS := middleware.NewCORSMiddleware(s.cfg.CorsHeaders)
next = handleCORS(next)
} }
handleUserAgent := middleware.NewUserAgentMiddleware(s.cfg.Version)
next = handleUserAgent(next)
// Only want this on debug level
if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel { if s.cfg.Logging && logrus.GetLevel() == logrus.DebugLevel {
next = middleware.DebugRequestMiddleware(next) next = middleware.DebugRequestMiddleware(next)
} }
if len(s.cfg.AuthorizationPluginNames) > 0 {
s.authZPlugins = authorization.NewPlugins(s.cfg.AuthorizationPluginNames)
handleAuthorization := middleware.NewAuthorizationMiddleware(s.authZPlugins)
next = handleAuthorization(next)
}
return next return next
} }

View File

@ -1,50 +0,0 @@
package middleware
import (
"net/http"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/pkg/authorization"
"golang.org/x/net/context"
)
// NewAuthorizationMiddleware creates a new Authorization middleware.
func NewAuthorizationMiddleware(plugins []authorization.Plugin) Middleware {
return func(handler httputils.APIFunc) httputils.APIFunc {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
user := ""
userAuthNMethod := ""
// Default authorization using existing TLS connection credentials
// FIXME: Non trivial authorization mechanisms (such as advanced certificate validations, kerberos support
// and ldap) will be extracted using AuthN feature, which is tracked under:
// https://github.com/docker/docker/pull/20883
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
user = r.TLS.PeerCertificates[0].Subject.CommonName
userAuthNMethod = "TLS"
}
authCtx := authorization.NewCtx(plugins, user, userAuthNMethod, r.Method, r.RequestURI)
if err := authCtx.AuthZRequest(w, r); err != nil {
logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
rw := authorization.NewResponseModifier(w)
if err := handler(ctx, rw, r, vars); err != nil {
logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
if err := authCtx.AuthZResponse(rw, r); err != nil {
logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
return nil
}
}
}

View File

@ -4,30 +4,34 @@ import (
"net/http" "net/http"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// NewCORSMiddleware creates a new CORS middleware. // CORSMiddleware injects CORS headers to each request
func NewCORSMiddleware(defaultHeaders string) Middleware { // when it's configured.
return func(handler httputils.APIFunc) httputils.APIFunc { type CORSMiddleware struct {
defaultHeaders string
}
// NewCORSMiddleware creates a new CORSMiddleware with default headers.
func NewCORSMiddleware(d string) CORSMiddleware {
return CORSMiddleware{defaultHeaders: d}
}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (c CORSMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
// otherwise, all head values will be passed to HTTP handler // otherwise, all head values will be passed to HTTP handler
corsHeaders := defaultHeaders corsHeaders := c.defaultHeaders
if corsHeaders == "" { if corsHeaders == "" {
corsHeaders = "*" corsHeaders = "*"
} }
writeCorsHeaders(w, r, corsHeaders)
return handler(ctx, w, r, vars)
}
}
}
func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) {
logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders) logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders)
w.Header().Add("Access-Control-Allow-Origin", corsHeaders) w.Header().Add("Access-Control-Allow-Origin", corsHeaders)
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth")
w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS") w.Header().Add("Access-Control-Allow-Methods", "HEAD, GET, POST, DELETE, PUT, OPTIONS")
return handler(ctx, w, r, vars)
}
} }

View File

@ -13,7 +13,7 @@ import (
) )
// DebugRequestMiddleware dumps the request to logger // DebugRequestMiddleware dumps the request to logger
func DebugRequestMiddleware(handler httputils.APIFunc) httputils.APIFunc { func DebugRequestMiddleware(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
logrus.Debugf("Calling %s %s", r.Method, r.RequestURI) logrus.Debugf("Calling %s %s", r.Method, r.RequestURI)

View File

@ -1,7 +1,13 @@
package middleware package middleware
import "github.com/docker/docker/api/server/httputils" import (
"net/http"
// Middleware is an adapter to allow the use of ordinary functions as Docker API filters. "golang.org/x/net/context"
// Any function that has the appropriate signature can be registered as a middleware. )
type Middleware func(handler httputils.APIFunc) httputils.APIFunc
// Middleware is an interface to allow the use of ordinary functions as Docker API filters.
// Any struct that has the appropriate signature can be registered as a middleware.
type Middleware interface {
WrapHandler(func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
}

View File

@ -10,11 +10,22 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
// NewUserAgentMiddleware creates a new UserAgent middleware. // UserAgentMiddleware is a middleware that
func NewUserAgentMiddleware(versionCheck string) Middleware { // validates the client user-agent.
serverVersion := version.Version(versionCheck) type UserAgentMiddleware struct {
serverVersion version.Version
}
return func(handler httputils.APIFunc) httputils.APIFunc { // NewUserAgentMiddleware creates a new UserAgentMiddleware
// with the server version.
func NewUserAgentMiddleware(s version.Version) UserAgentMiddleware {
return UserAgentMiddleware{
serverVersion: s,
}
}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (u UserAgentMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent")) ctx = context.WithValue(ctx, httputils.UAStringKey, r.Header.Get("User-Agent"))
@ -27,11 +38,10 @@ func NewUserAgentMiddleware(versionCheck string) Middleware {
userAgent[1] = strings.Split(userAgent[1], " ")[0] userAgent[1] = strings.Split(userAgent[1], " ")[0]
} }
if len(userAgent) == 2 && !serverVersion.Equal(version.Version(userAgent[1])) { if len(userAgent) == 2 && !u.serverVersion.Equal(version.Version(userAgent[1])) {
logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], serverVersion) logrus.Debugf("Client and server don't have the same version (client: %s, server: %s)", userAgent[1], u.serverVersion)
} }
} }
return handler(ctx, w, r, vars) return handler(ctx, w, r, vars)
} }
} }
}

View File

@ -5,7 +5,6 @@ import (
"net/http" "net/http"
"runtime" "runtime"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/pkg/version" "github.com/docker/docker/pkg/version"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -18,28 +17,43 @@ func (badRequestError) HTTPErrorStatusCode() int {
return http.StatusBadRequest return http.StatusBadRequest
} }
// NewVersionMiddleware creates a new Version middleware. // VersionMiddleware is a middleware that
func NewVersionMiddleware(versionCheck string, defaultVersion, minVersion version.Version) Middleware { // validates the client and server versions.
serverVersion := version.Version(versionCheck) type VersionMiddleware struct {
serverVersion version.Version
defaultVersion version.Version
minVersion version.Version
}
return func(handler httputils.APIFunc) httputils.APIFunc { // NewVersionMiddleware creates a new VersionMiddleware
// with the default versions.
func NewVersionMiddleware(s, d, m version.Version) VersionMiddleware {
return VersionMiddleware{
serverVersion: s,
defaultVersion: d,
minVersion: m,
}
}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (v VersionMiddleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
apiVersion := version.Version(vars["version"]) apiVersion := version.Version(vars["version"])
if apiVersion == "" { if apiVersion == "" {
apiVersion = defaultVersion apiVersion = v.defaultVersion
} }
if apiVersion.GreaterThan(defaultVersion) { if apiVersion.GreaterThan(v.defaultVersion) {
return badRequestError{fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, defaultVersion)} return badRequestError{fmt.Errorf("client is newer than server (client API version: %s, server API version: %s)", apiVersion, v.defaultVersion)}
} }
if apiVersion.LessThan(minVersion) { if apiVersion.LessThan(v.minVersion) {
return badRequestError{fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, minVersion)} return badRequestError{fmt.Errorf("client version %s is too old. Minimum supported API version is %s, please upgrade your client to a newer version", apiVersion, v.minVersion)}
} }
header := fmt.Sprintf("Docker/%s (%s)", serverVersion, runtime.GOOS) header := fmt.Sprintf("Docker/%s (%s)", v.serverVersion, runtime.GOOS)
w.Header().Set("Server", header) w.Header().Set("Server", header)
ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion) ctx = context.WithValue(ctx, "api-version", apiVersion)
return handler(ctx, w, r, vars) return handler(ctx, w, r, vars)
} }
}
} }

View File

@ -21,8 +21,8 @@ func TestVersionMiddleware(t *testing.T) {
defaultVersion := version.Version("1.10.0") defaultVersion := version.Version("1.10.0")
minVersion := version.Version("1.2.0") minVersion := version.Version("1.2.0")
m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion) m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion)
h := m(handler) h := m.WrapHandler(handler)
req, _ := http.NewRequest("GET", "/containers/json", nil) req, _ := http.NewRequest("GET", "/containers/json", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
@ -42,8 +42,8 @@ func TestVersionMiddlewareWithErrors(t *testing.T) {
defaultVersion := version.Version("1.10.0") defaultVersion := version.Version("1.10.0")
minVersion := version.Version("1.2.0") minVersion := version.Version("1.2.0")
m := NewVersionMiddleware(defaultVersion.String(), defaultVersion, minVersion) m := NewVersionMiddleware(defaultVersion, defaultVersion, minVersion)
h := m(handler) h := m.WrapHandler(handler)
req, _ := http.NewRequest("GET", "/containers/json", nil) req, _ := http.NewRequest("GET", "/containers/json", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()

View File

@ -8,8 +8,8 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/server/middleware"
"github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router"
"github.com/docker/docker/pkg/authorization"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -23,7 +23,6 @@ type Config struct {
Logging bool Logging bool
EnableCors bool EnableCors bool
CorsHeaders string CorsHeaders string
AuthorizationPluginNames []string
Version string Version string
SocketGroup string SocketGroup string
TLSConfig *tls.Config TLSConfig *tls.Config
@ -34,8 +33,8 @@ type Server struct {
cfg *Config cfg *Config
servers []*HTTPServer servers []*HTTPServer
routers []router.Router routers []router.Router
authZPlugins []authorization.Plugin
routerSwapper *routerSwapper routerSwapper *routerSwapper
middlewares []middleware.Middleware
} }
// New returns a new instance of the server based on the specified configuration. // New returns a new instance of the server based on the specified configuration.
@ -46,6 +45,12 @@ func New(cfg *Config) *Server {
} }
} }
// UseMiddleware appends a new middleware to the request chain.
// This needs to be called before the API routes are configured.
func (s *Server) UseMiddleware(m middleware.Middleware) {
s.middlewares = append(s.middlewares, m)
}
// Accept sets a listener the server accepts connections into. // Accept sets a listener the server accepts connections into.
func (s *Server) Accept(addr string, listeners ...net.Listener) { func (s *Server) Accept(addr string, listeners ...net.Listener) {
for _, listener := range listeners { for _, listener := range listeners {

View File

@ -6,7 +6,10 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/docker/api"
"github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/server/middleware"
"github.com/docker/docker/pkg/version"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -19,6 +22,8 @@ func TestMiddlewares(t *testing.T) {
cfg: cfg, cfg: cfg,
} }
srv.UseMiddleware(middleware.NewVersionMiddleware(version.Version("0.1omega2"), api.DefaultVersion, api.MinVersion))
req, _ := http.NewRequest("GET", "/containers/json", nil) req, _ := http.NewRequest("GET", "/containers/json", nil)
resp := httptest.NewRecorder() resp := httptest.NewRecorder()
ctx := context.Background() ctx := context.Background()

View File

@ -14,7 +14,9 @@ import (
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/docker/distribution/uuid" "github.com/docker/distribution/uuid"
"github.com/docker/docker/api"
apiserver "github.com/docker/docker/api/server" apiserver "github.com/docker/docker/api/server"
"github.com/docker/docker/api/server/middleware"
"github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router"
"github.com/docker/docker/api/server/router/build" "github.com/docker/docker/api/server/router/build"
"github.com/docker/docker/api/server/router/container" "github.com/docker/docker/api/server/router/container"
@ -29,12 +31,14 @@ import (
"github.com/docker/docker/dockerversion" "github.com/docker/docker/dockerversion"
"github.com/docker/docker/libcontainerd" "github.com/docker/docker/libcontainerd"
"github.com/docker/docker/opts" "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/authorization"
"github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/jsonlog"
"github.com/docker/docker/pkg/listeners" "github.com/docker/docker/pkg/listeners"
flag "github.com/docker/docker/pkg/mflag" flag "github.com/docker/docker/pkg/mflag"
"github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/pidfile"
"github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/version"
"github.com/docker/docker/registry" "github.com/docker/docker/registry"
"github.com/docker/docker/runconfig" "github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
@ -208,7 +212,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
} }
serverConfig := &apiserver.Config{ serverConfig := &apiserver.Config{
AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
Logging: true, Logging: true,
SocketGroup: cli.Config.SocketGroup, SocketGroup: cli.Config.SocketGroup,
Version: dockerversion.Version, Version: dockerversion.Version,
@ -301,6 +304,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
"graphdriver": d.GraphDriverName(), "graphdriver": d.GraphDriverName(),
}).Info("Docker daemon") }).Info("Docker daemon")
cli.initMiddlewares(api, serverConfig)
initRouter(api, d) initRouter(api, d)
reload := func(config *daemon.Config) { reload := func(config *daemon.Config) {
@ -433,3 +437,24 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon) {
s.InitRouter(utils.IsDebugEnabled(), routers...) s.InitRouter(utils.IsDebugEnabled(), routers...)
} }
func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config) {
v := version.Version(cfg.Version)
vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion)
s.UseMiddleware(vm)
if cfg.EnableCors {
c := middleware.NewCORSMiddleware(cfg.CorsHeaders)
s.UseMiddleware(c)
}
u := middleware.NewUserAgentMiddleware(v)
s.UseMiddleware(u)
if len(cli.Config.AuthorizationPlugins) > 0 {
authZPlugins := authorization.NewPlugins(cli.Config.AuthorizationPlugins)
handleAuthorization := authorization.NewMiddleware(authZPlugins)
s.UseMiddleware(handleAuthorization)
}
}

View File

@ -0,0 +1,60 @@
package authorization
import (
"net/http"
"github.com/Sirupsen/logrus"
"golang.org/x/net/context"
)
// Middleware uses a list of plugins to
// handle authorization in the API requests.
type Middleware struct {
plugins []Plugin
}
// NewMiddleware creates a new Middleware
// with a slice of plugins.
func NewMiddleware(p []Plugin) Middleware {
return Middleware{
plugins: p,
}
}
// WrapHandler returns a new handler function wrapping the previous one in the request chain.
func (m Middleware) WrapHandler(handler func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error) func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
user := ""
userAuthNMethod := ""
// Default authorization using existing TLS connection credentials
// FIXME: Non trivial authorization mechanisms (such as advanced certificate validations, kerberos support
// and ldap) will be extracted using AuthN feature, which is tracked under:
// https://github.com/docker/docker/pull/20883
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
user = r.TLS.PeerCertificates[0].Subject.CommonName
userAuthNMethod = "TLS"
}
authCtx := NewCtx(m.plugins, user, userAuthNMethod, r.Method, r.RequestURI)
if err := authCtx.AuthZRequest(w, r); err != nil {
logrus.Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
rw := NewResponseModifier(w)
if err := handler(ctx, rw, r, vars); err != nil {
logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
if err := authCtx.AuthZResponse(rw, r); err != nil {
logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
return err
}
return nil
}
}