authz.go 5.5 KB

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