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