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 }