@@ -67,14 +67,16 @@ 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
7378// all.
7479//
75- // Note that this migration currently only migrates the kvstores, but will be
76- // extended in the future to also migrate the privacy mapper and action stores.
77- //
7880// NOTE: As sessions may contain linked sessions and accounts, the sessions and
7981// accounts sql migration MUST be run prior to this migration.
8082func MigrateFirewallDBToSQL (ctx context.Context , kvStore * bbolt.DB ,
@@ -87,10 +89,14 @@ func MigrateFirewallDBToSQL(ctx context.Context, kvStore *bbolt.DB,
8789 return err
8890 }
8991
92+ err = migratePrivacyMapperDBToSQL (ctx , kvStore , sqlTx )
93+ if err != nil {
94+ return err
95+ }
96+
9097 log .Infof ("The rules DB has been migrated from KV to SQL." )
9198
92- // TODO(viktor): Add migration for the privacy mapper and the action
93- // stores.
99+ // TODO(viktor): Add migration for the action stores.
94100
95101 return nil
96102}
@@ -490,3 +496,281 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
490496 return fmt .Errorf ("unexpected key found: %s" , key )
491497 })
492498}
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