@@ -67,6 +67,11 @@ func (e *kvEntry) namespacedKey() string {
6767	return  ns 
6868}
6969
70+ // privacyPairs is a type alias for a map that holds the privacy pairs, where 
71+ // the outer key is the group ID, and the value is a map of real to pseudo 
72+ // values. 
73+ type  privacyPairs  =  map [int64 ]map [string ]string 
74+ 
7075// MigrateFirewallDBToSQL runs the migration of the firwalldb stores from the 
7176// bbolt database to a SQL database. The migration is done in a single 
7277// transaction to ensure that all rows in the stores are migrated or none at 
@@ -84,10 +89,14 @@ func MigrateFirewallDBToSQL(ctx context.Context, kvStore *bbolt.DB,
8489		return  err 
8590	}
8691
92+ 	err  =  migratePrivacyMapperDBToSQL (ctx , kvStore , sqlTx )
93+ 	if  err  !=  nil  {
94+ 		return  err 
95+ 	}
96+ 
8797	log .Infof ("The rules DB has been migrated from KV to SQL." )
8898
89- 	// TODO(viktor): Add migration for the privacy mapper and the action 
90- 	// stores. 
99+ 	// TODO(viktor): Add migration for the action stores. 
91100
92101	return  nil 
93102}
@@ -487,3 +496,272 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
487496		return  fmt .Errorf ("unexpected key found: %s" , key )
488497	})
489498}
499+ 
500+ func  migratePrivacyMapperDBToSQL (ctx  context.Context , kvStore  * bbolt.DB ,
501+ 	sqlTx  SQLQueries ) error  {
502+ 
503+ 	log .Infof ("Starting migration of the privacy mapper store to SQL" )
504+ 
505+ 	// 1) Collect all privacy pairs from the KV store. 
506+ 	privPairs , err  :=  collectPrivacyPairs (ctx , kvStore , sqlTx )
507+ 	if  err  !=  nil  {
508+ 		return  fmt .Errorf ("error migrating privacy mapper store: %w" ,
509+ 			err )
510+ 	}
511+ 
512+ 	// 2) Insert all collected privacy pairs into the SQL database. 
513+ 	err  =  insertPrivacyPairs (ctx , sqlTx , privPairs )
514+ 	if  err  !=  nil  {
515+ 		return  fmt .Errorf ("insertion of privacy pairs failed: %w" , err )
516+ 	}
517+ 
518+ 	// 3) Validate that all inserted privacy pairs match the original values 
519+ 	// in the KV store. Note that this is done after all values have been 
520+ 	// inserted, to ensure that the migration doesn't overwrite any values 
521+ 	// after they were inserted. 
522+ 	err  =  validatePrivacyPairsMigration (ctx , sqlTx , privPairs )
523+ 	if  err  !=  nil  {
524+ 		return  fmt .Errorf ("migration validation of privacy pairs " + 
525+ 			"failed: %w" , err )
526+ 	}
527+ 
528+ 	log .Infof ("Migration of the privacy mapper stores to SQL completed. " + 
529+ 		"Total number of rows migrated: %d" , len (privPairs ))
530+ 	return  nil 
531+ }
532+ 
533+ // collectPrivacyPairs collects all privacy pairs from the KV store. 
534+ func  collectPrivacyPairs (ctx  context.Context , kvStore  * bbolt.DB ,
535+ 	sqlTx  SQLQueries ) (privacyPairs , error ) {
536+ 
537+ 	groupPairs  :=  make (privacyPairs )
538+ 
539+ 	return  groupPairs , kvStore .View (func (kvTx  * bbolt.Tx ) error  {
540+ 		bkt  :=  kvTx .Bucket (privacyBucketKey )
541+ 		if  bkt  ==  nil  {
542+ 			// If we haven't generated any privacy bucket yet, 
543+ 			// we can skip the migration, as there are no privacy 
544+ 			// pairs to migrate. 
545+ 			return  nil 
546+ 		}
547+ 
548+ 		return  bkt .ForEach (func (groupId , v  []byte ) error  {
549+ 			if  v  !=  nil  {
550+ 				return  fmt .Errorf ("expected only buckets " + 
551+ 					"under %s bkt, but found value %s" ,
552+ 					privacyBucketKey , v )
553+ 			}
554+ 
555+ 			gBkt  :=  bkt .Bucket (groupId )
556+ 			if  gBkt  ==  nil  {
557+ 				return  fmt .Errorf ("group bkt for group id " + 
558+ 					"%s not found" , groupId )
559+ 			}
560+ 
561+ 			groupSqlId , err  :=  sqlTx .GetSessionIDByAlias (
562+ 				ctx , groupId ,
563+ 			)
564+ 			if  errors .Is (err , sql .ErrNoRows ) {
565+ 				return  fmt .Errorf ("session with group id %x " + 
566+ 					"not found in sql db" , groupId )
567+ 			} else  if  err  !=  nil  {
568+ 				return  err 
569+ 			}
570+ 
571+ 			groupRealToPseudoPairs , err  :=  collectGroupPairs (gBkt )
572+ 			if  err  !=  nil  {
573+ 				return  fmt .Errorf ("processing group bkt " + 
574+ 					"for group id %s (sqlID %d) failed: %w" ,
575+ 					groupId , groupSqlId , err )
576+ 			}
577+ 
578+ 			groupPairs [groupSqlId ] =  groupRealToPseudoPairs 
579+ 
580+ 			return  nil 
581+ 		})
582+ 	})
583+ }
584+ 
585+ // collectGroupPairs collects all privacy pairs for a specific session group, 
586+ // i.e. the group buckets under the privacy mapper bucket in the KV store. 
587+ // The function returns them as a map, where the key is the real value, and 
588+ // the value for the key is the pseudo values. 
589+ // It also checks that the pairs are consistent, i.e. that for each real value 
590+ // there is a corresponding pseudo value, and vice versa. If the pairs are 
591+ // inconsistent, it returns an error indicating the mismatch. 
592+ func  collectGroupPairs (bkt  * bbolt.Bucket ) (map [string ]string , error ) {
593+ 	var  (
594+ 		realToPseudoRes  map [string ]string 
595+ 		pseudoToRealRes  map [string ]string 
596+ 		err              error 
597+ 		missMatchErr     =  errors .New ("privacy mapper pairs mismatch" )
598+ 	)
599+ 
600+ 	if  realBkt  :=  bkt .Bucket (realToPseudoKey ); realBkt  !=  nil  {
601+ 		realToPseudoRes , err  =  collectPairs (realBkt )
602+ 		if  err  !=  nil  {
603+ 			return  nil , fmt .Errorf ("fetching real to pseudo pairs " + 
604+ 				"failed: %w" , err )
605+ 		}
606+ 	} else  {
607+ 		return  nil , fmt .Errorf ("%s bucket not found" , realToPseudoKey )
608+ 	}
609+ 
610+ 	if  pseudoBkt  :=  bkt .Bucket (pseudoToRealKey ); pseudoBkt  !=  nil  {
611+ 		pseudoToRealRes , err  =  collectPairs (pseudoBkt )
612+ 		if  err  !=  nil  {
613+ 			return  nil , fmt .Errorf ("fetching pseudo to real pairs " + 
614+ 				"failed: %w" , err )
615+ 		}
616+ 	} else  {
617+ 		return  nil , fmt .Errorf ("%s bucket not found" , pseudoToRealKey )
618+ 	}
619+ 
620+ 	if  len (realToPseudoRes ) !=  len (pseudoToRealRes ) {
621+ 		return  nil , missMatchErr 
622+ 	}
623+ 
624+ 	for  realVal , pseudoVal  :=  range  realToPseudoRes  {
625+ 		if  rv , ok  :=  pseudoToRealRes [pseudoVal ]; ! ok  ||  rv  !=  realVal  {
626+ 			return  nil , missMatchErr 
627+ 		}
628+ 	}
629+ 
630+ 	return  realToPseudoRes , nil 
631+ }
632+ 
633+ // collectPairs collects all privacy pairs from a specific realToPseudoKey or 
634+ // pseudoToRealKey bucket in the KV store. It returns a map where the key is 
635+ // the real value or pseudo value, and the value is the corresponding pseudo 
636+ // value or real value, respectively (depending on if the realToPseudo or 
637+ // pseudoToReal bucket is passed to the function). 
638+ func  collectPairs (pairsBucket  * bbolt.Bucket ) (map [string ]string , error ) {
639+ 	pairsRes  :=  make (map [string ]string )
640+ 
641+ 	return  pairsRes , pairsBucket .ForEach (func (k , v  []byte ) error  {
642+ 		if  v  ==  nil  {
643+ 			return  fmt .Errorf ("expected only key-values under " + 
644+ 				"pairs bucket, but found bucket %s" , k )
645+ 		}
646+ 
647+ 		if  len (v ) ==  0  {
648+ 			return  fmt .Errorf ("empty value stored for privacy " + 
649+ 				"pairs key %s" , k )
650+ 		}
651+ 
652+ 		pairsRes [string (k )] =  string (v )
653+ 
654+ 		return  nil 
655+ 	})
656+ }
657+ 
658+ // insertPrivacyPairs inserts the collected privacy pairs into the SQL database. 
659+ func  insertPrivacyPairs (ctx  context.Context , sqlTx  SQLQueries ,
660+ 	pairs  privacyPairs ) error  {
661+ 
662+ 	for  groupId , groupPairs  :=  range  pairs  {
663+ 		err  :=  insertGroupPairs (ctx , sqlTx , groupPairs , groupId )
664+ 		if  err  !=  nil  {
665+ 			return  fmt .Errorf ("inserting group pairs for group " + 
666+ 				"id %d failed: %w" , groupId , err )
667+ 		}
668+ 	}
669+ 
670+ 	return  nil 
671+ }
672+ 
673+ // insertGroupPairs inserts the privacy pairs for a specific group into 
674+ // the SQL database. It checks for duplicates before inserting, and returns 
675+ // an error if a duplicate pair is found. The function takes a map of real 
676+ // to pseudo values, where the key is the real value and the value is the 
677+ // corresponding pseudo value. 
678+ func  insertGroupPairs (ctx  context.Context , sqlTx  SQLQueries ,
679+ 	pairs  map [string ]string , groupID  int64 ) error  {
680+ 
681+ 	for  realVal , pseudoVal  :=  range  pairs  {
682+ 		err  :=  sqlTx .InsertPrivacyPair (
683+ 			ctx , sqlc.InsertPrivacyPairParams {
684+ 				GroupID :   groupID ,
685+ 				RealVal :   realVal ,
686+ 				PseudoVal : pseudoVal ,
687+ 			},
688+ 		)
689+ 		if  err  !=  nil  {
690+ 			return  fmt .Errorf ("inserting privacy pair %s:%s " + 
691+ 				"failed: %w" , realVal , pseudoVal , err )
692+ 		}
693+ 	}
694+ 
695+ 	return  nil 
696+ }
697+ 
698+ // validatePrivacyPairsMigration validates that the migrated privacy pairs 
699+ // match the original values in the KV store. 
700+ func  validatePrivacyPairsMigration (ctx  context.Context , sqlTx  SQLQueries ,
701+ 	pairs  privacyPairs ) error  {
702+ 
703+ 	for  groupId , groupPairs  :=  range  pairs  {
704+ 		err  :=  validateGroupPairsMigration (
705+ 			ctx , sqlTx , groupPairs , groupId ,
706+ 		)
707+ 		if  err  !=  nil  {
708+ 			return  fmt .Errorf ("migration validation of privacy " + 
709+ 				"pairs for group %d failed: %w" , groupId , err )
710+ 		}
711+ 	}
712+ 
713+ 	return  nil 
714+ }
715+ 
716+ // validateGroupPairsMigration validates that the migrated privacy pairs for 
717+ // a specific group match the original values in the KV store. It checks that 
718+ // for each real value, the pseudo value in the SQL database matches the 
719+ // original pseudo value, and vice versa. If any mismatch is found, it returns 
720+ // an error indicating the mismatch. 
721+ func  validateGroupPairsMigration (ctx  context.Context , sqlTx  SQLQueries ,
722+ 	pairs  map [string ]string , groupID  int64 ) error  {
723+ 
724+ 	for  realVal , pseudoVal  :=  range  pairs  {
725+ 		resPseudoVal , err  :=  sqlTx .GetPseudoForReal (
726+ 			ctx , sqlc.GetPseudoForRealParams {
727+ 				GroupID : groupID ,
728+ 				RealVal : realVal ,
729+ 			},
730+ 		)
731+ 		if  errors .Is (err , sql .ErrNoRows ) {
732+ 			return  fmt .Errorf ("migrated privacy pair %s:%s not " + 
733+ 				"found for real value" , realVal , pseudoVal )
734+ 		}
735+ 		if  err  !=  nil  {
736+ 			return  err 
737+ 		}
738+ 
739+ 		if  resPseudoVal  !=  pseudoVal  {
740+ 			return  fmt .Errorf ("pseudo value in db %s, does not " + 
741+ 				"match original value %s, for real value %s" ,
742+ 				resPseudoVal , pseudoVal , realVal )
743+ 		}
744+ 
745+ 		resRealVal , err  :=  sqlTx .GetRealForPseudo (
746+ 			ctx , sqlc.GetRealForPseudoParams {
747+ 				GroupID :   groupID ,
748+ 				PseudoVal : pseudoVal ,
749+ 			},
750+ 		)
751+ 		if  errors .Is (err , sql .ErrNoRows ) {
752+ 			return  fmt .Errorf ("migrated privacy pair %s:%s not " + 
753+ 				"found for pseudo value" , realVal , pseudoVal )
754+ 		}
755+ 		if  err  !=  nil  {
756+ 			return  err 
757+ 		}
758+ 
759+ 		if  resRealVal  !=  realVal  {
760+ 			return  fmt .Errorf ("real value in db %s, does not " + 
761+ 				"match original value %s, for pseudo value %s" ,
762+ 				resRealVal , realVal , pseudoVal )
763+ 		}
764+ 	}
765+ 
766+ 	return  nil 
767+ }
0 commit comments