docker/docker

View on GitHub
pkg/authorization/middleware.go

Summary

Maintainability
A
40 mins
Test Coverage
package authorization // import "github.com/docker/docker/pkg/authorization"

import (
    "context"
    "net/http"
    "sync"

    "github.com/containerd/log"
    "github.com/docker/docker/pkg/plugingetter"
)

// Middleware uses a list of plugins to
// handle authorization in the API requests.
type Middleware struct {
    mu      sync.Mutex
    plugins []Plugin
}

// NewMiddleware creates a new Middleware
// with a slice of plugins names.
func NewMiddleware(names []string, pg plugingetter.PluginGetter) *Middleware {
    SetPluginGetter(pg)
    return &Middleware{
        plugins: newPlugins(names),
    }
}

func (m *Middleware) getAuthzPlugins() []Plugin {
    m.mu.Lock()
    defer m.mu.Unlock()
    return m.plugins
}

// SetPlugins sets the plugin used for authorization
func (m *Middleware) SetPlugins(names []string) {
    m.mu.Lock()
    m.plugins = newPlugins(names)
    m.mu.Unlock()
}

// RemovePlugin removes a single plugin from this authz middleware chain
func (m *Middleware) RemovePlugin(name string) {
    m.mu.Lock()
    defer m.mu.Unlock()
    plugins := m.plugins[:0]
    for _, authPlugin := range m.plugins {
        if authPlugin.Name() != name {
            plugins = append(plugins, authPlugin)
        }
    }
    m.plugins = plugins
}

// 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 {
        plugins := m.getAuthzPlugins()
        if len(plugins) == 0 {
            return handler(ctx, w, r, vars)
        }

        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(plugins, user, userAuthNMethod, r.Method, r.RequestURI)

        if err := authCtx.AuthZRequest(w, r); err != nil {
            log.G(ctx).Errorf("AuthZRequest for %s %s returned error: %s", r.Method, r.RequestURI, err)
            return err
        }

        rw := NewResponseModifier(w)

        var errD error

        if errD = handler(ctx, rw, r, vars); errD != nil {
            log.G(ctx).Errorf("Handler for %s %s returned error: %s", r.Method, r.RequestURI, errD)
        }

        // There's a chance that the authCtx.plugins was updated. One of the reasons
        // this can happen is when an authzplugin is disabled.
        plugins = m.getAuthzPlugins()
        if len(plugins) == 0 {
            log.G(ctx).Debug("There are no authz plugins in the chain")
            return nil
        }

        authCtx.plugins = plugins

        if err := authCtx.AuthZResponse(rw, r); errD == nil && err != nil {
            log.G(ctx).Errorf("AuthZResponse for %s %s returned error: %s", r.Method, r.RequestURI, err)
            return err
        }

        if errD != nil {
            return errD
        }

        return nil
    }
}