/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2023-02-07 23:03:33 UTC
  • Revision ID: teddy@recompile.se-20230207230333-5halrp7943pgb3w1
Server: Bug fix: Stagger checker runs when creating clients

* mandos (Client.enable()): Do not set self.expires here; move it to
  "init_checker".
  (Client.init_checker()): Take new "randomize_start" argument.  If
  True, randomize delay before starting checker.  Also, do not start
  checker right now, but instead extend expire time so that the
  scheduled checker always has time to run.
  (Checker.start_checker): Take new "start_was_randomized" argument.
  If True, reset scheduled checker runs to be 'interval' apart,
  instead of using the initial delay.  (Bug fix)
  (main): On startup, pass argument randomize_start=True to
  client.init_checker() when initizlizing checkers for all enabled
  clients.

Reported-by: Louis Charreau <Louis.Charreau@vadesecure.com>
Suggested-by: Louis Charreau <Louis.Charreau@vadesecure.com>
Fixes: 1200 ("Server: Stagger checker runs when creating clients")

Show diffs side-by-side

added added

removed removed

Lines of Context:
50
50
    str = unicode
51
51
    input = raw_input
52
52
 
53
 
 
54
53
class gi:
55
54
    """Dummy gi module, for the tests"""
56
55
    class repository:
57
56
        class GLib:
58
57
            class Error(Exception):
59
58
                pass
60
 
 
61
 
 
62
59
dbussy = None
63
60
ravel = None
64
61
dbus_python = None
81
78
    warnings.simplefilter("default")
82
79
 
83
80
log = logging.getLogger(os.path.basename(sys.argv[0]))
84
 
