@@ -376,6 +376,117 @@ func (c *Client) DoUpdate(ctx context.Context, creds keys.Credentials) ([]byte,
376376 return result .Config , nebulaPrivkeyPEM , newCreds , meta , nil
377377}
378378
379+ // DoConfigUpdate sends a signed message to the DNClient API to fetch the new configuration update. During this call new keys
380+ // are generated for DNClient API communication. If the API response is successful, the new configuration
381+ // is returned along with the new DNClient API credentials and a meta object.
382+ //
383+ // See dnapi.InsertConfigPrivateKey and dnapi.InsertConfigCert for how to insert the old Nebula cert/private key into the configuration.
384+ func (c * Client ) DoConfigUpdate (ctx context.Context , creds keys.Credentials ) ([]byte , * keys.Credentials , * ConfigMeta , error ) {
385+ // Rotate key
386+ var hostPrivkey keys.PrivateKey // ECDSA
387+
388+ newKeys , err := keys .New ()
389+ if err != nil {
390+ return nil , nil , nil , fmt .Errorf ("failed to generate new keys: %s" , err )
391+ }
392+
393+ msg := message.DoConfigUpdateRequest {
394+ Nonce : nonce (),
395+ }
396+
397+ // Set the correct keypair based on the current private key type
398+ switch creds .PrivateKey .Unwrap ().(type ) {
399+ case ed25519.PrivateKey :
400+ hostPubkeyPEM , err := newKeys .HostEd25519PublicKey .MarshalPEM ()
401+ if err != nil {
402+ return nil , nil , nil , fmt .Errorf ("failed to marshal Ed25519 public key: %s" , err )
403+ }
404+ hostPrivkey = newKeys .HostEd25519PrivateKey
405+ msg .HostPubkeyEd25519 = hostPubkeyPEM
406+ case * ecdsa.PrivateKey :
407+ hostPubkeyPEM , err := newKeys .HostP256PublicKey .MarshalPEM ()
408+ if err != nil {
409+ return nil , nil , nil , fmt .Errorf ("failed to marshal P256 public key: %s" , err )
410+ }
411+ hostPrivkey = newKeys .HostP256PrivateKey
412+ msg .HostPubkeyP256 = hostPubkeyPEM
413+ }
414+
415+ blob , err := json .Marshal (msg )
416+ if err != nil {
417+ return nil , nil , nil , fmt .Errorf ("failed to marshal DNClient message: %s" , err )
418+ }
419+
420+ // Make API call
421+ resp , err := c .postDNClient (ctx , message .DoConfigUpdate , blob , creds .HostID , creds .Counter , creds .PrivateKey )
422+ if err != nil {
423+ return nil , nil , nil , fmt .Errorf ("failed to make API call to Defined Networking: %w" , err )
424+ }
425+ resultWrapper := message.SignedResponseWrapper {}
426+ err = json .Unmarshal (resp , & resultWrapper )
427+ if err != nil {
428+ return nil , nil , nil , fmt .Errorf ("failed to unmarshal signed response wrapper: %s" , err )
429+ }
430+
431+ // Verify the signature
432+ valid := false
433+ for _ , caPubkey := range creds .TrustedKeys {
434+ if caPubkey .Verify (resultWrapper .Data .Message , resultWrapper .Data .Signature ) {
435+ valid = true
436+ break
437+ }
438+ }
439+ if ! valid {
440+ return nil , nil , nil , fmt .Errorf ("failed to verify signed API result" )
441+ }
442+
443+ // Consume the verified message
444+ result := message.DoConfigUpdateResponse {}
445+ err = json .Unmarshal (resultWrapper .Data .Message , & result )
446+ if err != nil {
447+ return nil , nil , nil , fmt .Errorf ("failed to unmarshal response (%s): %s" , resultWrapper .Data .Message , err )
448+ }
449+
450+ // Verify the nonce
451+ if ! bytes .Equal (result .Nonce , msg .Nonce ) {
452+ return nil , nil , nil , fmt .Errorf ("nonce mismatch between request (%s) and response (%s)" , msg .Nonce , result .Nonce )
453+ }
454+
455+ // Verify the counter
456+ if result .Counter <= creds .Counter {
457+ return nil , nil , nil , fmt .Errorf ("counter in request (%d) should be less than counter in response (%d)" , creds .Counter , result .Counter )
458+ }
459+
460+ trustedKeys , err := keys .TrustedKeysFromPEM (result .TrustedKeys )
461+ if err != nil {
462+ return nil , nil , nil , fmt .Errorf ("failed to load trusted keys from bundle: %s" , err )
463+ }
464+
465+ newCreds := & keys.Credentials {
466+ HostID : creds .HostID ,
467+ Counter : result .Counter ,
468+ PrivateKey : hostPrivkey ,
469+ TrustedKeys : trustedKeys ,
470+ }
471+
472+ meta := & ConfigMeta {
473+ Org : ConfigOrg {
474+ ID : result .Organization .ID ,
475+ Name : result .Organization .Name ,
476+ },
477+ Network : ConfigNetwork {
478+ ID : result .Network .ID ,
479+ Name : result .Network .Name ,
480+ },
481+ Host : ConfigHost {
482+ ID : result .Host .ID ,
483+ Name : result .Host .Name ,
484+ IPAddress : result .Host .IPAddress ,
485+ },
486+ }
487+
488+ return result .Config , newCreds , meta , nil
489+ }
379490func (c * Client ) CommandResponse (ctx context.Context , creds keys.Credentials , responseToken string , response any ) error {
380491 value , err := json .Marshal (message.CommandResponseRequest {
381492 ResponseToken : responseToken ,
0 commit comments