...

Source file src/edge-infra.dev/pkg/edge/datasync/magpie/server.go

Documentation: edge-infra.dev/pkg/edge/datasync/magpie

     1  package magpie
     2  
     3  import (
     4  	"context"
     5  	"crypto/rsa"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  
    10  	"github.com/gin-contrib/cors"
    11  	"github.com/gin-contrib/requestid"
    12  	"github.com/gin-gonic/gin"
    13  	"github.com/go-logr/logr"
    14  	"sigs.k8s.io/controller-runtime/pkg/client"
    15  
    16  	"edge-infra.dev/pkg/edge/edgeencrypt"
    17  	"edge-infra.dev/pkg/lib/fog"
    18  )
    19  
    20  const (
    21  	DecryptionBodySize = 3 // todo calculate this better
    22  	ChannelParam       = "channel"
    23  )
    24  
    25  type Server struct {
    26  	cfg     *Config
    27  	router  *gin.Engine
    28  	metric  *gin.Engine
    29  	client  client.Client
    30  	decrypt edgeencrypt.DecryptFunc
    31  }
    32  
    33  func NewDecryptionServer(cfg *Config, cl client.Client, log logr.Logger, decrypt edgeencrypt.DecryptFunc) *Server {
    34  	router := gin.New()
    35  	router.ContextWithFallback = true
    36  
    37  	metric := edgeencrypt.MetricServer(router)
    38  
    39  	router.Use(requestid.New(requestid.WithCustomHeaderStrKey(edgeencrypt.CorrelationIDKey)))
    40  	router.Use(edgeencrypt.RequestLogger(log))
    41  	router.Use(gin.Recovery())
    42  
    43  	// Add CORS middleware around every request
    44  	corsConfig := cors.DefaultConfig()
    45  	corsConfig.AllowAllOrigins = true
    46  	corsConfig.AllowHeaders = []string{"*"}
    47  	router.Use(cors.New(corsConfig))
    48  
    49  	router.Use(edgeencrypt.MaxRequestBodySize(edgeencrypt.EncryptionDefaultBodySize * DecryptionBodySize))
    50  
    51  	server := &Server{
    52  		cfg:     cfg,
    53  		router:  router,
    54  		metric:  metric,
    55  		decrypt: decrypt,
    56  		client:  cl,
    57  	}
    58  
    59  	router.GET("/health", func(c *gin.Context) {
    60  		c.String(http.StatusOK, "ok")
    61  	})
    62  
    63  	router.GET("/ready", func(c *gin.Context) {
    64  		if _, err := edgeencrypt.GetPublicKey(c, cl, cfg.Namespace, cfg.JWTSecret); err != nil {
    65  			_ = c.Error(fmt.Errorf("failed to get decryption public key: %w", err))
    66  			c.AbortWithStatus(http.StatusServiceUnavailable)
    67  			return
    68  		}
    69  		c.String(http.StatusOK, "ok")
    70  	})
    71  
    72  	router.Use(edgeencrypt.BearerToken(server.GetPublicKey, edgeencrypt.Decryption))
    73  
    74  	encrypt := router.Group("/v1/decrypt/:" + ChannelParam)
    75  	{
    76  		encrypt.POST("", server.decryptHandler)
    77  	}
    78  
    79  	router.NoRoute(func(c *gin.Context) {
    80  		c.AbortWithStatus(404)
    81  	})
    82  
    83  	return server
    84  }
    85  
    86  func (s *Server) Start(address, metricAddress string) error {
    87  	go func() {
    88  		if err := s.metric.Run(metricAddress); err != nil {
    89  			panic(fmt.Errorf("failed to start metric server: %w", err))
    90  		}
    91  	}()
    92  	return s.router.Run(address)
    93  }
    94  
    95  func (s *Server) decryptHandler(c *gin.Context) {
    96  	ctx := c.Request.Context()
    97  	log := fog.FromContext(ctx)
    98  
    99  	claims := edgeencrypt.ClaimsFromContext(ctx)
   100  	if claims == nil {
   101  		_ = c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("claims not found in context"))
   102  		return
   103  	}
   104  
   105  	data, err := io.ReadAll(c.Request.Body)
   106  	if err != nil {
   107  		_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("failed to read request body: %w", err))
   108  		return
   109  	}
   110  
   111  	if len(data) == 0 {
   112  		_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("body is empty"))
   113  		return
   114  	}
   115  
   116  	e := &edgeencrypt.EncryptedData{}
   117  	if err = e.FromDecryptionRequest(data); err != nil {
   118  		_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("failed to unmarshal encrypted data: %w", err))
   119  		return
   120  	}
   121  
   122  	if err = e.Valid(); err != nil {
   123  		_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid encrypted data: %w", err))
   124  		return
   125  	}
   126  
   127  	channel := c.Param(ChannelParam)
   128  	if e.Channel != channel {
   129  		_ = c.AbortWithError(http.StatusUnauthorized, fmt.Errorf("invalid channel for encrypted data"))
   130  		return
   131  	}
   132  
   133  	res, err := edgeencrypt.DecryptData(ctx, e, claims, s.decrypt)
   134  	if err != nil {
   135  		_ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("failed to decrypt data: %w", err))
   136  		return
   137  	}
   138  
   139  	log.Info("successfully decrypted data")
   140  
   141  	c.Data(http.StatusOK, gin.MIMEPlain, res)
   142  }
   143  
   144  func (s *Server) GetPublicKey(ctx context.Context) (*rsa.PublicKey, error) {
   145  	pk, err := edgeencrypt.GetPublicKey(ctx, s.client, s.cfg.Namespace, s.cfg.JWTSecret)
   146  	if err != nil {
   147  		return nil, fmt.Errorf("failed to get decryption jwt public key: %w", err)
   148  	}
   149  	return pk.ToRSAPublicKey()
   150  }
   151  
   152  // swagger:route POST /v1/decrypt/{channel} PostDecrypt
   153  // Edge Decryption Service
   154  // consumes:
   155  // - text/plain
   156  // produces:
   157  // - text/plain
   158  // parameters:
   159  // + name: Channel
   160  //   in: path
   161  //   description: The encryption channel
   162  //   required: true
   163  //   schema:
   164  //     type: string
   165  // + name: Authorization
   166  //   in: header
   167  //   description: Bearer token for authentication
   168  //   required: true
   169  //   type:string
   170  //   pattern: ^Bearer\s[\W-.]+$
   171  // + name: Body
   172  //   in: body
   173  //   description: The data to decrypt
   174  //   required: true
   175  //   schema:
   176  //     type: string
   177  //
   178  //Responses:
   179  //   200: decryptedTextResponse
   180  //   400: description:Bad Request
   181  //   401: description:Invalid Credentials
   182  //   404: description:Path Not Found
   183  
   184  // The Decrypted data
   185  // swagger:response decryptedTextResponse
   186  type decryptedTextResponse struct { //nolint:unused
   187  	// The Decrypted data
   188  	// in:body
   189  	Body string
   190  }
   191  

View as plain text