logging.basicConfig(level="INFO",         # Show info level messages
 
81
logging.basicConfig(level="INFO", # Show info level messages
85
82
                    format="%(message)s") # Show basic log messages
86
83
 
87
84
logging.captureWarnings(True)   # Show warnings via the logging system
92
89
 
93
90
locale.setlocale(locale.LC_ALL, "")
94
91
 
95
 
version = "1.8.17"
 
92
version = "1.8.15"
96
93
 
97
94
 
98
95
def main():
395
392
 
396
393
 
397
394
def parse_pre_1_6_1_interval(interval):
398
 
    r"""Parse an interval string as documented by Mandos before 1.6.1,
 
395
    """Parse an interval string as documented by Mandos before 1.6.1,
399
396
    and return a datetime.timedelta
400
397
 
401
398
    >>> parse_pre_1_6_1_interval("7d") == datetime.timedelta(days=7)
413
410
    >>> parse_pre_1_6_1_interval("") == datetime.timedelta(0)
414
411
    True
415
412
    >>> # Ignore unknown characters, allow any order and repetitions
416
 
    >>> parse_pre_1_6_1_interval("2dxy7zz11y3m5m") \
417
 
    ... == datetime.timedelta(2, 480, 18000)
 
413
    >>> parse_pre_1_6_1_interval("2dxy7zz11y3m5m") == datetime.timedelta(2, 480, 18000)
418
414
    True
419
415
 
420
416
    """
489
485
    class SystemBus:
490
486
 
491
487
        object_manager_iface = "org.freedesktop.DBus.ObjectManager"
492
 
 
493
488
        def get_managed_objects(self, busname, objectpath):
494
489
            return self.call_method("GetManagedObjects", busname,
495
490
                                    objectpath,
496
491
                                    self.object_manager_iface)
497
492
 
498
493
        properties_iface = "org.freedesktop.DBus.Properties"
499
 
 
500
494
        def set_property(self, busname, objectpath, interface, key,
501
495
                         value):
502
496
            self.call_method("Set", busname, objectpath,
507
501
                        interface, *args):
508
502
            raise NotImplementedError()
509
503
 
 
504
 
510
505
    class MandosBus(SystemBus):
511
506
        busname_domain = "se.recompile"
512
507
        busname = busname_domain + ".Mandos"
605
600
 
606
601
    class SilenceLogger:
607
602
        "Simple context manager to silence a particular logger"
608
 
 
609
603
        def __init__(self, loggername):
610
604
            self.logger = logging.getLogger(loggername)
611
605
 
621
615
        def __exit__(self, exc_type, exc_val, exc_tb):
622
616
            self.logger.removeFilter(self.nullfilter)
623
617
 
 
618
 
624
619
    class CachingBus(SystemBus):
625
620
        """A caching layer for dbus_python_adapter.SystemBus"""
626
 
 
627
621
        def __init__(self, *args, **kwargs):
628
622
            self.object_cache = {}
629
623
            super(dbus_python_adapter.CachingBus,
630
624
                  self).__init__(*args, **kwargs)
631
 
 
632
625
        def get_object(self, busname, objectpath):
633
626
            try:
634
627
                return self.object_cache[(busname, objectpath)]
636
629
                new_object = super(
637
630
                    dbus_python_adapter.CachingBus,
638
631
                    self).get_object(busname, objectpath)
639
 
                self.object_cache[(busname, objectpath)] = new_object
 
632
                self.object_cache[(busname, objectpath)]  = new_object
640
633
                return new_object
641
634
 
642
635
 
689
682
 
690
683
    class CachingBus(SystemBus):
691
684
        """A caching layer for pydbus_adapter.SystemBus"""
692
 
 
693
685
        def __init__(self, *args, **kwargs):
694
686
            self.object_cache = {}
695
687
            super(pydbus_adapter.CachingBus,
696
688
                  self).__init__(*args, **kwargs)
697
 
 
698
689
        def get(self, busname, objectpath):
699
690
            try:
700
691
                return self.object_cache[(busname, objectpath)]
701
692
            except KeyError:
702
693
                new_object = (super(pydbus_adapter.CachingBus, self)
703
694
                              .get(busname, objectpath))
704
 
                self.object_cache[(busname, objectpath)] = new_object
 
695
                self.object_cache[(busname, objectpath)]  = new_object
705
696
                return new_object
706
697
 
707
698
 
733
724
            iface = proxy_object.get_interface(interface)
734
725
            method = getattr(iface, methodname)
735
726
            with self.convert_exception(dbus.Error):
736
 
                value = method(*args)
 
727
                value =  method(*args)
737
728
            # DBussy returns values either as an empty list or as a
738
729
            # list of one element with the return value
739
730
            if value:
780
771
 
781
772
    class CachingBus(MandosBus):
782
773
        """A caching layer for dbussy_adapter.MandosBus"""
783
 
 
784
774
        def __init__(self, *args, **kwargs):
785
775
            self.object_cache = {}
786
776
            super(dbussy_adapter.CachingBus, self).__init__(*args,
787
777
                                                            **kwargs)
788
 
 
789
778
        def get_object(self, busname, objectpath):
790
779
            try:
791
780
                return self.object_cache[(busname, objectpath)]
793
782
                new_object = super(
794
783
                    dbussy_adapter.CachingBus,
795
784
                    self).get_object(busname, objectpath)
796
 
                self.object_cache[(busname, objectpath)] = new_object
 
785
                self.object_cache[(busname, objectpath)]  = new_object
797
786
                return new_object
798
787
 
799
788
 
835
824
 
836
825
    class Base:
837
826
        """Abstract base class for commands"""
838
 
 
839
827
        def run(self, clients, bus=None):
840
828
            """Normal commands should implement run_on_one_client(),
841
829
but commands which want to operate on all clients at the same time can
845
833
            for client, properties in clients.items():
846
834
                self.run_on_one_client(client, properties)
847
835
 
 
836
 
848
837
    class IsEnabled(Base):
849
838
        def run(self, clients, bus=None):
850
839
            properties = next(iter(clients.values()))
852
841
                sys.exit(0)
853
842
            sys.exit(1)
854
843
 
 
844
 
855
845
    class Approve(Base):
856
846
        def run_on_one_client(self, client, properties):
857
847
            self.bus.call_client_method(client, "Approve", True)
858
848
 
 
849
 
859
850
    class Deny(Base):
860
851
        def run_on_one_client(self, client, properties):
861
852
            self.bus.call_client_method(client, "Approve", False)
862
853
 
 
854
 
863
855
    class Remove(Base):
864
856
        def run(self, clients, bus):
865
857
            for clientpath in frozenset(clients.keys()):
866
858
                bus.call_server_method("RemoveClient", clientpath)
867
859
 
 
860
 
868
861
    class Output(Base):
869
862
        """Abstract class for commands outputting client details"""
870
863
        all_keywords = ("Name", "Enabled", "Timeout", "LastCheckedOK",
876
869
                        "Checker", "ExtendedTimeout", "Expires",
877
870
                        "LastCheckerStatus")
878
871
 
 
872
 
879
873
    class DumpJSON(Output):
880
874
        def run(self, clients, bus=None):
881
875
            data = {properties["Name"]:
884
878
                    for properties in clients.values()}
885
879
            print(json.dumps(data, indent=4, separators=(",", ": ")))
886
880
 
 
881
 
887
882
    class PrintTable(Output):
888
883
        def __init__(self, verbose=False):
889
884
            self.verbose = verbose
929
924
 
930
925
            if sys.version_info.major == 2:
931
926
                __unicode__ = __str__
932
 
 
933
927
                def __str__(self):
934
928
                    return str(self).encode(
935
929
                        locale.getpreferredencoding())
981
975
                                minutes=(td.seconds % 3600) // 60,
982
976
                                seconds=td.seconds % 60))
