@@ -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 
@@ -87,10 +92,14 @@ func MigrateFirewallDBToSQL(ctx context.Context, kvStore *bbolt.DB,
8792		return  err 
8893	}
8994
95+ 	err  =  migratePrivacyMapperDBToSQL (ctx , kvStore , sqlTx )
96+ 	if  err  !=  nil  {
97+ 		return  err 
98+ 	}
99+ 
90100	log .Infof ("The rules DB has been migrated from KV to SQL." )
91101
92- 	// TODO(viktor): Add migration for the privacy mapper and the action 
93- 	// stores. 
102+ 	// TODO(viktor): Add migration for the action stores. 
94103
95104	return  nil 
96105}
@@ -490,3 +499,299 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
490499		return  fmt .Errorf ("unexpected key found: %s" , key )
491500	})
492501}
502+ 
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+ 	return  nil 
534+ }
535+ 
536+ // collectPrivacyPairs collects all privacy pairs from the KV store and 
537+ // returns them as the privacyPairs type alias. 
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+ 		missMatchErr     =  errors .New ("privacy mapper pairs mismatch" )
602+ 	)
603+ 
604+ 	if  realBkt  :=  bkt .Bucket (realToPseudoKey ); realBkt  !=  nil  {
605+ 		realToPseudoRes , err  =  collectPairs (realBkt )
606+ 		if  err  !=  nil  {
607+ 			return  nil , fmt .Errorf ("fetching real to pseudo pairs " + 
608+ 				"failed: %w" , err )
609+ 		}
610+ 	} else  {
611+ 		return  nil , fmt .Errorf ("%s bucket not found" , realToPseudoKey )
612+ 	}
613+ 
614+ 	if  pseudoBkt  :=  bkt .Bucket (pseudoToRealKey ); pseudoBkt  !=  nil  {
615+ 		pseudoToRealRes , err  =  collectPairs (pseudoBkt )
616+ 		if  err  !=  nil  {
617+ 			return  nil , fmt .Errorf ("fetching pseudo to real pairs " + 
618+ 				"failed: %w" , err )
619+ 		}
620+ 	} else  {
621+ 		return  nil , fmt .Errorf ("%s bucket not found" , pseudoToRealKey )
622+ 	}
623+ 
624+ 	if  len (realToPseudoRes ) !=  len (pseudoToRealRes ) {
625+ 		return  nil , missMatchErr 
626+ 	}
627+ 
628+ 	for  realVal , pseudoVal  :=  range  realToPseudoRes  {
629+ 		if  rv , ok  :=  pseudoToRealRes [pseudoVal ]; ! ok  ||  rv  !=  realVal  {
630+ 			return  nil , missMatchErr 
631+ 		}
632+ 	}
633+ 
634+ 	return  realToPseudoRes , nil 
635+ }
636+ 
637+ // collectPairs collects all privacy pairs from a specific realToPseudoKey or 
638+ // pseudoToRealKey bucket in the KV store. It returns a map where the key is 
639+ // the real value or pseudo value, and the value is the corresponding pseudo 
640+ // value or real value, respectively (depending on if the realToPseudo or 
641+ // pseudoToReal bucket is passed to the function). 
642+ func  collectPairs (pairsBucket  * bbolt.Bucket ) (map [string ]string , error ) {
643+ 	pairsRes  :=  make (map [string ]string )
644+ 
645+ 	return  pairsRes , pairsBucket .ForEach (func (k , v  []byte ) error  {
646+ 		if  v  ==  nil  {
647+ 			return  fmt .Errorf ("expected only key-values under " + 
648+ 				"pairs bucket, but found bucket %s" , k )
649+ 		}
650+ 
651+ 		if  len (v ) ==  0  {
652+ 			return  fmt .Errorf ("empty value stored for privacy " + 
653+ 				"pairs key %s" , k )
654+ 		}
655+ 
656+ 		pairsRes [string (k )] =  string (v )
657+ 
658+ 		return  nil 
659+ 	})
660+ }
661+ 
662+ // insertPrivacyPairs inserts the collected privacy pairs into the SQL database. 
663+ func  insertPrivacyPairs (ctx  context.Context , sqlTx  SQLQueries ,
664+ 	pairs  privacyPairs ) error  {
665+ 
666+ 	for  groupId , groupPairs  :=  range  pairs  {
667+ 		err  :=  insertGroupPairs (ctx , sqlTx , groupPairs , groupId )
668+ 		if  err  !=  nil  {
669+ 			return  fmt .Errorf ("inserting group pairs for group " + 
670+ 				"id %d failed: %w" , groupId , err )
671+ 		}
672+ 	}
673+ 
674+ 	return  nil 
675+ }
676+ 
677+ // insertGroupPairs inserts the privacy pairs for a specific group into 
678+ // the SQL database. It checks for duplicates before inserting, and returns 
679+ // an error if a duplicate pair is found. The function takes a map of real 
680+ // to pseudo values, where the key is the real value and the value is the 
681+ // corresponding pseudo value. 
682+ func  insertGroupPairs (ctx  context.Context , sqlTx  SQLQueries ,
683+ 	pairs  map [string ]string , groupID  int64 ) error  {
684+ 
685+ 	for  realVal , pseudoVal  :=  range  pairs  {
686+ 		_ , err  :=  sqlTx .GetPseudoForReal (
687+ 			ctx , sqlc.GetPseudoForRealParams {
688+ 				GroupID : groupID ,
689+ 				RealVal : realVal ,
690+ 			},
691+ 		)
692+ 		if  err  ==  nil  {
693+ 			return  fmt .Errorf ("duplicate privacy pair %s:%s: %w" ,
694+ 				realVal , pseudoVal , ErrDuplicatePseudoValue )
695+ 		} else  if  ! errors .Is (err , sql .ErrNoRows ) {
696+ 			return  err 
697+ 		}
698+ 
699+ 		_ , err  =  sqlTx .GetRealForPseudo (
700+ 			ctx , sqlc.GetRealForPseudoParams {
701+ 				GroupID :   groupID ,
702+ 				PseudoVal : pseudoVal ,
703+ 			},
704+ 		)
705+ 		if  err  ==  nil  {
706+ 			return  fmt .Errorf ("duplicate privacy pair %s:%s: %w" ,
707+ 				realVal , pseudoVal , ErrDuplicatePseudoValue )
708+ 		} else  if  ! errors .Is (err , sql .ErrNoRows ) {
709+ 			return  err 
710+ 		}
711+ 
712+ 		err  =  sqlTx .InsertPrivacyPair (
713+ 			ctx , sqlc.InsertPrivacyPairParams {
714+ 				GroupID :   groupID ,
715+ 				RealVal :   realVal ,
716+ 				PseudoVal : pseudoVal ,
717+ 			},
718+ 		)
719+ 		if  err  !=  nil  {
720+ 			return  fmt .Errorf ("inserting privacy pair %s:%s " + 
721+ 				"failed: %w" , realVal , pseudoVal , err )
722+ 		}
723+ 	}
724+ 
725+ 	return  nil 
726+ }
727+ 
728+ // validatePrivacyPairsMigration validates that the migrated privacy pairs 
729+ // match the original values in the KV store. 
730+ func  validatePrivacyPairsMigration (ctx  context.Context , sqlTx  SQLQueries ,
731+ 	pairs  privacyPairs ) error  {
732+ 
733+ 	for  groupId , groupPairs  :=  range  pairs  {
734+ 		err  :=  validateGroupPairsMigration (
735+ 			ctx , sqlTx , groupPairs , groupId ,
736+ 		)
737+ 		if  err  !=  nil  {
738+ 			return  fmt .Errorf ("migration validation of privacy " + 
739+ 				"pairs for group %d failed: %w" , groupId , err )
740+ 		}
741+ 	}
742+ 
743+ 	return  nil 
744+ }
745+ 
746+ // validateGroupPairsMigration validates that the migrated privacy pairs for 
747+ // a specific group match the original values in the KV store. It checks that 
748+ // for each real value, the pseudo value in the SQL database matches the 
749+ // original pseudo value, and vice versa. If any mismatch is found, it returns 
750+ // an error indicating the mismatch. 
751+ func  validateGroupPairsMigration (ctx  context.Context , sqlTx  SQLQueries ,
752+ 	pairs  map [string ]string , groupID  int64 ) error  {
753+ 
754+ 	for  realVal , pseudoVal  :=  range  pairs  {
755+ 		resPseudoVal , err  :=  sqlTx .GetPseudoForReal (
756+ 			ctx , sqlc.GetPseudoForRealParams {
757+ 				GroupID : groupID ,
758+ 				RealVal : realVal ,
759+ 			},
760+ 		)
761+ 		if  errors .Is (err , sql .ErrNoRows ) {
762+ 			return  fmt .Errorf ("migrated privacy pair %s:%s not " + 
763+ 				"found for real value" , realVal , pseudoVal )
764+ 		}
765+ 		if  err  !=  nil  {
766+ 			return  err 
767+ 		}
768+ 
769+ 		if  resPseudoVal  !=  pseudoVal  {
770+ 			return  fmt .Errorf ("pseudo value in db %s, does not " + 
771+ 				"match original value %s, for real value %s" ,
772+ 				resPseudoVal , pseudoVal , realVal )
773+ 		}
774+ 
775+ 		resRealVal , err  :=  sqlTx .GetRealForPseudo (
776+ 			ctx , sqlc.GetRealForPseudoParams {
777+ 				GroupID :   groupID ,
778+ 				PseudoVal : pseudoVal ,
779+ 			},
780+ 		)
781+ 		if  errors .Is (err , sql .ErrNoRows ) {
782+ 			return  fmt .Errorf ("migrated privacy pair %s:%s not " + 
783+ 				"found for pseudo value" , realVal , pseudoVal )
784+ 		}
785+ 		if  err  !=  nil  {
786+ 			return  err 
787+ 		}
788+ 
789+ 		if  resRealVal  !=  realVal  {
790+ 			return  fmt .Errorf ("real value in db %s, does not " + 
791+ 				"match original value %s, for pseudo value %s" ,
792+ 				resRealVal , realVal , pseudoVal )
793+ 		}
794+ 	}
795+ 
796+ 	return  nil 
797+ }
0 commit comments