authz.go 5.2 KB

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