983
977
 
 
978
 
984
979
    class PropertySetter(Base):
985
980
        "Abstract class for Actions for setting one client property"
986
981
 
993
988
        def propname(self):
994
989
            raise NotImplementedError()
995
990
 
 
991
 
996
992
    class Enable(PropertySetter):
997
993
        propname = "Enabled"
998
994
        value_to_set = True
999
995
 
 
996
 
1000
997
    class Disable(PropertySetter):
1001
998
        propname = "Enabled"
1002
999
        value_to_set = False
1003
1000
 
 
1001
 
1004
1002
    class BumpTimeout(PropertySetter):
1005
1003
        propname = "LastCheckedOK"
1006
1004
        value_to_set = ""
1007
1005
 
 
1006
 
1008
1007
    class StartChecker(PropertySetter):
1009
1008
        propname = "CheckerRunning"
1010
1009
        value_to_set = True
1011
1010
 
 
1011
 
1012
1012
    class StopChecker(PropertySetter):
1013
1013
        propname = "CheckerRunning"
1014
1014
        value_to_set = False
1015
1015
 
 
1016
 
1016
1017
    class ApproveByDefault(PropertySetter):
1017
1018
        propname = "ApprovedByDefault"
1018
1019
        value_to_set = True
1019
1020
 
 
1021
 
1020
1022
    class DenyByDefault(PropertySetter):
1021
1023
        propname = "ApprovedByDefault"
1022
1024
        value_to_set = False
1023
1025
 
 
1026
 
1024
1027
    class PropertySetterValue(PropertySetter):
1025
1028
        """Abstract class for PropertySetter recieving a value as
1026
1029
constructor argument instead of a class attribute."""
1027
 
 
1028
1030
        def __init__(self, value):
1029
1031
            self.value_to_set = value
1030
1032
 
1037
1039
    class SetChecker(PropertySetterValue):
1038
1040
        propname = "Checker"
1039
1041
 
 
1042
 
1040
1043
    class SetHost(PropertySetterValue):
1041
1044
        propname = "Host"
1042
1045
 
 
1046
 
1043
1047
    class SetSecret(PropertySetterValue):
1044
1048
        propname = "Secret"
1045
1049
 
1053
1057
            self._vts = value.read()
1054
1058
            value.close()
1055
1059
 
 
1060
 
1056
1061
    class PropertySetterValueMilliseconds(PropertySetterValue):
1057
1062
        """Abstract class for PropertySetterValue taking a value
1058
1063
argument as a datetime.timedelta() but should store it as
1067
1072
            "When setting, convert value from a datetime.timedelta"
1068
1073
            self._vts = int(round(value.total_seconds() * 1000))
1069
1074
 
 
1075
 
1070
1076
    class SetTimeout(PropertySetterValueMilliseconds):
1071
1077
        propname = "Timeout"
1072
1078
 
 
1079
 
1073
1080
    class SetExtendedTimeout(PropertySetterValueMilliseconds):
1074
1081
        propname = "ExtendedTimeout"
1075
1082
 
 
1083
 
1076
1084
    class SetInterval(PropertySetterValueMilliseconds):
1077
1085
        propname = "Interval"
1078
1086
 
 
1087
 
1079
1088
    class SetApprovalDelay(PropertySetterValueMilliseconds):
1080
1089
        propname = "ApprovalDelay"
1081
1090
 
 
1091
 
1082
1092
    class SetApprovalDuration(PropertySetterValueMilliseconds):
1083
1093
        propname = "ApprovalDuration"
1084
1094
 
1755
1765
        self.assertIs(ret, expected_method_return)
1756
1766
 
1757
1767
    def test_call_method_handles_exception(self):
 
1768
        dbus_logger = logging.getLogger("dbus.proxies")
 
1769
 
1758
1770
        def func():
1759
1771
            raise gi.repository.GLib.Error()
1760
1772