@@ -67,6 +67,11 @@ func (e *kvEntry) namespacedKey() string {
67
67
return ns
68
68
}
69
69
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
+
70
75
// MigrateFirewallDBToSQL runs the migration of the firwalldb stores from the
71
76
// bbolt database to a SQL database. The migration is done in a single
72
77
// 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,
84
89
return err
85
90
}
86
91
92
+ err = migratePrivacyMapperDBToSQL (ctx , kvStore , sqlTx )
93
+ if err != nil {
94
+ return err
95
+ }
96
+
87
97
log .Infof ("The rules DB has been migrated from KV to SQL." )
88
98
89
- // TODO(viktor): Add migration for the privacy mapper and the action
90
- // stores.
99
+ // TODO(viktor): Add migration for the action stores.
91
100
92
101
return nil
93
102
}
@@ -487,3 +496,281 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
487
496
return fmt .Errorf ("unexpected key found: %s" , key )
488
497
})
489
498
}
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