mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 05:40:42 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			232 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			232 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Package api implements an HTTP-based API and server for CFSSL.
 | |
| package api
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 
 | |
| 	"github.com/cloudflare/cfssl/errors"
 | |
| 	"github.com/cloudflare/cfssl/log"
 | |
| )
 | |
| 
 | |
| // Handler is an interface providing a generic mechanism for handling HTTP requests.
 | |
| type Handler interface {
 | |
| 	Handle(w http.ResponseWriter, r *http.Request) error
 | |
| }
 | |
| 
 | |
| // HTTPHandler is a wrapper that encapsulates Handler interface as http.Handler.
 | |
| // HTTPHandler also enforces that the Handler only responds to requests with registered HTTP methods.
 | |
| type HTTPHandler struct {
 | |
| 	Handler          // CFSSL handler
 | |
| 	Methods []string // The associated HTTP methods
 | |
| }
 | |
| 
 | |
| // HandlerFunc is similar to the http.HandlerFunc type; it serves as
 | |
| // an adapter allowing the use of ordinary functions as Handlers. If
 | |
| // f is a function with the appropriate signature, HandlerFunc(f) is a
 | |
| // Handler object that calls f.
 | |
| type HandlerFunc func(http.ResponseWriter, *http.Request) error
 | |
| 
 | |
| // Handle calls f(w, r)
 | |
| func (f HandlerFunc) Handle(w http.ResponseWriter, r *http.Request) error {
 | |
| 	w.Header().Set("Content-Type", "application/json")
 | |
| 	return f(w, r)
 | |
| }
 | |
| 
 | |
| // HandleError is the centralised error handling and reporting.
 | |
| func HandleError(w http.ResponseWriter, err error) (code int) {
 | |
| 	if err == nil {
 | |
| 		return http.StatusOK
 | |
| 	}
 | |
| 	msg := err.Error()
 | |
| 	httpCode := http.StatusInternalServerError
 | |
| 
 | |
| 	// If it is recognized as HttpError emitted from cfssl,
 | |
| 	// we rewrite the status code accordingly. If it is a
 | |
| 	// cfssl error, set the http status to StatusBadRequest
 | |
| 	switch err := err.(type) {
 | |
| 	case *errors.HTTPError:
 | |
| 		httpCode = err.StatusCode
 | |
| 		code = err.StatusCode
 | |
| 	case *errors.Error:
 | |
| 		httpCode = http.StatusBadRequest
 | |
| 		code = err.ErrorCode
 | |
| 		msg = err.Message
 | |
| 	}
 | |
| 
 | |
| 	response := NewErrorResponse(msg, code)
 | |
| 	jsonMessage, err := json.Marshal(response)
 | |
| 	if err != nil {
 | |
| 		log.Errorf("Failed to marshal JSON: %v", err)
 | |
| 	} else {
 | |
| 		msg = string(jsonMessage)
 | |
| 	}
 | |
| 	http.Error(w, msg, httpCode)
 | |
| 	return code
 | |
| }
 | |
| 
 | |
| // ServeHTTP encapsulates the call to underlying Handler to handle the request
 | |
| // and return the response with proper HTTP status code
 | |
| func (h HTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | |
| 	var err error
 | |
| 	var match bool
 | |
| 	// Throw 405 when requested with an unsupported verb.
 | |
| 	for _, m := range h.Methods {
 | |
| 		if m == r.Method {
 | |
| 			match = true
 | |
| 		}
 | |
| 	}
 | |
| 	if match {
 | |
| 		err = h.Handle(w, r)
 | |
| 	} else {
 | |
| 		err = errors.NewMethodNotAllowed(r.Method)
 | |
| 	}
 | |
| 	status := HandleError(w, err)
 | |
| 	log.Infof("%s - \"%s %s\" %d", r.RemoteAddr, r.Method, r.URL, status)
 | |
| }
 | |
| 
 | |
| // readRequestBlob takes a JSON-blob-encoded response body in the form
 | |
| // map[string]string and returns it, the list of keywords presented,
 | |
| // and any error that occurred.
 | |
| func readRequestBlob(r *http.Request) (map[string]string, error) {
 | |
| 	var blob map[string]string
 | |
| 
 | |
| 	body, err := ioutil.ReadAll(r.Body)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	r.Body.Close()
 | |
| 
 | |
| 	err = json.Unmarshal(body, &blob)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return blob, nil
 | |
| }
 | |
| 
 | |
| // ProcessRequestOneOf reads a JSON blob for the request and makes
 | |
| // sure it contains one of a set of keywords. For example, a request
 | |
| // might have the ('foo' && 'bar') keys, OR it might have the 'baz'
 | |
