...

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

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

     1  // Device Registrar
     2  //
     3  // API documentation for the device-registrar service.
     4  //
     5  // swagger:meta
     6  package services
     7  
     8  import (
     9  	"context"
    10  	"net/http"
    11  
    12  	api "edge-infra.dev/pkg/edge/device-registrar/api/v1alpha1"
    13  	"edge-infra.dev/pkg/edge/device-registrar/config"
    14  	"edge-infra.dev/pkg/lib/crypto"
    15  
    16  	"github.com/gin-gonic/gin"
    17  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  	"k8s.io/apimachinery/pkg/types"
    20  
    21  	"sigs.k8s.io/controller-runtime/pkg/client"
    22  )
    23  
    24  // Register Device API initiates the creation of device specific data, including short-lived activation code,
    25  // edge ID client configuration and TLS client certificate/key pairs.
    26  // The activation code can be retrieved when the Registration request completes.
    27  
    28  // swagger:model DeviceRegistrationResponse
    29  // success response after registering a device
    30  type DeviceRegistrationResponse struct {
    31  	// required: true
    32  	// description: bootstrap URL for the device
    33  	// example: https://edge.store.ncr.corp/activation-code
    34  	BootstrapURL string `json:"bootstrapURL"`
    35  	// HostMapping value
    36  	// required: true
    37  	// description: host mapping for the device
    38  	HostMapping HostMapping `json:"hostMapping"`
    39  }
    40  
    41  // swagger:model ListApplicationsResponse
    42  // success response after listing all available external applications
    43  type ListApplicationsResponse struct {
    44  	// required: true
    45  	// description: unique identifier for the external application
    46  	// example: 123e4567-e89b-12d3-a456-426614174000
    47  	ID string `json:"id"`
    48  	// required: true
    49  	// description: name of the external application
    50  	// example: connected-associates
    51  	Name string `json:"name"`
    52  	// required: true
    53  	// description: description of the external application
    54  	// example: This application is used to manage connected associates
    55  	Description string `json:"description"`
    56  }
    57  
    58  // swagger:model HostMapping
    59  type HostMapping struct {
    60  	// required: true
    61  	// description: base bootstrap url for the device
    62  	// example: edge-bootstrap.store.ncr.corp
    63  	Host string `json:"host"`
    64  	// required: true
    65  	// description: cluster VIP address
    66  	// example: 10.10.11.2
    67  	VIP string `json:"vip"`
    68  }
    69  
    70  // swagger:route GET /applications additional ListApplications
    71  // Lists external applications
    72  //
    73  // Lists all available external application names.
    74  // responses:
    75  //
    76  //	200: []ListApplicationsResponse Success
    77  //	500: description:Internal Server Error
    78  func ListApplications(c *gin.Context) {
    79  	k8sClient, ctx, cancel := config.GetClientandContext(c)
    80  	defer cancel()
    81  
    82  	externalApplications := &api.ExternalApplicationList{}
    83  	err := k8sClient.List(ctx, externalApplications, client.InNamespace(config.Namespace))
    84  	if err != nil {
    85  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve applications: " + err.Error()})
    86  		return
    87  	}
    88  
    89  	listApplicationsResponse := []ListApplicationsResponse{}
    90  	for _, app := range externalApplications.Items {
    91  		listApplicationsResponse = append(listApplicationsResponse, ListApplicationsResponse{
    92  			ID:          app.Spec.ID,
    93  			Name:        app.GetName(),
    94  			Description: app.Spec.Description,
    95  		})
    96  	}
    97  
    98  	c.JSON(http.StatusOK, listApplicationsResponse)
    99  }
   100  
   101  // swagger:route POST /register legacy RegisterDevice
   102  // Registers a device
   103  //
   104  // Register Device API initiates the creation of device specific data, including short-lived activation code, edge ID client configuration and TLS client certificate/key pairs. The activation code can be retrieved when the registration request completes.
   105  //
   106  // parameters: RegisterDeviceRequest
   107  // responses:
   108  //
   109  //	200: DeviceRegistrationResponse Success
   110  //	400: description:Bad Request
   111  //	500: description:Internal Server Error
   112  func RegisterDevice(c *gin.Context) {
   113  	req, err := checkRequest(c)
   114  	if err != nil {
   115  		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
   116  		return
   117  	}
   118  
   119  	k8sClient, ctx, cancel := config.GetClientandContext(c)
   120  	defer cancel()
   121  
   122  	// 1. get/create the device
   123  	device, err := createDevice(ctx, k8sClient, req)
   124  	if err != nil {
   125  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create external device: " + err.Error()})
   126  		return
   127  	}
   128  
   129  	// step 2: loop through applicationIDs to create application and construct deviceBinding
   130  	applicationSubjects, extApps, err := getAppSubjectsAndExternalApps(ctx, k8sClient, c, req)
   131  	if err != nil {
   132  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get External Application: " + err.Error()})
   133  		return
   134  	}
   135  
   136  	// step 3: create DeviceBinding
   137  	deviceBindingName := device.GetName() + "-device-binding"
   138  	deviceBinding := &api.DeviceBinding{
   139  		ObjectMeta: metav1.ObjectMeta{
   140  			Name:      deviceBindingName,
   141  			Namespace: device.GetNamespace(),
   142  			OwnerReferences: []metav1.OwnerReference{
   143  				{
   144  					APIVersion: api.GroupVersion.String(),
   145  					Kind:       "ExternalDevice",
   146  					Name:       device.GetName(),
   147  					UID:        device.GetUID(),
   148  				},
   149  			},
   150  		},
   151  		Spec: api.DeviceBindingSpec{
   152  			Device: api.DeviceSubject{
   153  				APIGroup: api.GroupVersion.Group,
   154  				Kind:     "ExternalDevice",
   155  				Name:     device.GetName(),
   156  			},
   157  			Applications: applicationSubjects,
   158  		},
   159  	}
   160  
   161  	if err := k8sClient.Create(ctx, deviceBinding); err != nil && !k8serrors.IsAlreadyExists(err) {
   162  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create device binding: " + err.Error()})
   163  		return
   164  	}
   165  
   166  	// retrieve the deviceBinding
   167  	if err := k8sClient.Get(ctx, types.NamespacedName{
   168  		Name:      deviceBindingName,
   169  		Namespace: device.GetNamespace(),
   170  	}, deviceBinding); err != nil {
   171  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get device binding: " + err.Error()})
   172  		return
   173  	}
   174  
   175  	// step 4: loop through and create EdgeID client
   176  	for _, extApp := range extApps {
   177  		client := createClient(device, extApp, deviceBinding)
   178  
   179  		// override the secret name to make it unique per registration
   180  		client.Spec.SecretName = device.GetName() + "-" + extApp.GetName() + "-secret"
   181  		if err := k8sClient.Create(ctx, client); err != nil && !k8serrors.IsAlreadyExists(err) {
   182  			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create edgeID client: " + err.Error()})
   183  			return
   184  		}
   185  	}
   186  
   187  	// step 5: create ActivationCode
   188  	// Generate new activation code
   189  	ac, err := generateActivationCode()
   190  	if err != nil {
   191  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create activation code: " + err.Error()})
   192  		return
   193  	}
   194  	deviceBinding.Status.ActivationCode = ac.Plain()
   195  	deviceBinding.Status.Timestamp = metav1.Now()
   196  
   197  	err = k8sClient.Status().Update(ctx, deviceBinding)
   198  	if err != nil {
   199  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update deviceBinding status: " + err.Error()})
   200  		return
   201  	}
   202  
   203  	storeID := ""
   204  	if storeID, err = config.GetStoreID(ctx, k8sClient); err != nil {
   205  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve edge-info: " + err.Error()})
   206  		return
   207  	}
   208  
   209  	err = k8sClient.Get(ctx, types.NamespacedName{
   210  		Name:      device.GetName(),
   211  		Namespace: config.Namespace,
   212  	}, device)
   213  	if err != nil {
   214  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve external device: " + err.Error()})
   215  		return
   216  	}
   217  
   218  	cert := createCert(device, storeID)
   219  	if err := k8sClient.Create(ctx, cert); err != nil {
   220  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create client certificate: " + err.Error()})
   221  		return
   222  	}
   223  
   224  	vip, err := config.GetVIP(ctx, k8sClient)
   225  	if err != nil {
   226  		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to retrieve VIP address: " + err.Error()})
   227  		return
   228  	}
   229  
   230  	resp := DeviceRegistrationResponse{
   231  		BootstrapURL: config.BootstrapURL + "/" + deviceBinding.Status.ActivationCode,
   232  		HostMapping: HostMapping{
   233  			Host: config.BootstrapHost,
   234  			VIP:  vip,
   235  		},
   236  	}
   237  	c.JSON(http.StatusOK, resp)
   238  }
   239  
   240  func getExternalApplicationByID(ctx context.Context, c *gin.Context, k8sClient client.Client, appID string) (*api.ExternalApplication, error) {
   241  	applications := &api.ExternalApplicationList{}
   242  	err := k8sClient.List(ctx, applications, client.InNamespace(config.Namespace))
   243  	if err != nil {
   244  		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
   245  		return nil, err
   246  	}
   247  	for _, app := range applications.Items {
   248  		if app.Spec.ID == appID {
   249  			//found
   250  			return &app, nil
   251  		}
   252  	}
   253  
   254  	// not found
   255  	return nil, k8serrors.NewNotFound(api.GroupVersion.WithResource("externalapplications").GroupResource(), appID)
   256  }
   257  
   258  func generateActivationCode() (crypto.Credential, error) {
   259  	ac, err := crypto.GenerateRandomActivationCode()
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	return ac, nil
   264  }
   265  

View as plain text