github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/pkg/authorization/authz.go (about)

     1  package authorization
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"strings"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  )
    13  
    14  // NewCtx creates new authZ context, it is used to store authorization information related to a specific docker
    15  // REST http session
    16  // A context provides two method:
    17  // Authenticate Request:
    18  // Call authZ plugins with current REST request and AuthN response
    19  // Request contains full HTTP packet sent to the docker daemon
    20  // https://docs.docker.com/reference/api/docker_remote_api/
    21  //
    22  // Authenticate Response:
    23  // Call authZ plugins with full info about current REST request, REST response and AuthN response
    24  // The response from this method may contains content that overrides the daemon response
    25  // This allows authZ plugins to filter privileged content
    26  //
    27  // If multiple authZ plugins are specified, the block/allow decision is based on ANDing all plugin results
    28  // For response manipulation, the response from each plugin is piped between plugins. Plugin execution order
    29  // is determined according to daemon parameters
    30  func NewCtx(authZPlugins []Plugin, user, userAuthNMethod, requestMethod, requestURI string) *Ctx {
    31  	return &Ctx{
    32  		plugins:         authZPlugins,
    33  		user:            user,
    34  		userAuthNMethod: userAuthNMethod,
    35  		requestMethod:   requestMethod,
    36  		requestURI:      requestURI,
    37  	}
    38  }
    39  
    40  // Ctx stores a a single request-response interaction context
    41  type Ctx struct {
    42  	user            string
    43  	userAuthNMethod string
    44  	requestMethod   string
    45  	requestURI      string
    46  	plugins         []Plugin
    47  	// authReq stores the cached request object for the current transaction
    48  	authReq *Request
    49  }
    50  
    51  // AuthZRequest authorized the request to the docker daemon using authZ plugins
    52  func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error {
    53  	var body []byte
    54  	if sendBody(ctx.requestURI, r.Header) {
    55  		var (
    56  			err         error
    57  			drainedBody io.ReadCloser
    58  		)
    59  		drainedBody, r.Body, err = drainBody(r.Body)
    60  		if err != nil {
    61  			return err
    62  		}
    63  		defer drainedBody.Close()
    64  		body, err = ioutil.ReadAll(drainedBody)
    65  		if err != nil {
    66  			return err
    67  		}
    68  	}
    69  
    70  	var h bytes.Buffer
    71  	if err := r.Header.Write(&h); err != nil {
    72  		return err
    73  	}
    74  
    75  	ctx.authReq = &Request{
    76  		User:            ctx.user,
    77  		UserAuthNMethod: ctx.userAuthNMethod,
    78  		RequestMethod:   ctx.requestMethod,
    79  		RequestURI:      ctx.requestURI,
    80  		RequestBody:     body,
    81  		RequestHeaders:  headers(r.Header),
    82  	}
    83  
    84  	for _, plugin := range ctx.plugins {
    85  		logrus.Debugf("AuthZ request using plugin %s", plugin.Name())
    86  
    87  		authRes, err := plugin.AuthZRequest(ctx.authReq)
    88  		if err != nil {
    89  			return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
    90  		}
    91  
    92  		if !authRes.Allow {
    93  			return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
    94  		}
    95  	}
    96  
    97  	return nil
    98  }
    99  
   100  // AuthZResponse authorized and manipulates the response from docker daemon using authZ plugins
   101  func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
   102  	ctx.authReq.ResponseStatusCode = rm.StatusCode()
   103  	ctx.authReq.ResponseHeaders = headers(rm.Header())
   104  
   105  	if sendBody(ctx.requestURI, rm.Header()) {
   106  		ctx.authReq.ResponseBody = rm.RawBody()
   107  	}
   108  
   109  	for _, plugin := range ctx.plugins {
   110  		logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
   111  
   112  		authRes, err := plugin.AuthZResponse(ctx.authReq)
   113  		if err != nil {
   114  			return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
   115  		}
   116  
   117  		if !authRes.Allow {
   118  			return fmt.Errorf("authorization denied by plugin %s: %s", plugin.Name(), authRes.Msg)
   119  		}
   120  	}
   121  
   122  	rm.Flush()
   123  
   124  	return nil
   125  }
   126  
   127  // drainBody dump the body, it reads the body data into memory and
   128  // see go sources /go/src/net/http/httputil/dump.go
   129  func drainBody(b io.ReadCloser) (io.ReadCloser, io.ReadCloser, error) {
   130  	var buf bytes.Buffer
   131  	if _, err := buf.ReadFrom(b); err != nil {
   132  		return nil, nil, err
   133  	}
   134  	if err := b.Close(); err != nil {
   135  		return nil, nil, err
   136  	}
   137  	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
   138  }
   139  
   140  // sendBody returns true when request/response body should be sent to AuthZPlugin
   141  func sendBody(url string, header http.Header) bool {
   142  	// Skip body for auth endpoint
   143  	if strings.HasSuffix(url, "/auth") {
   144  		return false
   145  	}
   146  
   147  	// body is sent only for text or json messages
   148  	v := header.Get("Content-Type")
   149  	return strings.HasPrefix(v, "text/") || v == "application/json"
   150  }
   151  
   152  // headers returns flatten version of the http headers excluding authorization
   153  func headers(header http.Header) map[string]string {
   154  	v := make(map[string]string, 0)
   155  	for k, values := range header {
   156  		// Skip authorization headers
   157  		if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "X-Registry-Config") || strings.EqualFold(k, "X-Registry-Auth") {
   158  			continue
   159  		}
   160  		for _, val := range values {
   161  			v[k] = val
   162  		}
   163  	}
   164  	return v
   165  }