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