@@ -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
@@ -87,10 +92,14 @@ func MigrateFirewallDBToSQL(ctx context.Context, kvStore *bbolt.DB,
87
92
return err
88
93
}
89
94
95
+ err = migratePrivacyMapperDBToSQL (ctx , kvStore , sqlTx )
96
+ if err != nil {
97
+ return err
98
+ }
99
+
90
100
log .Infof ("The rules DB has been migrated from KV to SQL." )
91
101
92
- // TODO(viktor): Add migration for the privacy mapper and the action
93
- // stores.
102
+ // TODO(viktor): Add migration for the action stores.
94
103
95
104
return nil
96
105
}
@@ -490,3 +499,299 @@ func verifyBktKeys(bkt *bbolt.Bucket, errorOnKeyValues bool,
490
499
return fmt .Errorf ("unexpected key found: %s" , key )
491
500
})
492
501
}
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