github.com/kim0/docker@v0.6.2-0.20161130212042-4addda3f07e7/pkg/authorization/authz.go (about) 1 package authorization 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "net/http" 9 "strings" 10 11 "github.com/Sirupsen/logrus" 12 "github.com/docker/docker/pkg/ioutils" 13 ) 14 15 const maxBodySize = 1048576 // 1MB 16 17 // NewCtx creates new authZ context, it is used to store authorization information related to a specific docker 18 // REST http session 19 // A context provides two method: 20 // Authenticate Request: 21 // Call authZ plugins with current REST request and AuthN response 22 // Request contains full HTTP packet sent to the docker daemon 23 // https://docs.docker.com/engine/reference/api/docker_remote_api/ 24 // 25 // Authenticate Response: 26 // Call authZ plugins with full info about current REST request, REST response and AuthN response 27 // The response from this method may contains content that overrides the daemon response 28 // This allows authZ plugins to filter privileged content 29 // 30 // If multiple authZ plugins are specified, the block/allow decision is based on ANDing all plugin results 31 // For response manipulation, the response from each plugin is piped between plugins. Plugin execution order 32 // is determined according to daemon parameters 33 func NewCtx(authZPlugins []Plugin, user, userAuthNMethod, requestMethod, requestURI string) *Ctx { 34 return &Ctx{ 35 plugins: authZPlugins, 36 user: user, 37 userAuthNMethod: userAuthNMethod, 38 requestMethod: requestMethod, 39 requestURI: requestURI, 40 } 41 } 42 43 // Ctx stores a single request-response interaction context 44 type Ctx struct { 45 user string 46 userAuthNMethod string 47 requestMethod string 48 requestURI string 49 plugins []Plugin 50 // authReq stores the cached request object for the current transaction 51 authReq *Request 52 } 53 54 // AuthZRequest authorized the request to the docker daemon using authZ plugins 55 // Side effect: If the authz plugin is invalid, then update ctx.plugins, so that 56 // the caller(middleware) can update its list and stop retrying with invalid plugins. 57 func (ctx *Ctx) AuthZRequest(w http.ResponseWriter, r *http.Request) error { 58 var body []byte 59 if sendBody(ctx.requestURI, r.Header) && r.ContentLength > 0 && r.ContentLength < maxBodySize { 60 var err error 61 body, r.Body, err = drainBody(r.Body) 62 if err != nil { 63 return err 64 } 65 } 66 67 var h bytes.Buffer 68 if err := r.Header.Write(&h); err != nil { 69 return err 70 } 71 72 ctx.authReq = &Request{ 73 User: ctx.user, 74 UserAuthNMethod: ctx.userAuthNMethod, 75 RequestMethod: ctx.requestMethod, 76 RequestURI: ctx.requestURI, 77 RequestBody: body, 78 RequestHeaders: headers(r.Header), 79 } 80 81 for i, plugin := range ctx.plugins { 82 logrus.Debugf("AuthZ request using plugin %s", plugin.Name()) 83 84 authRes, err := plugin.AuthZRequest(ctx.authReq) 85 if err != nil { 86 if err == ErrInvalidPlugin { 87 ctx.plugins = append(ctx.plugins[:i], ctx.plugins[i+1:]...) 88 } 89 return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err) 90 } 91 92 if !authRes.Allow { 93 return newAuthorizationError(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 // Side effect: If the authz plugin is invalid, then update ctx.plugins, so that 102 // the caller(middleware) can update its list and stop retrying with invalid plugins. 103 func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error { 104 ctx.authReq.ResponseStatusCode = rm.StatusCode() 105 ctx.authReq.ResponseHeaders = headers(rm.Header()) 106 107 if sendBody(ctx.requestURI, rm.Header()) { 108 ctx.authReq.ResponseBody = rm.RawBody() 109 } 110 111 for i, plugin := range ctx.plugins { 112 logrus.Debugf("AuthZ response using plugin %s", plugin.Name()) 113 114 authRes, err := plugin.AuthZResponse(ctx.authReq) 115 if err != nil { 116 if err == ErrInvalidPlugin { 117 ctx.plugins = append(ctx.plugins[:i], ctx.plugins[i+1:]...) 118 } 119 return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err) 120 } 121 122 if !authRes.Allow { 123 return newAuthorizationError(plugin.Name(), authRes.Msg) 124 } 125 } 126 127 rm.FlushAll() 128 129 return nil 130 } 131 132 // drainBody dump the body (if its length is less than 1MB) without modifying the request state 133 func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) { 134 bufReader := bufio.NewReaderSize(body, maxBodySize) 135 newBody := ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() }) 136 137 data, err := bufReader.Peek(maxBodySize) 138 // Body size exceeds max body size 139 if err == nil { 140 logrus.Warnf("Request body is larger than: '%d' skipping body", maxBodySize) 141 return nil, newBody, nil 142 } 143 // Body size is less than maximum size 144 if err == io.EOF { 145 return data, newBody, nil 146 } 147 // Unknown error 148 return nil, newBody, err 149 } 150 151 // sendBody returns true when request/response body should be sent to AuthZPlugin 152 func sendBody(url string, header http.Header) bool { 153 // Skip body for auth endpoint 154 if strings.HasSuffix(url, "/auth") { 155 return false 156 } 157 158 // body is sent only for text or json messages 159 return header.Get("Content-Type") == "application/json" 160 } 161 162 // headers returns flatten version of the http headers excluding authorization 163 func headers(header http.Header) map[string]string { 164 v := make(map[string]string, 0) 165 for k, values := range header { 166 // Skip authorization headers 167 if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "X-Registry-Config") || strings.EqualFold(k, "X-Registry-Auth") { 168 continue 169 } 170 for _, val := range values { 171 v[k] = val 172 } 173 } 174 return v 175 } 176 177 // authorizationError represents an authorization deny error 178 type authorizationError struct { 179 error 180 } 181 182 // HTTPErrorStatusCode returns the authorization error status code (forbidden) 183 func (e authorizationError) HTTPErrorStatusCode() int { 184 return http.StatusForbidden 185 } 186 187 func newAuthorizationError(plugin, msg string) authorizationError { 188 return authorizationError{error: fmt.Errorf("authorization denied by plugin %s: %s", plugin, msg)} 189 }