123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package authorization
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "net/http"
- "strings"
- "github.com/Sirupsen/logrus"
- "github.com/docker/docker/pkg/ioutils"
- )
- const maxBodySize = 1048576 // 1MB
- // NewCtx creates new authZ context, it is used to store authorization information related to a specific docker
- // REST http session
- // A context provides two method:
- // Authenticate Request:
- // Call authZ plugins with current REST request and AuthN response
- // Request contains full HTTP packet sent to the docker daemon
- // https://docs.docker.com/engine/reference/api/
- //
- // Authenticate Response:
- // Call authZ plugins with full info about current REST request, REST response and AuthN response
- // The response from this method may contains content that overrides the daemon response
- // This allows authZ plugins to filter privileged content
- //
- // If multiple authZ plugins are specified, the block/allow decision is based on ANDing all plugin results
- // For response manipulation, the response from each plugin is piped between plugins. Plugin execution order
- // is determined according to daemon parameters
- func NewCtx(authZPlugins []Plugin, user, userAuthNMethod, requestMethod, requestURI string) *Ctx {
- return &Ctx{
- plugins: authZPlugins,
- user: user,
- userAuthNMethod: userAuthNMethod,
- requestMethod: requestMethod,
- requestURI: requestURI,
- }
- }
- // Ctx stores a single request-response interaction context
- type Ctx struct {
- user string
- userAuthNMethod string
- requestMethod string
- requestURI string
- plugins []Plugin
- // authReq stores the cached request object for the current transaction
- authReq *Request
- }
- // AuthZRequest authorized the request to the docker daemon using authZ plugins
- func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
- var body []byte
- if sendBody(ctx.requestURI, r.Header) && r.ContentLength > 0 && r.ContentLength < maxBodySize {
- var err error
- body, r.Body, err = drainBody(r.Body)
- if err != nil {
- return err
- }
- }
- var h bytes.Buffer
- if err := r.Header.Write(&h); err != nil {
- return err
- }
- ctx.authReq = &Request{
- User: ctx.user,
- UserAuthNMethod: ctx.userAuthNMethod,
- RequestMethod: ctx.requestMethod,
- RequestURI: ctx.requestURI,
- RequestBody: body,
- RequestHeaders: headers(r.Header),
- }
- if r.TLS != nil {
- for _, c := range r.TLS.PeerCertificates {
- pc := PeerCertificate(*c)
- ctx.authReq.RequestPeerCertificates = append(ctx.authReq.RequestPeerCertificates, &pc)
- }
- }
- for _, plugin := range ctx.plugins {
- logrus.Debugf("AuthZ request using plugin %s", plugin.Name())
- authRes, err := plugin.AuthZRequest(ctx.authReq)
- if err != nil {
- return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
- }
- if !authRes.Allow {
- return newAuthorizationError(plugin.Name(), authRes.Msg)
- }
- }
- return nil
- }
- // AuthZResponse authorized and manipulates the response from docker daemon using authZ plugins
- func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
- ctx.authReq.ResponseStatusCode = rm.StatusCode()
- ctx.authReq.ResponseHeaders = headers(rm.Header())
- if sendBody(ctx.requestURI, rm.Header()) {
- ctx.authReq.ResponseBody = rm.RawBody()
- }
- for _, plugin := range ctx.plugins {
- logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
- authRes, err := plugin.AuthZResponse(ctx.authReq)
- if err != nil {
- return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
- }
- if !authRes.Allow {
- return newAuthorizationError(plugin.Name(), authRes.Msg)
- }
- }
- rm.FlushAll()
- return nil
- }
- // drainBody dump the body (if its length is less than 1MB) without modifying the request state
- func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
- bufReader := bufio.NewReaderSize(body, maxBodySize)
- newBody := ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
- data, err := bufReader.Peek(maxBodySize)
- // Body size exceeds max body size
- if err == nil {
- logrus.Warnf("Request body is larger than: '%d' skipping body", maxBodySize)
- return nil, newBody, nil
- }
- // Body size is less than maximum size
- if err == io.EOF {
- return data, newBody, nil
- }
- // Unknown error
- return nil, newBody, err
- }
- // sendBody returns true when request/response body should be sent to AuthZPlugin
- func sendBody(url string, header http.Header) bool {
- // Skip body for auth endpoint
- if strings.HasSuffix(url, "/auth") {
- return false
- }
- // body is sent only for text or json messages
- return header.Get("Content-Type") == "application/json"
- }
- // headers returns flatten version of the http headers excluding authorization
- func headers(header http.Header) map[string]string {
- v := make(map[string]string, 0)
- for k, values := range header {
- // Skip authorization headers
- if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "X-Registry-Config") || strings.EqualFold(k, "X-Registry-Auth") {
- continue
- }
- for _, val := range values {
- v[k] = val
- }
- }
- return v
- }
- // authorizationError represents an authorization deny error
- type authorizationError struct {
- error
- }
- // HTTPErrorStatusCode returns the authorization error status code (forbidden)
- func (e authorizationError) HTTPErrorStatusCode() int {
- return http.StatusForbidden
- }
- func newAuthorizationError(plugin, msg string) authorizationError {
- return authorizationError{error: fmt.Errorf("authorization denied by plugin %s: %s", plugin, msg)}
- }
|