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 }