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