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