...

Source file src/edge-infra.dev/pkg/edge/device-registrar/services/device_discovery_service.go

Documentation: edge-infra.dev/pkg/edge/device-registrar/services

     1  package services
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"strings"
     7  
     8  	"sigs.k8s.io/controller-runtime/pkg/client"
     9  
    10  	"github.com/gin-gonic/gin"
    11  
    12  	"edge-infra.dev/pkg/edge/device-registrar/config"
    13  
    14  	api "edge-infra.dev/pkg/edge/device-registrar/api/v1alpha1"
    15  )
    16  
    17  // DiscoverServiceResponse contains the name and discovery URL of the workload
    18  // swagger:model DiscoverServiceResponse
    19  type DiscoverServiceResponse struct {
    20  	// example: jarvis
    21  	Name string `json:"name"`
    22  	// example: https://jarvis.store.ncr.corp/config
    23  	URL *string `json:"url"`
    24  }
    25  
    26  // DiscoverInput contains the name of the workload
    27  // swagger:model DiscoverInput
    28  type DiscoverInput struct {
    29  	Name string `uri:"serviceName" binding:"required"`
    30  }
    31  
    32  // swagger:route GET /discover/{serviceName} additional discover
    33  // Discovers an endpoint
    34  //
    35  // Returns the name and discovery URL of the workload
    36  // parameters:
    37  //   - name: X-Client-DN
    38  //     in: header
    39  //     type: string
    40  //     required: true
    41  //     description: the header is formatted as: `OU=urn:storeID:[STORE-ID]+OU=urn:deviceName:[DEVICE-NAME]+OU=urn:SN:[SERIAL-NUMBER]`
    42  //
    43  // responses:
    44  //
    45  //	200: DiscoverServiceResponse Success
    46  //	500: description:Internal Server Error
    47  func DiscoverService(c *gin.Context) {
    48  	k8sClient, ctx, cancel := config.GetClientandContext(c)
    49  	defer cancel()
    50  
    51  	// check header exists
    52  	clientDN := c.GetHeader("X-Client-DN")
    53  	if clientDN == "" {
    54  		c.JSON(http.StatusUnauthorized, gin.H{"error": "X-Client-DN is required"})
    55  		return
    56  	}
    57  
    58  	// Parse the DN to extract the SN
    59  	serialNumber := parseSerialNumber(clientDN)
    60  	if serialNumber == "" {
    61  		c.JSON(http.StatusUnauthorized, gin.H{"error": "Serial Number is required in X-Client-DN"})
    62  		return
    63  	}
    64  
    65  	// Verify the SN against devices
    66  	if !isSNAuthorized(ctx, k8sClient, serialNumber) {
    67  		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized Serial Number"})
    68  		return
    69  	}
    70  
    71  	var serviceName DiscoverInput
    72  	if err := c.ShouldBindUri(&serviceName); err != nil {
    73  		c.JSON(http.StatusBadRequest, gin.H{"error": "serviceName is required"})
    74  		return
    75  	}
    76  
    77  	discoveryList := &api.DiscoveryList{}
    78  	err := k8sClient.List(ctx, discoveryList)
    79  	if err != nil {
    80  		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    81  		return
    82  	}
    83  
    84  	configURL := ""
    85  	for i := range discoveryList.Items {
    86  		service := discoveryList.Items[i]
    87  		if service.Spec.Name == serviceName.Name {
    88  			configURL = service.Spec.URL
    89  			break
    90  		}
    91  	}
    92  
    93  	resp := DiscoverServiceResponse{
    94  		Name: serviceName.Name,
    95  		URL:  &configURL,
    96  	}
    97  
    98  	if configURL == "" {
    99  		resp.URL = nil
   100  	}
   101  
   102  	c.JSON(http.StatusOK, resp)
   103  }
   104  
   105  // OU=urn:storeID:my-store-id+OU=urn:deviceName:test-device-name+OU=urn:SN:a1b2c3d4
   106  func parseSerialNumber(dn string) string {
   107  	parts := strings.Split(dn, "+")
   108  	for _, part := range parts {
   109  		if strings.HasPrefix(part, "OU=urn:SN:") {
   110  			return strings.TrimSpace(strings.TrimPrefix(part, "OU=urn:SN:"))
   111  		}
   112  	}
   113  	return ""
   114  }
   115  
   116  // compares the serial number against the list of devices serial numbers
   117  func isSNAuthorized(ctx context.Context, k8sClient client.Client, sn string) bool {
   118  	// get list of devices
   119  	deviceList := &api.ExternalDeviceList{}
   120  	err := k8sClient.List(ctx, deviceList)
   121  	if err != nil {
   122  		return false
   123  	}
   124  
   125  	// no serial numbers, return early
   126  	if len(deviceList.Items) == 0 {
   127  		return false
   128  	}
   129  
   130  	// check if SN is in the list of devices' serial numbers
   131  	for _, device := range deviceList.Items {
   132  		if sn == device.Spec.SN {
   133  			return true
   134  		}
   135  	}
   136  	return false
   137  }
   138  

View as plain text