Browse code

When authz plugin is disabled, remove from authz middleware chain.

When the daemon is configured to run with an authorization-plugin and if
the plugin is disabled, the daemon continues to send API requests to the
plugin and expect it to respond. But the plugin has been disabled. As a
result, all API requests are blocked. Fix this behavior by removing the
disabled plugin from the authz middleware chain.

Tested using riyaz/authz-no-volume-plugin and observed that after
disabling the plugin, API request/response is functional.

Fixes #31836

Signed-off-by: Anusha Ragunathan <anusha.ragunathan@docker.com>

Anusha Ragunathan authored on 2017/03/18 06:57:23
Showing 9 changed files
... ...
@@ -43,6 +43,7 @@ import (
43 43
 	"github.com/docker/docker/pkg/plugingetter"
44 44
 	"github.com/docker/docker/pkg/signal"
45 45
 	"github.com/docker/docker/pkg/system"
46
+	"github.com/docker/docker/plugin"
46 47
 	"github.com/docker/docker/registry"
47 48
 	"github.com/docker/docker/runconfig"
48 49
 	"github.com/docker/go-connections/tlsconfig"
... ...
@@ -264,11 +265,22 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
264 264
 	// Notify that the API is active, but before daemon is set up.
265 265
 	preNotifySystem()
266 266
 
267
-	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
267
+	pluginStore := plugin.NewStore()
268
+
269
+	if err := cli.initMiddlewares(api, serverConfig, pluginStore); err != nil {
270
+		logrus.Fatalf("Error creating middlewares: %v", err)
271
+	}
272
+
273
+	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote, pluginStore)
268 274
 	if err != nil {
269 275
 		return fmt.Errorf("Error starting daemon: %v", err)
270 276
 	}
271 277
 
278
+	// validate after NewDaemon has restored enabled plugins. Dont change order.
279
+	if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
280
+		return fmt.Errorf("Error validating authorization plugin: %v", err)
281
+	}
282
+
272 283
 	if cli.Config.MetricsAddress != "" {
273 284
 		if !d.HasExperimental() {
274 285
 			return fmt.Errorf("metrics-addr is only supported when experimental is enabled")
... ...
@@ -307,10 +319,6 @@ func (cli *DaemonCli) start(opts daemonOptions) (err error) {
307 307
 
308 308
 	cli.d = d
309 309
 
310
-	// initMiddlewares needs cli.d to be populated. Dont change this init order.
311
-	if err := cli.initMiddlewares(api, serverConfig); err != nil {
312
-		logrus.Fatalf("Error creating middlewares: %v", err)
313
-	}
314 310
 	d.SetCluster(c)
315 311
 	initRouter(api, d, c)
316 312
 
... ...
@@ -494,10 +502,10 @@ func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
494 494
 	s.InitRouter(debug.IsEnabled(), routers...)
495 495
 }
496 496
 
497
-func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config) error {
497
+func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config, pluginStore *plugin.Store) error {
498 498
 	v := cfg.Version
499 499
 
500
-	exp := middleware.NewExperimentalMiddleware(cli.d.HasExperimental())
500
+	exp := middleware.NewExperimentalMiddleware(cli.Config.Experimental)
501 501
 	s.UseMiddleware(exp)
502 502
 
503 503
 	vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, api.MinVersion)
... ...
@@ -508,10 +516,8 @@ func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config
508 508
 		s.UseMiddleware(c)
509 509
 	}
510 510
 
511
-	if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, cli.d.PluginStore); err != nil {
512
-		return fmt.Errorf("Error validating authorization plugin: %v", err)
513
-	}
514
-	cli.authzMiddleware = authorization.NewMiddleware(cli.Config.AuthorizationPlugins, cli.d.PluginStore)
511
+	cli.authzMiddleware = authorization.NewMiddleware(cli.Config.AuthorizationPlugins, pluginStore)
512
+	cli.Config.AuthzMiddleware = cli.authzMiddleware
515 513
 	s.UseMiddleware(cli.authzMiddleware)
516 514
 	return nil
517 515
 }
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"github.com/Sirupsen/logrus"
17 17
 	daemondiscovery "github.com/docker/docker/daemon/discovery"
