/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: 2024-11-24 00:44:25 UTC
  • Revision ID: teddy@recompile.se-20241124004425-6k3y0ir1ksyjq3c4
mandos-keygen: Show warning about old OpenSSH versions

When generating a config file snippet on the Mandos client system
using mandos-keygen, and the default ssh-keyscan checker is used, and
if the OpenSSH version is 9.8 or later, the "checker" command
generated for the config file on the Mandos server will include the
"-q" option for ssh-keyscan.  This option did not exist on ssh-keyscan
from OpenSSH older than version 9.8.  Therefore, if the Mandos
*server* is running an older version of OpenSSH, where ssh-keyscan
does not support the "-q" option, this option must be removed from the
generated "checker" setting.  Since we cannot know if this is the case
when running mandos-keygen on the Mandos client system, we print this
information as a comment above the generated "checker" setting.

* mandos-keygen: Show warning if the new "-q" options was used with
  ssh-keyscan in the generated "checker" setting.

Show diffs side-by-side

added added

removed removed

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