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