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  }