authz.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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/engine/reference/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. if r.TLS != nil {
  71. for _, c := range r.TLS.PeerCertificates {
  72. pc := PeerCertificate(*c)
  73. ctx.authReq.RequestPeerCertificates = append(ctx.authReq.RequestPeerCertificates, &pc)
  74. }
  75. }
  76. for _, plugin := range ctx.plugins {
  77. logrus.Debugf("AuthZ request using plugin %s", plugin.Name())
  78. authRes, err := plugin.AuthZRequest(ctx.authReq)
  79. if err != nil {
  80. return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
  81. }
  82. if !authRes.Allow {
  83. return newAuthorizationError(plugin.Name(), authRes.Msg)
  84. }
  85. }
  86. return nil
  87. }
  88. // AuthZResponse authorized and manipulates the response from docker daemon using authZ plugins
  89. func (ctx *Ctx) AuthZResponse(rm ResponseModifier, r *http.Request) error {
  90. ctx.authReq.ResponseStatusCode = rm.StatusCode()
  91. ctx.authReq.ResponseHeaders = headers(rm.Header())
  92. if sendBody(ctx.requestURI, rm.Header()) {
  93. ctx.authReq.ResponseBody = rm.RawBody()
  94. }
  95. for _, plugin := range ctx.plugins {
  96. logrus.Debugf("AuthZ response using plugin %s", plugin.Name())
  97. authRes, err := plugin.AuthZResponse(ctx.authReq)
  98. if err != nil {
  99. return fmt.Errorf("plugin %s failed with error: %s", plugin.Name(), err)
  100. }
  101. if !authRes.Allow {
  102. return newAuthorizationError(plugin.Name(), authRes.Msg)
  103. }
  104. }
  105. rm.FlushAll()
  106. return nil
  107. }
  108. // drainBody dump the body (if its length is less than 1MB) without modifying the request state
  109. func drainBody(body io.ReadCloser) ([]byte, io.ReadCloser, error) {
  110. bufReader := bufio.NewReaderSize(body, maxBodySize)
  111. newBody := ioutils.NewReadCloserWrapper(bufReader, func() error { return body.Close() })
  112. data, err := bufReader.Peek(maxBodySize)
  113. // Body size exceeds max body size
  114. if err == nil {
  115. logrus.Warnf("Request body is larger than: '%d' skipping body", maxBodySize)
  116. return nil, newBody, nil
  117. }
  118. // Body size is less than maximum size
  119. if err == io.EOF {
  120. return data, newBody, nil
  121. }
  122. // Unknown error
  123. return nil, newBody, err
  124. }
  125. // sendBody returns true when request/response body should be sent to AuthZPlugin
  126. func sendBody(url string, header http.Header) bool {
  127. // Skip body for auth endpoint
  128. if strings.HasSuffix(url, "/auth") {
  129. return false
  130. }
  131. // body is sent only for text or json messages
  132. return header.Get("Content-Type") == "application/json"
  133. }
  134. // headers returns flatten version of the http headers excluding authorization
  135. func headers(header http.Header) map[string]string {
  136. v := make(map[string]string, 0)
  137. for k, values := range header {
  138. // Skip authorization headers
  139. if strings.EqualFold(k, "Authorization") || strings.EqualFold(k, "X-Registry-Config") || strings.EqualFold(k, "X-Registry-Auth") {
  140. continue
  141. }
  142. for _, val := range values {
  143. v[k] = val
  144. }
  145. }
  146. return v
  147. }
  148. // authorizationError represents an authorization deny error
  149. type authorizationError struct {
  150. error
  151. }
  152. // HTTPErrorStatusCode returns the authorization error status code (forbidden)
  153. func (e authorizationError) HTTPErrorStatusCode() int {
  154. return http.StatusForbidden
  155. }
  156. func newAuthorizationError(plugin, msg string) authorizationError {
  157. return authorizationError{error: fmt.Errorf("authorization denied by plugin %s: %s", plugin, msg)}
  158. }