| // key.  In either case, we want to accept the request; however, if
 | |
| // none of these sets shows up, the request is a bad request, and it
 | |
| // should be returned.
 | |
| func ProcessRequestOneOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
 | |
| 	blob, err := readRequestBlob(r)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	var matched []string
 | |
| 	for _, set := range keywordSets {
 | |
| 		if matchKeywords(blob, set) {
 | |
| 			if matched != nil {
 | |
| 				return nil, nil, errors.NewBadRequestString("mismatched parameters")
 | |
| 			}
 | |
| 			matched = set
 | |
| 		}
 | |
| 	}
 | |
| 	if matched == nil {
 | |
| 		return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
 | |
| 	}
 | |
| 	return blob, matched, nil
 | |
| }
 | |
| 
 | |
| // ProcessRequestFirstMatchOf reads a JSON blob for the request and returns
 | |
| // the first match of a set of keywords. For example, a request
 | |
| // might have one of the following combinations: (foo=1, bar=2), (foo=1), and (bar=2)
 | |
| // By giving a specific ordering of those combinations, we could decide how to accept
 | |
| // the request.
 | |
| func ProcessRequestFirstMatchOf(r *http.Request, keywordSets [][]string) (map[string]string, []string, error) {
 | |
| 	blob, err := readRequestBlob(r)
 | |
| 	if err != nil {
 | |
| 		return nil, nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, set := range keywordSets {
 | |
| 		if matchKeywords(blob, set) {
 | |
| 			return blob, set, nil
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, nil, errors.NewBadRequestString("no valid parameter sets found")
 | |
| }
 | |
| 
 | |
| func matchKeywords(blob map[string]string, keywords []string) bool {
 | |
| 	for _, keyword := range keywords {
 | |
| 		if _, ok := blob[keyword]; !ok {
 | |
| 			return false
 | |
| 		}
 | |
| 	}
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| // ResponseMessage implements the standard for response errors and
 | |
| // messages. A message has a code and a string message.
 | |
| type ResponseMessage struct {
 | |
| 	Code    int    `json:"code"`
 | |
| 	Message string `json:"message"`
 | |
| }
 | |
| 
 | |
| // Response implements the CloudFlare standard for API
 | |
| // responses.
 | |
| type Response struct {
 | |
| 	Success  bool              `json:"success"`
 | |
| 	Result   interface{}       `json:"result"`
 | |
| 	Errors   []ResponseMessage `json:"errors"`
 | |
| 	Messages []ResponseMessage `json:"messages"`
 | |
| }
 | |
| 
 | |
| // NewSuccessResponse is a shortcut for creating new successul API
 | |
| // responses.
 | |
| func NewSuccessResponse(result interface{}) Response {
 | |
| 	return Response{
 | |
| 		Success:  true,
 | |
| 		Result:   result,
 | |
| 		Errors:   []ResponseMessage{},
 | |
| 		Messages: []ResponseMessage{},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewSuccessResponseWithMessage is a shortcut for creating new successul API
 | |
| // responses that includes a message.
 | |
| func NewSuccessResponseWithMessage(result interface{}, message string, code int) Response {
 | |
| 	return Response{
 | |
| 		Success:  true,
 | |
| 		Result:   result,
 | |
| 		Errors:   []ResponseMessage{},
 | |
| 		Messages: []ResponseMessage{{code, message}},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // NewErrorResponse is a shortcut for creating an error response for a
 | |
| // single error.
 | |
| func NewErrorResponse(message string, code int) Response {
 | |
| 	return Response{
 | |
| 		Success:  false,
 | |
| 		Result:   nil,
 | |
| 		Errors:   []ResponseMessage{{code, message}},
 | |
| 		Messages: []ResponseMessage{},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SendResponse builds a response from the result, sets the JSON
 | |
| // header, and writes to the http.ResponseWriter.
 | |
| func SendResponse(w http.ResponseWriter, result interface{}) error {
 | |
| 	response := NewSuccessResponse(result)
 | |
| 	w.Header().Set("Content-Type", "application/json")
 | |
| 	enc := json.NewEncoder(w)
 | |
| 	err := enc.Encode(response)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // SendResponseWithMessage builds a response from the result and the
 | |
| // provided message, sets the JSON header, and writes to the
 | |
| // http.ResponseWriter.
 | |
| func SendResponseWithMessage(w http.ResponseWriter, result interface{}, message string, code int) error {
 | |
| 	response := NewSuccessResponseWithMessage(result, message, code)
 | |
| 	w.Header().Set("Content-Type", "application/json")
 | |
| 	enc := json.NewEncoder(w)
 | |
| 	err := enc.Encode(response)
 | |
| 	return err
 | |
| }
 |