18 18
 	"github.com/docker/docker/opts"
19
+	"github.com/docker/docker/pkg/authorization"
19 20
 	"github.com/docker/docker/pkg/discovery"
20 21
 	"github.com/docker/docker/registry"
21 22
 	"github.com/imdario/mergo"
... ...
@@ -82,25 +83,26 @@ type CommonTLSOptions struct {
82 82
 // It includes json tags to deserialize configuration from a file
83 83
 // using the same names that the flags in the command line use.
84 84
 type CommonConfig struct {
85
-	AuthorizationPlugins []string            `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
86
-	AutoRestart          bool                `json:"-"`
87
-	Context              map[string][]string `json:"-"`
88
-	DisableBridge        bool                `json:"-"`
89
-	DNS                  []string            `json:"dns,omitempty"`
90
-	DNSOptions           []string            `json:"dns-opts,omitempty"`
91
-	DNSSearch            []string            `json:"dns-search,omitempty"`
92
-	ExecOptions          []string            `json:"exec-opts,omitempty"`
93
-	GraphDriver          string              `json:"storage-driver,omitempty"`
94
-	GraphOptions         []string            `json:"storage-opts,omitempty"`
95
-	Labels               []string            `json:"labels,omitempty"`
96
-	Mtu                  int                 `json:"mtu,omitempty"`
97
-	Pidfile              string              `json:"pidfile,omitempty"`
98
-	RawLogs              bool                `json:"raw-logs,omitempty"`
99
-	Root                 string              `json:"graph,omitempty"`
100
-	SocketGroup          string              `json:"group,omitempty"`
101
-	TrustKeyPath         string              `json:"-"`
102
-	CorsHeaders          string              `json:"api-cors-header,omitempty"`
103
-	EnableCors           bool                `json:"api-enable-cors,omitempty"`
85
+	AuthzMiddleware      *authorization.Middleware `json:"-"`
86
+	AuthorizationPlugins []string                  `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
87
+	AutoRestart          bool                      `json:"-"`
88
+	Context              map[string][]string       `json:"-"`
89
+	DisableBridge        bool                      `json:"-"`
90
+	DNS                  []string                  `json:"dns,omitempty"`
91
+	DNSOptions           []string                  `json:"dns-opts,omitempty"`
92
+	DNSSearch            []string                  `json:"dns-search,omitempty"`
93
+	ExecOptions          []string                  `json:"exec-opts,omitempty"`
94
+	GraphDriver          string                    `json:"storage-driver,omitempty"`
95
+	GraphOptions         []string                  `json:"storage-opts,omitempty"`
96
+	Labels               []string                  `json:"labels,omitempty"`
97
+	Mtu                  int                       `json:"mtu,omitempty"`
98
+	Pidfile              string                    `json:"pidfile,omitempty"`
99
+	RawLogs              bool                      `json:"raw-logs,omitempty"`
100
+	Root                 string                    `json:"graph,omitempty"`
101
+	SocketGroup          string                    `json:"group,omitempty"`
102
+	TrustKeyPath         string                    `json:"-"`
103
+	CorsHeaders          string                    `json:"api-cors-header,omitempty"`
104
+	EnableCors           bool                      `json:"api-enable-cors,omitempty"`
104 105
 
105 106
 	// LiveRestoreEnabled determines whether we should keep containers
106 107
 	// alive upon daemon shutdown/start
... ...
@@ -465,7 +465,7 @@ func (daemon *Daemon) IsSwarmCompatible() error {
465 465
 
466 466
 // NewDaemon sets up everything for the daemon to be able to service
467 467
 // requests from the webserver.
468
-func NewDaemon(config *config.Config, registryService registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
468
+func NewDaemon(config *config.Config, registryService registry.Service, containerdRemote libcontainerd.Remote, pluginStore *plugin.Store) (daemon *Daemon, err error) {
469 469
 	setDefaultMtu(config)
470 470
 
471 471
 	// Ensure that we have a correct root key limit for launching containers.
... ...
@@ -562,7 +562,8 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
562 562
 	}
563 563
 
564 564
 	d.RegistryService = registryService
565
-	d.PluginStore = plugin.NewStore(config.Root) // todo: remove
565
+	d.PluginStore = pluginStore
566
+
566 567
 	// Plugin system initialization should happen before restore. Do not change order.
567 568
 	d.pluginManager, err = plugin.NewManager(plugin.ManagerConfig{
568 569
 		Root:               filepath.Join(config.Root, "plugins"),
... ...
@@ -572,6 +573,7 @@ func NewDaemon(config *config.Config, registryService registry.Service, containe
572 572
 		RegistryService:    registryService,
573 573
 		LiveRestoreEnabled: config.LiveRestoreEnabled,
574 574
 		LogPluginEvent:     d.LogPluginEvent, // todo: make private
575
+		AuthzMiddleware:    config.AuthzMiddleware,
575 576
 	})
576 577
 	if err != nil {
577 578
 		return nil, errors.Wrap(err, "couldn't create plugin manager")
... ...
@@ -75,6 +75,36 @@ func (s *DockerAuthzV2Suite) TestAuthZPluginAllowNonVolumeRequest(c *check.C) {
75 75
 	c.Assert(assertContainerList(out, []string{id}), check.Equals, true)
76 76
 }
77 77
 
78
+func (s *DockerAuthzV2Suite) TestAuthZPluginDisable(c *check.C) {
79
+	testRequires(c, DaemonIsLinux, IsAmd64, Network)
80
+	// Install authz plugin
81
+	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
82
+	c.Assert(err, checker.IsNil)
83
+	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
84
+	// because it needs to pull busybox
85
+	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
86
+	c.Assert(s.d.LoadBusybox(), check.IsNil)
87
+
88
+	// defer removing the plugin
89
+	defer func() {
90
+		s.d.Restart(c)
91
+		_, err = s.d.Cmd("plugin", "rm", "-f", authzPluginNameWithTag)
92
+		c.Assert(err, checker.IsNil)
93
+	}()
94
+
95
+	out, err := s.d.Cmd("volume", "create")
96
+	c.Assert(err, check.NotNil)
97
+	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
98
+
99
+	// disable the plugin
100
+	_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
101
+	c.Assert(err, checker.IsNil)
102
+
103
+	// now test to see if the docker api works.
104
+	_, err = s.d.Cmd("volume", "create")
105
+	c.Assert(err, checker.IsNil)
106
+}
107
+
78 108
 func (s *DockerAuthzV2Suite) TestAuthZPluginRejectVolumeRequests(c *check.C) {
79 109
 	testRequires(c, DaemonIsLinux, IsAmd64, Network)
80 110
 	// Install authz plugin
... ...
@@ -25,6 +25,20 @@ func NewMiddleware(names []string, pg plugingetter.PluginGetter) *Middleware {
25 25
 	}
26 26
 }
27 27
 
28
+// GetAuthzPlugins gets authorization plugins
29
+func (m *Middleware) GetAuthzPlugins() []Plugin {
30
+	m.mu.Lock()
31
+	defer m.mu.Unlock()
32
+	return m.plugins
33
+}
34
+
35
+// SetAuthzPlugins sets authorization plugins
36
+func (m *Middleware) SetAuthzPlugins(plugins []Plugin) {
37
+	m.mu.Lock()
38
+	m.plugins = plugins
39
+	m.mu.Unlock()
40
+}
41
+
28 42
 // SetPlugins sets the plugin used for authorization
29 43
 func (m *Middleware) SetPlugins(names []string) {
30 44
 	m.mu.Lock()
... ...
@@ -35,10 +49,7 @@ func (m *Middleware) SetPlugins(names []string) {
35 35
 // WrapHandler returns a new handler function wrapping the previous one in the request chain.
36 36
 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 {
37 37
 	return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
38
-
39
-		m.mu.Lock()
40
-		plugins := m.plugins
41
-		m.mu.Unlock()
38
+		plugins := m.GetAuthzPlugins()
42 39
 		if len(plugins) == 0 {
43 40
 			return handler(ctx, w, r, vars)
44 41
 		}
... ...
@@ -70,6 +81,16 @@ func (m *Middleware) WrapHandler(handler func(ctx context.Context, w http.Respon
70 70
 			logrus.Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, errD)
71 71
 		}
72 72
 
73
+		// There's a chance that the authCtx.plugins was updated. One of the reasons
74
+		// this can happen is when an authzplugin is disabled.
75
+		plugins = m.GetAuthzPlugins()
76
+		if len(plugins) == 0 {
77
+			logrus.Debug("There are no authz plugins in the chain")
78
+			return nil
79
+		}
80
+
81
+		authCtx.plugins = plugins
82
+
73 83
 		if err := authCtx.AuthZResponse(rw, r); errD == nil && err != nil {
74 84
 			logrus.Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
75 85
 			return err
... ...
@@ -61,6 +61,11 @@ func (a *authorizationPlugin) Name() string {
61 61
 	return a.name
62 62
 }
63 63
 
64
+// Set the remote for an authz pluginv2
65
+func (a *authorizationPlugin) SetName(remote string) {
66
+	a.name = remote
67
+}
68
+
64 69
 func (a *authorizationPlugin) AuthZRequest(authReq *Request) (*Response, error) {
65 70
 	if err := a.initPlugin(); err != nil {
66 71
 		return nil, err
... ...
@@ -98,6 +103,7 @@ func (a *authorizationPlugin) initPlugin() error {
98 98
 
99 99
 			if pg := GetPluginGetter(); pg != nil {
100 100
 				plugin, e = pg.Get(a.name, AuthZApiImplements, plugingetter.Lookup)
101
+				a.SetName(plugin.Name())
101 102
 			} else {
102 103
 				plugin, e = plugins.Get(a.name, AuthZApiImplements)
103 104
 			}
... ...
@@ -26,6 +26,7 @@ import (
26 26
 	"github.com/docker/docker/distribution/xfer"
27 27
 	"github.com/docker/docker/image"
28 28
 	"github.com/docker/docker/layer"
29
+	"github.com/docker/docker/pkg/authorization"
29 30
 	"github.com/docker/docker/pkg/chrootarchive"
30 31
 	"github.com/docker/docker/pkg/mount"
31 32
 	"github.com/docker/docker/pkg/pools"
... ...
@@ -56,6 +57,19 @@ func (pm *Manager) Disable(refOrID string, config *types.PluginDisableConfig) er
56 56
 		return fmt.Errorf("plugin %s is in use", p.Name())
57 57
 	}
58 58
 
59
+	for _, typ := range p.GetTypes() {
60
+		if typ.Capability == authorization.AuthZApiImplements {
61
+			authzList := pm.config.AuthzMiddleware.GetAuthzPlugins()
62
+			for i, authPlugin := range authzList {
63
+				if authPlugin.Name() == p.Name() {
64
+					// Remove plugin from authzmiddleware chain
65
+					authzList = append(authzList[:i], authzList[i+1:]...)
66
+					pm.config.AuthzMiddleware.SetAuthzPlugins(authzList)
67
+				}
68
+			}
69
+		}
70
+	}
71
+
59 72
 	if err := pm.disable(p, c); err != nil {
60 73
 		return err
61 74
 	}
... ...
@@ -18,7 +18,7 @@ type Store struct {
18 18
 }
19 19
 
20 20
 // NewStore creates a Store.
21
-func NewStore(libRoot string) *Store {
21
+func NewStore() *Store {
22 22
 	return &Store{
23 23
 		plugins:  make(map[string]*v2.Plugin),
24 24
 		handlers: make(map[string][]func(string, *plugins.Client)),
... ...
@@ -18,6 +18,7 @@ import (
18 18
 	"github.com/docker/docker/image"
19 19
 	"github.com/docker/docker/layer"
20 20
 	"github.com/docker/docker/libcontainerd"
21
+	"github.com/docker/docker/pkg/authorization"
21 22
 	"github.com/docker/docker/pkg/ioutils"
22 23
 	"github.com/docker/docker/pkg/mount"
23 24
 	"github.com/docker/docker/plugin/v2"
... ...
@@ -49,6 +50,7 @@ type ManagerConfig struct {
49 49
 	LogPluginEvent     eventLogger
50 50
 	Root               string
51 51
 	ExecRoot           string
52
+	AuthzMiddleware    *authorization.Middleware
52 53
 }
53 54
 
54 55
 // Manager controls the plugin subsystem.