@@ -94,6 +94,7 @@ type Router struct {
9494 biscuitExpiration time.Time
9595 trustedPublicKeys []ed25519.PublicKey
9696 keysMu sync.RWMutex
97+ enrollMu sync.Mutex
9798 privKey crypto.PrivKey
9899
99100 // Control contexts
@@ -320,9 +321,9 @@ func (r *Router) enroll(peerID peer.ID) error {
320321 return err
321322 }
322323
324+ r .keysMu .Lock ()
323325 r .biscuitToken = enrollResp .BiscuitToken
324326 r .biscuitExpiration = time .Unix (enrollResp .Expiration , 0 )
325- r .keysMu .Lock ()
326327 r .trustedPublicKeys = []ed25519.PublicKey {enrollResp .HubPublicKey }
327328 r .keysMu .Unlock ()
328329
@@ -442,9 +443,9 @@ func (r *Router) enrollBootstrap(peerID peer.ID) error {
442443 return fmt .Errorf ("enrollment not approved (status: %v)" , enrollResp .Status )
443444 }
444445
446+ r .keysMu .Lock ()
445447 r .biscuitToken = enrollResp .BiscuitToken
446448 r .biscuitExpiration = time .Unix (enrollResp .Expiration , 0 )
447- r .keysMu .Lock ()
448449 r .trustedPublicKeys = []ed25519.PublicKey {enrollResp .HubPublicKey }
449450 r .keysMu .Unlock ()
450451
@@ -456,6 +457,22 @@ func (r *Router) enrollBootstrap(peerID peer.ID) error {
456457 return nil
457458}
458459
460+ func (r * Router ) reEnroll () error {
461+ r .enrollMu .Lock ()
462+ defer r .enrollMu .Unlock ()
463+
464+ if r .Host == nil {
465+ return fmt .Errorf ("cannot re-enroll: router host is not initialized" )
466+ }
467+
468+ if r .config .BootstrapToken != "" {
469+ return r .enrollBootstrap (r .Host .ID ())
470+ } else if r .config .OIDCToken != "" {
471+ return r .enroll (r .Host .ID ())
472+ }
473+ return fmt .Errorf ("no enrollment token available for re-enrollment" )
474+ }
475+
459476func (r * Router ) syncKeys () error {
460477 client := & http.Client {Timeout : 10 * time .Second }
461478 resp , err := client .Get (r .config .ControlPlaneURL + "/keys" )
@@ -534,7 +551,11 @@ func (r *Router) runLeaseRenewalLoop() {
534551}
535552
536553func (r * Router ) renewLease () {
537- if len (r .biscuitToken ) == 0 {
554+ r .keysMu .RLock ()
555+ biscuit := r .biscuitToken
556+ r .keysMu .RUnlock ()
557+
558+ if len (biscuit ) == 0 {
538559 logger .Warn ("Cannot renew lease: router is not enrolled (no biscuit)" )
539560 return
540561 }
@@ -564,7 +585,7 @@ func (r *Router) renewLease() {
564585 req := & api.RouterLeaseRequest {
565586 PeerId : r .Host .ID ().String (),
566587 Addresses : addrs ,
567- Biscuit : r . biscuitToken ,
588+ Biscuit : biscuit ,
568589 ConnectedPeers : connectedPeers ,
569590 DhtSize : dhtSize ,
570591 }
@@ -578,6 +599,18 @@ func (r *Router) renewLease() {
578599 }
579600 defer func () { _ = resp .Body .Close () }()
580601
602+ if resp .StatusCode == http .StatusUnauthorized {
603+ body , _ := io .ReadAll (resp .Body )
604+ logger .Warnf ("Control plane lease renewal rejected (401 Unauthorized: %s), attempting re-enrollment..." , string (body ))
605+ if err := r .reEnroll (); err != nil {
606+ logger .Errorf ("Re-enrollment failed after 401 Unauthorized lease renewal: %v" , err )
607+ return
608+ }
609+ logger .Info ("Successfully re-enrolled after 401 Unauthorized lease renewal, retrying lease renewal..." )
610+ r .renewLease ()
611+ return
612+ }
613+
581614 if resp .StatusCode != http .StatusOK {
582615 body , _ := io .ReadAll (resp .Body )
583616 logger .Errorf ("Control plane lease renewal rejected, status %s: %s" , resp .Status , string (body ))
@@ -718,10 +751,14 @@ func (r *Router) HandleAuthHandshake(s network.Stream) {
718751 logger .Infof ("[AuthN] Successfully authenticated peer %s" , remotePeer )
719752
720753 // Send mutual response (our biscuit)
754+ r .keysMu .RLock ()
755+ ourBiscuit := r .biscuitToken
756+ r .keysMu .RUnlock ()
757+
721758 writer := msgio .NewVarintWriter (s )
722759 resp := & api.AuthResponse {
723760 Success : true ,
724- Biscuit : r . biscuitToken ,
761+ Biscuit : ourBiscuit ,
725762 }
726763 respBytes , _ := proto .Marshal (resp )
727764 if err := writer .WriteMsg (respBytes ); err != nil {
@@ -859,10 +896,11 @@ func isLoopbackOrLinkLocal(addr multiaddr.Multiaddr) bool {
859896
860897// RefreshEnrollment trades the expiring biscuit token for a new one using a cryptographic challenge.
861898func (r * Router ) RefreshEnrollment (ctx context.Context ) error {
862- r .keysMu .Lock ()
863- defer r .keysMu .Unlock ()
899+ r .keysMu .RLock ()
900+ currentBiscuit := r .biscuitToken
901+ r .keysMu .RUnlock ()
864902
865- if len (r . biscuitToken ) == 0 {
903+ if len (currentBiscuit ) == 0 {
866904 return fmt .Errorf ("router not enrolled (no biscuit)" )
867905 }
868906
@@ -890,8 +928,7 @@ func (r *Router) RefreshEnrollment(ctx context.Context) error {
890928 return fmt .Errorf ("failed to create http request: %w" , err )
891929 }
892930 httpReq .Header .Set ("Content-Type" , "application/x-protobuf" )
893- // Set current biscuit in authorization header
894- b64Biscuit := base64 .StdEncoding .EncodeToString (r .biscuitToken )
931+ b64Biscuit := base64 .StdEncoding .EncodeToString (currentBiscuit )
895932 httpReq .Header .Set ("Authorization" , "Bearer " + b64Biscuit )
896933
897934 client := & http.Client {Timeout : 10 * time .Second }
@@ -901,6 +938,12 @@ func (r *Router) RefreshEnrollment(ctx context.Context) error {
901938 }
902939 defer func () { _ = resp .Body .Close () }()
903940
941+ if resp .StatusCode == http .StatusUnauthorized {
942+ body , _ := io .ReadAll (resp .Body )
943+ logger .Warnf ("Proactive biscuit refresh rejected (401 Unauthorized: %s), attempting re-enrollment..." , string (body ))
944+ return r .reEnroll ()
945+ }
946+
904947 if resp .StatusCode == http .StatusForbidden {
905948 logger .Errorf ("Refresh rejected: Router is banned (403 Forbidden). Hard-killing router." )
906949 if r .Host != nil {
@@ -928,9 +971,12 @@ func (r *Router) RefreshEnrollment(ctx context.Context) error {
928971 return fmt .Errorf ("refresh error: %s" , refreshResp .ErrorMessage )
929972 }
930973
931- // Update local biscuit token and expiration
974+ // Update local biscuit token and expiration under lock
975+ r .keysMu .Lock ()
932976 r .biscuitToken = refreshResp .BiscuitToken
933977 r .biscuitExpiration = time .Unix (refreshResp .ExpiresAt , 0 )
978+ r .keysMu .Unlock ()
979+
934980 logger .Infof ("Router biscuit token refreshed successfully." )
935981 return nil
936982}
0 commit comments