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