summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--debian/changelog12
-rw-r--r--debian/control3
-rwxr-xr-xdebian/rules5
-rw-r--r--interface-definitions/ssh.xml16
-rw-r--r--interface-definitions/syslog.xml863
-rw-r--r--interface-definitions/vrrp.xml255
-rw-r--r--op-mode-definitions/show-configuration.xml2
-rw-r--r--op-mode-definitions/show-disk.xml2
-rw-r--r--op-mode-definitions/show-host.xml37
-rw-r--r--op-mode-definitions/vrrp.xml37
-rw-r--r--python/vyos/authutils.py43
-rw-r--r--python/vyos/defaults.py2
-rw-r--r--python/vyos/initialsetup.py72
-rw-r--r--python/vyos/keepalived.py153
-rw-r--r--python/vyos/util.py68
-rwxr-xr-xsrc/conf_mode/snmp.py1
-rwxr-xr-xsrc/conf_mode/syslog.py266
-rwxr-xr-xsrc/conf_mode/vrrp.py338
-rwxr-xr-xsrc/migration-scripts/vrrp/1-to-2270
-rwxr-xr-xsrc/op_mode/dns_forwarding_restart.sh2
-rwxr-xr-xsrc/op_mode/show_configuration_files.sh (renamed from src/op_mode/show-configuration-files.sh)0
-rwxr-xr-xsrc/op_mode/show_disk_format.sh (renamed from src/op_mode/show-disk-format.sh)0
-rwxr-xr-xsrc/op_mode/show_raid.sh (renamed from src/op_mode/show-raid.sh)0
-rwxr-xr-xsrc/op_mode/vrrp.py98
-rwxr-xr-xsrc/system/vrrp-script-wrapper.py77
-rw-r--r--src/tests/test_initial_setup.py103
-rwxr-xr-xsrc/validators/numeric8
-rwxr-xr-xsrc/validators/script44
-rw-r--r--tests/data/config.boot.default40
30 files changed, 2815 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index d850036c5..5e0cbc910 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ OP_TMPL_DIR := templates-op
interface_definitions:
mkdir -p $(TMPL_DIR)
- find $(CURDIR)/interface-definitions/ -type f | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
+ find $(CURDIR)/interface-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-templates {} $(CURDIR)/schema/interface_definition.rng $(TMPL_DIR) || exit 1
# XXX: delete top level node.def's that now live in other packages
rm -f $(TMPL_DIR)/system/node.def
@@ -18,7 +18,7 @@ interface_definitions:
op_mode_definitions:
mkdir -p $(OP_TMPL_DIR)
- find $(CURDIR)/op-mode-definitions/ -type f | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
+ find $(CURDIR)/op-mode-definitions/ -type f -name "*.xml" | xargs -I {} $(CURDIR)/scripts/build-command-op-templates {} $(CURDIR)/schema/op-mode-definition.rng $(OP_TMPL_DIR) || exit 1
# XXX: delete top level op mode node.def's that now live in other packages
rm -f $(OP_TMPL_DIR)/show/node.def
diff --git a/debian/changelog b/debian/changelog
index 0f4183837..e8519e87a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
+vyos-1x (1.2.0-1) unstable; urgency=medium
+
+ * T666, T616: new implementation of the VRRP CLI.
+
+ -- Daniil Baturin <daniil@baturin.org> Fri, 27 Jul 2018 10:25:52 +0200
+
+vyos-1x (1.0.6) unstable; urgency=medium
+
+ * T736: Rewrite remote logging (syslog) to XML/Python
+
+ -- hagbard <vyosdev@derith.de> Tue, 24 Jul 2018 10:59:25 -0700
+
vyos-1x (1.0.5) unstable; urgency=medium
* T606: Error in DNS Forwarder listen-on
diff --git a/debian/control b/debian/control
index 51f396604..c87e7452c 100644
--- a/debian/control
+++ b/debian/control
@@ -19,6 +19,8 @@ Depends: python3,
python3-netifaces,
python3-jinja2,
python3-pystache,
+ python3-psutil,
+ python3-tabulate,
ipaddrcheck,
tcpdump,
bmon,
@@ -33,6 +35,7 @@ Depends: python3,
iputils-arping,
libvyosconfig0,
beep,
+ keepalived (>=2.0.5),
${shlibs:Depends},
${misc:Depends}
Description: VyOS configuration scripts and data
diff --git a/debian/rules b/debian/rules
index d284471ec..15dfec551 100755
--- a/debian/rules
+++ b/debian/rules
@@ -9,6 +9,7 @@ VYOS_CFG_TMPL_DIR := /opt/vyatta/share/vyatta-cfg/templates
VYOS_OP_TMPL_DIR := /opt/vyatta/share/vyatta-op/templates
MIGRATION_SCRIPTS_DIR := /opt/vyatta/etc/config-migrate/migrate/
+SYSTEM_SCRIPTS_DIR := usr/libexec/vyos/system
%:
dh $@ --with python3, --with quilt
@@ -48,6 +49,10 @@ override_dh_auto_install:
mkdir -p $(DIR)/$(MIGRATION_SCRIPTS_DIR)
cp -r src/migration-scripts/* $(DIR)/$(MIGRATION_SCRIPTS_DIR)
+ # Install system scripts
+ mkdir -p $(DIR)/$(SYSTEM_SCRIPTS_DIR)
+ cp -r src/system/* $(DIR)/$(SYSTEM_SCRIPTS_DIR)
+
# Install configuration command definitions
mkdir -p $(DIR)/$(VYOS_CFG_TMPL_DIR)
cp -r templates-cfg/* $(DIR)/$(VYOS_CFG_TMPL_DIR)
diff --git a/interface-definitions/ssh.xml b/interface-definitions/ssh.xml
index 9b3a2fddc..e8786d202 100644
--- a/interface-definitions/ssh.xml
+++ b/interface-definitions/ssh.xml
@@ -21,12 +21,20 @@
<leafNode name="group">
<properties>
<help>Allow members of a group to login</help>
+ <constraint>
+ <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
<leafNode name="user">
<properties>
<help>Allow specific users to login</help>
+ <constraint>
+ <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
@@ -37,12 +45,20 @@
<leafNode name="group">
<properties>
<help>Disallow members of a group to login</help>
+ <constraint>
+ <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
<leafNode name="user">
<properties>
<help>Disallow specific users to login</help>
+ <constraint>
+ <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters or more than 32 characters</constraintErrorMessage>
<multi/>
</properties>
</leafNode>
diff --git a/interface-definitions/syslog.xml b/interface-definitions/syslog.xml
new file mode 100644
index 000000000..0776fff56
--- /dev/null
+++ b/interface-definitions/syslog.xml
@@ -0,0 +1,863 @@
+<?xml version="1.0"?>
+
+<interfaceDefinition>
+ <node name="system">
+ <children>
+ <node name="syslog" owner="${vyos_conf_scripts_dir}/syslog.py">
+ <properties>
+ <help>System logging</help>
+ <priority>400</priority>
+ </properties>
+ <children>
+ <tagNode name="user">
+ <properties>
+ <help>Logging to specific user's terminal</help>
+ <constraint>
+ <regex>^[a-z_][a-z0-9_-]{1,31}[$]?</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in user</constraintErrorMessage>
+ <valueHelp>
+ <format>username</format>
+ <description>user login name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <tagNode name="facility">
+ <properties>
+ <help>Facility for logging</help>
+ <completionHelp>
+ <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All facilities excluding "mark"</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>authpriv</format>
+ <description>Non-system authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>cron</format>
+ <description>Cron daemon</description>
+ </valueHelp>
+ <valueHelp>
+ <format>daemon</format>
+ <description>System daemons</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kern</format>
+ <description>Kernel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lpr</format>
+ <description>Line printer spooler</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mail</format>
+ <description>Mail subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mark</format>
+ <description>Timestamp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>news</format>
+ <description>USENET subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocols</format>
+ <description>depricated will be set to local7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>security</format>
+ <description>depricated will be set to auth</description>
+ </valueHelp>
+ <valueHelp>
+ <format>syslog</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Application processes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>uucp</format>
+ <description>UUCP subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local0</format>
+ <description>Local facility 0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local1</format>
+ <description>Local facility 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local2</format>
+ <description>Local facility 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local3</format>
+ <description>Local facility 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local4</format>
+ <description>Local facility 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local5</format>
+ <description>Local facility 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local6</format>
+ <description>Local facility 6</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local7</format>
+ <description>Local facility 7</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="level">
+ <properties>
+ <help>Logging level</help>
+ <completionHelp>
+ <list>emerg alert crit err warning notice info debug all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emergency messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Urgent messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warning messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Messages for further investigation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Informational messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Log everything</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="host">
+ <properties>
+ <help>Logging to a remote host</help>
+ <constraint>
+ <!-- at least let's make sure whitespace isn't allowed, ideally it should be checked for IPv4/IPv6 address or fqdn/hostname -->
+ <regex>[^ ]{1,63}</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in user</constraintErrorMessage>
+ <valueHelp>
+ <format>x.x.x.x or host.domain.tld</format>
+ <description>Remote host name or IP address</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <tagNode name="facility">
+ <properties>
+ <help>Facility for logging</help>
+ <completionHelp>
+ <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All facilities excluding "mark"</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>authpriv</format>
+ <description>Non-system authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>cron</format>
+ <description>Cron daemon</description>
+ </valueHelp>
+ <valueHelp>
+ <format>daemon</format>
+ <description>System daemons</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kern</format>
+ <description>Kernel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lpr</format>
+ <description>Line printer spooler</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mail</format>
+ <description>Mail subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mark</format>
+ <description>Timestamp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>news</format>
+ <description>USENET subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocols</format>
+ <description>depricated will be set to local7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>security</format>
+ <description>depricated will be set to auth</description>
+ </valueHelp>
+ <valueHelp>
+ <format>syslog</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Application processes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>uucp</format>
+ <description>UUCP subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local0</format>
+ <description>Local facility 0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local1</format>
+ <description>Local facility 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local2</format>
+ <description>Local facility 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local3</format>
+ <description>Local facility 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local4</format>
+ <description>Local facility 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local5</format>
+ <description>Local facility 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local6</format>
+ <description>Local facility 6</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local7</format>
+ <description>Local facility 7</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="protocol">
+ <properties>
+ <help>syslog communication protocol</help>
+ <valueHelp>
+ <format>udp</format>
+ <description>send log messages to remote syslog server over udp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>send log messages to remote syslog server over tdp</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+
+ <leafNode name="level">
+ <properties>
+ <help>Logging level</help>
+ <completionHelp>
+ <list>emerg alert crit err warning notice info debug all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emergency messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Urgent messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warning messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Messages for further investigation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Informational messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Log everything</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <node name="global">
+ <children>
+ <node name="archive">
+ <properties>
+ <help>Log file size and rotation characteristics</help>
+ </properties>
+ <children>
+ <leafNode name="file">
+ <properties>
+ <help>Number of saved files (default is 5)</help>
+ <constraint>
+ <regex>^[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="size">
+ <properties>
+ <help>Size of log files (in kbytes, default is 256)</help>
+ <constraint>
+ <regex>^[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in size</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="facility">
+ <properties>
+ <help>Facility for logging</help>
+ <completionHelp>
+ <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All facilities excluding "mark"</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>authpriv</format>
+ <description>Non-system authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>cron</format>
+ <description>Cron daemon</description>
+ </valueHelp>
+ <valueHelp>
+ <format>daemon</format>
+ <description>System daemons</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kern</format>
+ <description>Kernel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lpr</format>
+ <description>Line printer spooler</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mail</format>
+ <description>Mail subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mark</format>
+ <description>Timestamp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>news</format>
+ <description>USENET subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocols</format>
+ <description>depricated will be set to local7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>security</format>
+ <description>depricated will be set to auth</description>
+ </valueHelp>
+ <valueHelp>
+ <format>syslog</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Application processes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>uucp</format>
+ <description>UUCP subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local0</format>
+ <description>Local facility 0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local1</format>
+ <description>Local facility 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local2</format>
+ <description>Local facility 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local3</format>
+ <description>Local facility 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local4</format>
+ <description>Local facility 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local5</format>
+ <description>Local facility 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local6</format>
+ <description>Local facility 6</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local7</format>
+ <description>Local facility 7</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="level">
+ <properties>
+ <help>Logging level</help>
+ <completionHelp>
+ <list>emerg alert crit err warning notice info debug all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emergency messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Urgent messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warning messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Messages for further investigation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Informational messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Log everything</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ <tagNode name="file">
+ <properties>
+ <help>Logging to a file</help>
+ <constraint>
+ <regex>^[a-zA-Z0-9\-_.]{1,255}</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in filename or filename longer than 255 characters</constraintErrorMessage>
+ </properties>
+ <children>
+ <node name="archive">
+ <properties>
+ <help>Log file size and rotation characteristics</help>
+ </properties>
+ <children>
+ <leafNode name="file">
+ <properties>
+ <help>Number of saved files (default is 5)</help>
+ <constraint>
+ <regex>^[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="size">
+ <properties>
+ <help>Size of log files (in kbytes, default is 256)</help>
+ <constraint>
+ <regex>^[0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>illegal characters in size</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="facility">
+ <properties>
+ <help>Facility for logging</help>
+ <completionHelp>
+ <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All facilities excluding "mark"</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>authpriv</format>
+ <description>Non-system authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>cron</format>
+ <description>Cron daemon</description>
+ </valueHelp>
+ <valueHelp>
+ <format>daemon</format>
+ <description>System daemons</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kern</format>
+ <description>Kernel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lpr</format>
+ <description>Line printer spooler</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mail</format>
+ <description>Mail subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mark</format>
+ <description>Timestamp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>news</format>
+ <description>USENET subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocols</format>
+ <description>depricated will be set to local7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>security</format>
+ <description>depricated will be set to auth</description>
+ </valueHelp>
+ <valueHelp>
+ <format>syslog</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Application processes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>uucp</format>
+ <description>UUCP subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local0</format>
+ <description>Local facility 0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local1</format>
+ <description>Local facility 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local2</format>
+ <description>Local facility 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local3</format>
+ <description>Local facility 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local4</format>
+ <description>Local facility 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local5</format>
+ <description>Local facility 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local6</format>
+ <description>Local facility 6</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local7</format>
+ <description>Local facility 7</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="level">
+ <properties>
+ <help>Logging level</help>
+ <completionHelp>
+ <list>emerg alert crit err warning notice info debug all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emergency messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Urgent messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warning messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Messages for further investigation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Informational messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Log everything</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ <node name="console">
+ <properties>
+ <help>logging to serial console</help>
+ </properties>
+ <children>
+ <tagNode name="facility">
+ <properties>
+ <help>Facility for logging</help>
+ <completionHelp>
+ <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All facilities excluding "mark"</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>authpriv</format>
+ <description>Non-system authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>cron</format>
+ <description>Cron daemon</description>
+ </valueHelp>
+ <valueHelp>
+ <format>daemon</format>
+ <description>System daemons</description>
+ </valueHelp>
+ <valueHelp>
+ <format>kern</format>
+ <description>Kernel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>lpr</format>
+ <description>Line printer spooler</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mail</format>
+ <description>Mail subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>mark</format>
+ <description>Timestamp</description>
+ </valueHelp>
+ <valueHelp>
+ <format>news</format>
+ <description>USENET subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>protocols</format>
+ <description>depricated will be set to local7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>security</format>
+ <description>depricated will be set to auth</description>
+ </valueHelp>
+ <valueHelp>
+ <format>syslog</format>
+ <description>Authentication and authorization</description>
+ </valueHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Application processes</description>
+ </valueHelp>
+ <valueHelp>
+ <format>uucp</format>
+ <description>UUCP subsystem</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local0</format>
+ <description>Local facility 0</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local1</format>
+ <description>Local facility 1</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local2</format>
+ <description>Local facility 2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local3</format>
+ <description>Local facility 3</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local4</format>
+ <description>Local facility 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local5</format>
+ <description>Local facility 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local6</format>
+ <description>Local facility 6</description>
+ </valueHelp>
+ <valueHelp>
+ <format>local7</format>
+ <description>Local facility 7</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <leafNode name="level">
+ <properties>
+ <help>Logging level</help>
+ <completionHelp>
+ <list>emerg alert crit err warning notice info debug all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>emerg</format>
+ <description>Emergency messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>alert</format>
+ <description>Urgent messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>crit</format>
+ <description>Critical messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>err</format>
+ <description>Error messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>warning</format>
+ <description>Warning messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>notice</format>
+ <description>Messages for further investigation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>info</format>
+ <description>Informational messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>debug</format>
+ <description>Debug messages</description>
+ </valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Log everything</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/vrrp.xml b/interface-definitions/vrrp.xml
new file mode 100644
index 000000000..72419efe9
--- /dev/null
+++ b/interface-definitions/vrrp.xml
@@ -0,0 +1,255 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="high-availability">
+ <properties>
+ <help>High availability settings</help>
+ </properties>
+ <children>
+ <node name="vrrp" owner="${vyos_conf_scripts_dir}/vrrp.py">
+ <properties>
+ <priority>800</priority> <!-- after all interfaces and conntrack-sync -->
+ <help>Virtual Router Redundancy Protocol settings</help>
+ </properties>
+ <children>
+ <tagNode name="group">
+ <properties>
+ <help>VRRP group</help>
+ </properties>
+ <children>
+ <leafNode name="interface">
+ <properties>
+ <help>Network interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py --broadcast</script>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="advertise-interval">
+ <properties>
+ <help>Advertise interval</help>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Advertise interval in seconds (default: 1)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="authentication">
+ <properties>
+ <help>VRRP authentication</help>
+ </properties>
+ <children>
+ <leafNode name="password">
+ <properties>
+ <help>VRRP password</help>
+ <valueHelp>
+ <format>text</format>
+ <description>Password string (up to 8 characters)</description>
+ </valueHelp>
+ <constraint>
+ <regex>.{1,8}</regex>
+ </constraint>
+ <constraintErrorMessage>Password must not be longer than 8 characters</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Authentication type</help>
+ <completionHelp>
+ <list>plaintext-password ah</list>
+ </completionHelp>
+ <constraint>
+ <regex>(plaintext-password|ah)</regex>
+ </constraint>
+ <constraintErrorMessage>Authentication type must be plaintext-password or ah</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="description">
+ <properties>
+ <help>Group description</help>
+ </properties>
+ </leafNode>
+ <leafNode name="disable">
+ <properties>
+ <valueless/>
+ <help>Disable VRRP group</help>
+ </properties>
+ </leafNode>
+ <node name="health-check">
+ <properties>
+ <help>Health check script</help>
+ </properties>
+ <children>
+ <leafNode name="failure-count">
+ <properties>
+ <help>Health check failure count required for transition to fault (default: 3)</help>
+ <constraint>
+ <validator name="numeric" argument="--positive" />
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>Health check execution interval in seconds (default: 60)</help>
+ <constraint>
+ <validator name="numeric" argument="--positive"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="script">
+ <properties>
+ <help>Health check script file</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="hello-source-address">
+ <properties>
+ <help>VRRP hello source address (IPv4 or IPv6)</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <valueHelp>
+ <format>&lt;IPv4|IPv6&gt;</format>
+ <description>IPv4 or IPv6 hello source address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="peer-address">
+ <properties>
+ <help>Unicast VRRP peer address (IPv4 or IPv6)</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <valueHelp>
+ <format>&lt;IPv4|IPv6&gt;</format>
+ <description>IPv4 or IPv6 unicast peer address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="no-preempt">
+ <properties>
+ <valueless/>
+ <help>Disable master preemption</help>
+ </properties>
+ </leafNode>
+ <leafNode name="preempt-delay">
+ <properties>
+ <help>Preempt delay (in seconds)</help>
+ <constraint>
+ <validator name="numeric" argument="--positive"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="priority">
+ <properties>
+ <help>Router priority</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Router priority (default: 100)</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="rfc3768-compatibility">
+ <properties>
+ <valueless/>
+ <help>Use VRRP virtual MAC address as per RFC3768</help>
+ </properties>
+ </leafNode>
+ <node name="transition-script">
+ <properties>
+ <help>VRRP transition scripts</help>
+ </properties>
+ <children>
+ <leafNode name="master">
+ <properties>
+ <help>Script to run on VRRP state transition to master</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="backup">
+ <properties>
+ <help>Script to run on VRRP state transition to backup</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="fault">
+ <properties>
+ <help>Script to run on VRRP state transition to fault</help>
+ <constraint>
+ <validator name="script"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="virtual-address">
+ <properties>
+ <multi/>
+ <help>Virtual address (IPv4 or IPv6, but they must not be mixed in one group)</help>
+ <constraint>
+ <validator name="ipv4-host"/>
+ <validator name="ipv6-host"/>
+ </constraint>
+ <constraintErrorMessage>Virtual address must be a valid IPv4 or IPv6 address with prefix length (e.g. 192.0.2.3/24 or 2001:db8:ff::10/64)</constraintErrorMessage>
+ <valueHelp>
+ <format>&lt;IPv4|IPv6&gt;</format>
+ <description>IPv4 or IPv6 virtual address</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="vrid">
+ <properties>
+ <help>Virtual router identifier</help>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Virtual router identifier</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="sync-group">
+ <properties>
+ <help>VRRP sync group</help>
+ </properties>
+ <children>
+ <leafNode name="member">
+ <properties>
+ <multi/>
+ <help>Sync group member</help>
+ <valueHelp>
+ <format>text</format>
+ <description>VRRP group name</description>
+ </valueHelp>
+ <completionHelp>
+ <path>high-availability vrrp group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-configuration.xml b/op-mode-definitions/show-configuration.xml
index 0b1507ba4..90c1533fb 100644
--- a/op-mode-definitions/show-configuration.xml
+++ b/op-mode-definitions/show-configuration.xml
@@ -29,7 +29,7 @@
<help> Show available saved configurations </help>
</properties>
<!-- no admin check -->
- <command>${vyos_op_scripts_dir}/show-configuration-files.sh</command>
+ <command>${vyos_op_scripts_dir}/show_configuration_files.sh</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-disk.xml b/op-mode-definitions/show-disk.xml
index db473959a..8a8e35515 100644
--- a/op-mode-definitions/show-disk.xml
+++ b/op-mode-definitions/show-disk.xml
@@ -14,7 +14,7 @@
<properties>
<help>Show disk drive formatting</help>
</properties>
- <command>${vyos_op_scripts_dir}/show-disk-format.sh $3</command>
+ <command>${vyos_op_scripts_dir}/show_disk_format.sh $3</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-host.xml b/op-mode-definitions/show-host.xml
new file mode 100644
index 000000000..b3ea129a2
--- /dev/null
+++ b/op-mode-definitions/show-host.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="host">
+ <properties>
+ <help>Show host information</help>
+ </properties>
+ <children>
+ <leafNode name="domain">
+ <properties>
+ <help>Show domain name</help>
+ </properties>
+ <command>/bin/domainname -d</command>
+ </leafNode>
+
+ <leafNode name="name">
+ <properties>
+ <help>Show host name</help>
+ </properties>
+ <command>/bin/hostname</command>
+ </leafNode>
+
+ <tagNode name="lookup">
+ <properties>
+ <help>Lookup host information for hostname|IPv4 address</help>
+ </properties>
+ <command>/usr/bin/host $4</command>
+ </tagNode>
+
+
+ </children>
+ </node>
+
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/vrrp.xml b/op-mode-definitions/vrrp.xml
new file mode 100644
index 000000000..856fb440d
--- /dev/null
+++ b/op-mode-definitions/vrrp.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="vrrp">
+ <properties>
+ <help>Show VRRP (Virtual Router Redundancy Protocol) information</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/vrrp.py --summary</command>
+ <children>
+ <node name="statistics">
+ <properties>
+ <help>Show VRRP statistics</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/vrrp.py --statistics</command>
+ </node>
+ <node name="detail">
+ <properties>
+ <help>Show detailed VRRP state information</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/vrrp.py --data</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="restart">
+ <children>
+ <node name="vrrp">
+ <properties>
+ <help>Restart the VRRP (Virtual Router Redundancy Protocol) process</help>
+ </properties>
+ <command>sudo systemctl restart keepalived.service</command>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/authutils.py b/python/vyos/authutils.py
new file mode 100644
index 000000000..234294649
--- /dev/null
+++ b/python/vyos/authutils.py
@@ -0,0 +1,43 @@
+# authutils -- miscelanneous functions for handling passwords and publis keys
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or modify it under the terms of
+# the GNU Lesser General Public License as published by the Free Software Foundation;
+# either version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with this library;
+# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import re
+
+from subprocess import Popen, PIPE, STDOUT
+
+
+def make_password_hash(password):
+ """ Makes a password hash for /etc/shadow using mkpasswd """
+
+ mkpasswd = Popen(['mkpasswd', '--method=sha-512', '--stdin'], stdout=PIPE, stdin=PIPE, stderr=PIPE)
+ hash = mkpasswd.communicate(input=password.encode(), timeout=5)[0].decode().strip()
+
+ return hash
+
+def split_ssh_public_key(key_string, defaultname=""):
+ """ Splits an SSH public key into its components """
+
+ key_string = key_string.strip()
+ parts = re.split(r'\s+', key_string)
+
+ if len(parts) == 3:
+ key_type, key_data, key_name = parts[0], parts[1], parts[2]
+ else:
+ key_type, key_data, key_name = parts[0], parts[1], defaultname
+
+ if key_type not in ['ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519']:
+ raise ValueError("Bad key type \'{0}\', must be one of must be one of ssh-rsa, ssh-dss, ecdsa-sha2-nistp<256|384|521> or ssh-ed25519".format(key_type))
+
+ return({"type": key_type, "data": key_data, "name": key_name})
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index ac831c176..36185f16a 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -17,3 +17,5 @@
directories = {
"data": "/usr/share/vyos/"
}
+
+cfg_group = 'vyattacfg'
diff --git a/python/vyos/initialsetup.py b/python/vyos/initialsetup.py
new file mode 100644
index 000000000..574e7892d
--- /dev/null
+++ b/python/vyos/initialsetup.py
@@ -0,0 +1,72 @@
+# initialsetup -- functions for setting common values in config file,
+# for use in installation and first boot scripts
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or modify it under the terms of
+# the GNU Lesser General Public License as published by the Free Software Foundation;
+# either version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License along with this library;
+# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+import vyos.configtree
+import vyos.authutils
+
+def set_interface_address(config, intf, addr, intf_type="ethernet"):
+ config.set(["interfaces", intf_type, intf, "address"], value=addr)
+ config.set_tag(["interfaces", intf_type])
+
+def set_host_name(config, hostname):
+ config.set(["system", "host-name"], value=hostname)
+
+def set_name_servers(config, servers):
+ for s in servers:
+ config.set(["system", "name-server"], replace=False, value=s)
+
+def set_default_gateway(config, gateway):
+ config.set(["protocols", "static", "route", "0.0.0.0/0", "next-hop", gateway])
+ config.set_tag(["protocols", "static", "route"])
+ config.set_tag(["protocols", "static", "route", "0.0.0.0/0", "next-hop"])
+
+def set_user_password(config, user, password):
+ # Make a password hash
+ hash = vyos.authutils.make_password_hash(password)
+
+ config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value=hash)
+ config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="")
+
+def disable_user_password(config, user):
+ config.set(["system", "login", "user", user, "authentication", "encrypted-password"], value="!")
+ config.set(["system", "login", "user", user, "authentication", "plaintext-password"], value="")
+
+def set_user_level(config, user, level):
+ config.set(["system", "login", "user", user, "level"], value=level)
+
+def set_user_ssh_key(config, user, key_string):
+ key = vyos.authutils.split_ssh_public_key(key_string, defaultname=user)
+
+ config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "key"], value=key["data"])
+ config.set(["system", "login", "user", user, "authentication", "public-keys", key["name"], "type"], value=key["type"])
+ config.set_tag(["system", "login", "user", user, "authentication", "public-keys"])
+
+def create_user(config, user, password=None, key=None, level="admin"):
+ config.set(["system", "login", "user", user])
+ config.set_tag(["system", "login", "user", user])
+
+ if not key and not password:
+ raise ValueError("Must set at least password or SSH public key")
+
+ if password:
+ set_user_password(config, user, password)
+ else:
+ disable_user_password(config, user)
+
+ if key:
+ set_user_ssh_key(config, user, key)
+
+ set_user_level(config, user, level)
diff --git a/python/vyos/keepalived.py b/python/vyos/keepalived.py
new file mode 100644
index 000000000..4114aa736
--- /dev/null
+++ b/python/vyos/keepalived.py
@@ -0,0 +1,153 @@
+# Copyright 2018 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import signal
+import json
+
+import vyos.util
+
+
+pid_file = '/var/run/keepalived.pid'
+state_file = '/tmp/keepalived.data'
+stats_file = '/tmp/keepalived.stats'
+json_file = '/tmp/keepalived.json'
+
+state_dir = '/var/run/vyos/vrrp/'
+
+def vrrp_running():
+ if not os.path.exists(vyos.keepalived.pid_file) \
+ or not vyos.util.process_running(vyos.keepalived.pid_file):
+ return False
+ else:
+ return True
+
+def keepalived_running():
+ return vyos.util.process_running(pid_file)
+
+def force_state_data_dump():
+ pid = vyos.util.read_file(pid_file)
+ os.kill(int(pid), signal.SIGUSR1)
+
+def force_stats_dump():
+ pid = vyos.util.read_file(pid_file)
+ os.kill(int(pid), signal.SIGUSR2)
+
+def force_json_dump():
+ pid = vyos.util.read_file(pid_file)
+ os.kill(int(pid), signal.SIGRTMIN+2)
+
+def get_json_data():
+ with open(json_file, 'r') as f:
+ j = json.load(f)
+ return j
+
+def get_statistics():
+ return vyos.util.read_file(stats_file)
+
+def get_state_data():
+ return vyos.util.read_file(state_file)
+
+def decode_state(code):
+ state = None
+ if code == 0:
+ state = "INIT"
+ elif code == 1:
+ state = "BACKUP"
+ elif code == 2:
+ state = "MASTER"
+ elif code == 3:
+ state = "FAULT"
+ else:
+ state = "UNKNOWN"
+
+ return state
+
+## The functions are mainly for transition script wrappers
+## to compensate for the fact that keepalived doesn't keep persistent
+## state between reloads.
+def get_old_state(group):
+ file = os.path.join(state_dir, "{0}.state".format(group))
+ if os.path.exists(file):
+ with open(file, 'r') as f:
+ data = f.read().strip()
+ return data
+ else:
+ return None
+
+def save_state(group, state):
+ if not os.path.exists(state_dir):
+ os.makedirs(state_dir)
+
+ file = os.path.join(state_dir, "{0}.state".format(group))
+ with open(file, 'w') as f:
+ f.write(state)
+
+## These functions are for the old, and hopefully obsolete plaintext
+## (non machine-readable) data format introduced by Vyatta back in the days
+## They are kept here just in case, if JSON output option turns out or becomes
+## insufficient.
+
+def read_state_data():
+ with open(state_file, 'r') as f:
+ lines = f.readlines()
+ return lines
+
+def parse_keepalived_data(data_lines):
+ vrrp_groups = {}
+
+ # Scratch variable
+ group_name = None
+
+ # Sadly there is no explicit end marker in that format, so we have
+ # only two states, one before the first VRRP instance is encountered
+ # and one after an instance/"group" was encountered
+ # We'll set group_name once the first group is encountered,
+ # and assume we are inside a group if it's set afterwards
+ #
+ # It may not be necessary since the keywords found inside groups and before
+ # the VRRP Topology section seem to have no intersection,
+ # but better safe than sorry.
+
+ for line in data_lines:
+ if re.match(r'^\s*VRRP Instance', line, re.IGNORECASE):
+ # Example: "VRRP Instance = Foo"
+ name = re.match(r'^\s*VRRP Instance\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
+ group_name = name
+ vrrp_groups[name] = {}
+ elif re.match(r'^\s*State', line, re.IGNORECASE) and group_name:
+ # Example: " State = MASTER"
+ group_state = re.match(r'^\s*State\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
+ vrrp_groups[group_name]["state"] = group_state
+ elif re.match(r'^\s*Last transition', line, re.IGNORECASE) and group_name:
+ # Example: " Last transition = 1532043820 (Thu Jul 19 23:43:40 2018)"
+ trans_time = re.match(r'^\s*Last transition\s+=\s+(\d+)\s', line, re.IGNORECASE).groups()[0]
+ vrrp_groups[group_name]["last_transition"] = trans_time
+ elif re.match(r'^\s*Interface', line, re.IGNORECASE) and group_name:
+ # Example: " Interface = eth0.30"
+ interface = re.match(r'\s*Interface\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
+ vrrp_groups[group_name]["interface"] = interface
+ elif re.match(r'^\s*Virtual Router ID', line, re.IGNORECASE) and group_name:
+ # Example: " Virtual Router ID = 14"
+ vrid = re.match(r'^\s*Virtual Router ID\s+=\s+(.*)$', line, re.IGNORECASE).groups()[0].strip()
+ vrrp_groups[group_name]["vrid"] = vrid
+ elif re.match(r'^\s*------< Interfaces', line, re.IGNORECASE):
+ # Interfaces section appears to always be present,
+ # and there's nothing of interest for us below that section,
+ # so we use it as an end of input marker
+ break
+
+ return vrrp_groups
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 8b3de7999..8b5342575 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -13,8 +13,19 @@
# You should have received a copy of the GNU Lesser General Public
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+import os
import re
+import grp
+import psutil
+import vyos.defaults
+
+
+def read_file(path):
+ """ Read a file to string """
+ with open(path, 'r') as f:
+ data = f.read().strip()
+ return data
def colon_separated_to_dict(data_string, uniquekeys=False):
""" Converts a string containing newline-separated entries
@@ -63,3 +74,60 @@ def colon_separated_to_dict(data_string, uniquekeys=False):
pass
return data
+
+def process_running(pid_file):
+ """ Checks if a process with PID in pid_file is running """
+ with open(pid_file, 'r') as f:
+ pid = f.read().strip()
+ return psutil.pid_exists(int(pid))
+
+def seconds_to_human(s, separator=""):
+ """ Converts number of seconds passed to a human-readable
+ interval such as 1w4d18h35m59s
+ """
+ s = int(s)
+
+ week = 60 * 60 * 24 * 7
+ day = 60 * 60 * 24
+ hour = 60 * 60
+
+ remainder = 0
+ result = ""
+
+ weeks = s // week
+ if weeks > 0:
+ result = "{0}w".format(weeks)
+ s = s % week
+
+ days = s // day
+ if days > 0:
+ result = "{0}{1}{2}d".format(result, separator, days)
+ s = s % day
+
+ hours = s // hour
+ if hours > 0:
+ result = "{0}{1}{2}h".format(result, separator, hours)
+ s = s % hour
+
+ minutes = s // 60
+ if minutes > 0:
+ result = "{0}{1}{2}m".format(result, separator, minutes)
+ s = s % 60
+
+ seconds = s
+ if seconds > 0:
+ result = "{0}{1}{2}s".format(result, separator, seconds)
+
+ return result
+
+def get_cfg_group_id():
+ group_data = grp.getgrnam(vyos.defaults.cfg_group)
+ return group_data.gr_gid
+
+def file_is_persistent(path):
+ if not re.match(r'^(/config|/opt/vyatta/etc/config)', os.path.dirname(path)):
+ warning = "Warning: file {0} is outside the /config directory\n".format(path)
+ warning += "It will not be automatically migrated to a new image on system update"
+ return (False, warning)
+ else:
+ return (True, None)
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 1ef69ceb2..001e6c6b4 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -144,6 +144,7 @@ agentaddress unix:/run/snmpd.socket{% if listen_on %}{% for li in listen_on %},{
{% if c.network -%}
{% for network in c.network %}
{{ c.authorization }}community {{ c.name }} {{ network }}
+{{ c.authorization }}community6 {{ c.name }} {{ network }}
{% endfor %}
{% else %}
{{ c.authorization }}community {{ c.name }}
diff --git a/src/conf_mode/syslog.py b/src/conf_mode/syslog.py
new file mode 100755
index 000000000..5dfc6f390
--- /dev/null
+++ b/src/conf_mode/syslog.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import sys
+import os
+import re
+import jinja2
+
+from vyos.config import Config
+from vyos import ConfigError
+
+########### config templates
+
+#### /etc/rsyslog.d/vyos-rsyslog.conf ###
+configs = '''
+## generated by syslog.py ##
+## file based logging
+{% for file in files %}
+$outchannel {{file}},{{files[file]['log-file']}},{{files[file]['max-size']}},{{files[file]['action-on-max-size']}}
+{{files[file]['selectors']}} :omfile:${{file}}
+{% endfor %}
+{% if console %}
+## console logging
+{% for con in console %}
+{{console[con]['selectors']}} /dev/console
+{% endfor %}
+{% endif %}
+{% if hosts %}
+## remote logging
+{% for host in hosts %}
+{% if hosts[host]['proto'] == 'tcp' %}
+{{hosts[host]['selectors']}} @@{{host}}
+{% else %}
+{{hosts[host]['selectors']}} @{{host}}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% if user %}
+{% for u in user %}
+{{user[u]['selectors']}} :omusrmsg:{{u}}
+{% endfor %}
+{% endif %}
+'''
+
+logrotate_configs = '''
+{% for file in files %}
+{{files[file]['log-file']}} {
+ missingok
+ notifempty
+ create
+ rotate {{files[file]['max-files']}}
+ size={{ files[file]['max-size']//1024}}k
+ postrotate
+ invoke-rc.d rsyslog rotate > /dev/null
+ endscript
+}
+{% endfor %}
+'''
+############# config templates end
+
+def get_config():
+ c = Config()
+ if not c.exists('system syslog'):
+ return None
+ c.set_level('system syslog')
+
+ config_data = {
+ 'files' : {},
+ 'console' : {},
+ 'hosts' : {},
+ 'user' : {}
+ }
+
+ #####
+ # /etc/rsyslog.d/vyos-rsyslog.conf
+ # 'set system syslog global'
+ #####
+ config_data['files'].update(
+ {
+ 'global' : {
+ 'log-file' : '/var/log/vyos-rsyslog',
+ 'max-size' : 262144,
+ 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog',
+ 'selectors' : '*.notice;local7.debug',
+ 'max-files' : '5'
+ }
+ }
+ )
+
+ if c.exists('global facility'):
+ config_data['files']['global']['selectors'] = generate_selectors(c, 'global facility')
+ if c.exists('global archive size'):
+ config_data['files']['global']['max-size'] = int(c.return_value('global archive size'))* 1024
+ if c.exists('global archive files'):
+ config_data['files']['global']['max-files'] = c.return_value('global archive files')
+
+ ###
+ # set system syslog file
+ ###
+
+ if c.exists('file'):
+ filenames = c.list_nodes('file')
+ for filename in filenames:
+ config_data['files'].update(
+ {
+ filename : {
+ 'log-file' : '/var/log/user/' + filename,
+ 'max-files' : '5',
+ 'action-on-max-size' : '/usr/sbin/logrotate /etc/logrotate.d/' + filename,
+ 'selectors' : '*.err',
+ 'max-size' : 262144
+ }
+ }
+ )
+
+ if c.exists('file ' + filename + ' facility'):
+ config_data['files'][filename]['selectors'] = generate_selectors(c, 'file ' + filename + ' facility')
+ if c.exists('file ' + filename + ' archive size'):
+ config_data['files'][filename]['max-size'] = int(c.return_value('file ' + filename + ' archive size'))* 1024
+ if c.exists('file ' + filename + ' archive files'):
+ config_data['files'][filename]['max-files'] = c.return_value('file ' + filename + ' archive files')
+
+ ## set system syslog console
+ if c.exists('console'):
+ config_data['console'] = {
+ '/dev/console' : {
+ 'selectors' : '*.err'
+ }
+ }
+
+ for f in c.list_nodes('console facility'):
+ if c.exists('console facility ' + f + ' level'):
+ config_data['console'] = {
+ '/dev/console' : {
+ 'selectors' : generate_selectors(c, 'console facility')
+ }
+ }
+
+ ## set system syslog host
+ if c.exists('host'):
+ proto = 'udp'
+ rhosts = c.list_nodes('host')
+ for rhost in rhosts:
+ for fac in c.list_nodes('host ' + rhost + ' facility'):
+ if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'):
+ proto = c.return_value('host ' + rhost + ' facility ' + fac + ' protocol')
+
+ config_data['hosts'].update(
+ {
+ rhost : {
+ 'selectors' : generate_selectors(c, 'host ' + rhost + ' facility'),
+ 'proto' : proto
+ }
+ }
+ )
+
+ ## set system syslog user
+ if c.exists('user'):
+ usrs = c.list_nodes('user')
+ for usr in usrs:
+ config_data['user'].update(
+ {
+ usr : {
+ 'selectors' : generate_selectors(c, 'user ' + usr + ' facility')
+ }
+ }
+ )
+
+ return config_data
+
+def generate_selectors(c, config_node):
+## protocols and security are being mapped here
+## for backward compatibility with old configs
+## security and protocol mappings can be removed later
+ if c.is_tag(config_node):
+ nodes = c.list_nodes(config_node)
+ selectors = ""
+ for node in nodes:
+ lvl = c.return_value( config_node + ' ' + node + ' level')
+ if lvl == None:
+ lvl = "err"
+ if lvl == 'all':
+ lvl = '*'
+ if node == 'all' and node != nodes[-1]:
+ selectors += "*." + lvl + ";"
+ elif node == 'all':
+ selectors += "*." + lvl
+ elif node != nodes[-1]:
+ if node == 'protocols':
+ node = 'local7'
+ if node == 'security':
+ node = 'auth'
+ selectors += node + "." + lvl + ";"
+ else:
+ if node == 'protocols':
+ node = 'local7'
+ if node == 'security':
+ node = 'auth'
+ selectors += node + "." + lvl
+ return selectors
+
+def generate(c):
+ tmpl = jinja2.Template(configs, trim_blocks=True)
+ config_text = tmpl.render(c)
+ #print (config_text)
+ with open('/etc/rsyslog.d/vyos-rsyslog.conf', 'w') as f:
+ f.write(config_text)
+
+ ## eventually write for each file its own logrotate file, since size is defined it shouldn't matter
+ tmpl = jinja2.Template(logrotate_configs, trim_blocks=True)
+ config_text = tmpl.render(c)
+ #print (config_text)
+ with open('/etc/logrotate.d/vyos-rsyslog', 'w') as f:
+ f.write(config_text)
+
+def verify(c):
+ if c == None:
+ return None
+
+ fac = ['*','auth','authpriv','cron','daemon','kern','lpr','mail','mark','news','protocols','security',\
+ 'syslog','user','uucp','local0','local1','local2','local3','local4','local5','local6','local7']
+ lvl = ['emerg','alert','crit','err','warning','notice','info','debug','*']
+ for conf in c:
+ if c[conf]:
+ for item in c[conf]:
+ for s in c[conf][item]['selectors'].split(";"):
+ f = re.sub("\..*$","",s)
+ if f not in fac:
+ print (c[conf])
+ raise ConfigError('Invalid facility ' + s + ' set in '+ conf + ' ' + item)
+ l = re.sub("^.+\.","",s)
+ if l not in lvl:
+ raise ConfigError('Invalid logging level ' + s + ' set in '+ conf + ' ' + item)
+
+def apply(c):
+ ### vyatta-log.conf is being generated somewhere
+ ### this is just a quick hack to remove the old configfile
+
+ if os.path.exists('/etc/rsyslog.d/vyatta-log.conf'):
+ os.remove('/etc/rsyslog.d/vyatta-log.conf')
+ os.system("sudo systemctl restart rsyslog >/dev/null")
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/conf_mode/vrrp.py b/src/conf_mode/vrrp.py
new file mode 100755
index 000000000..155b71aa8
--- /dev/null
+++ b/src/conf_mode/vrrp.py
@@ -0,0 +1,338 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import sys
+import subprocess
+import ipaddress
+
+import jinja2
+
+import vyos.config
+import vyos.keepalived
+
+from vyos import ConfigError
+
+
+config_file = "/etc/keepalived/keepalived.conf"
+
+config_tmpl = """
+# Autogenerated by VyOS
+# Do not edit this file, all your changes will be lost
+# on next commit or reboot
+
+{% for group in groups -%}
+
+{% if group.health_check_script -%}
+vrrp_script healthcheck_{{ group.name }} {
+ script {{ group.health_check_script }}
+ interval {{ group.health_check_interval }}
+ fall {{ group.health_check_count }}
+ rise 1
+
+}
+{% endif %}
+
+vrrp_instance {{ group.name }} {
+ {% if group.description -%}
+ # {{ group.description }}
+ {% endif -%}
+
+ state BACKUP
+ interface {{ group.interface }}
+ virtual_router_id {{ group.vrid }}
+ priority {{ group.priority }}
+ advert_int {{ group.advertise_interval }}
+
+ {% if group.preempt -%}
+ preempt_delay {{ group.preempt_delay }}
+ {% else -%}
+ nopreempt
+ {% endif -%}
+
+ {% if group.peer_address -%}
+ unicast_peer { {{ group.peer_address }} }
+ {% endif -%}
+
+ {% if group.hello_source -%}
+ {%- if group.peer_address -%}
+ unicast_src_ip {{ group.hello_source }}
+ {%- else -%}
+ mcast_src_ip {{ group.hello_source }}
+ {%- endif %}
+ {% endif -%}
+
+ {% if group.use_vmac -%}
+ use_vmac {{group.interface}}v{{group.vrid}}
+ {% endif -%}
+
+ {% if group.auth_password -%}
+ authentication {
+ auth_pass {{ group.auth_password }}
+ auth_type {{ group.auth_type }}
+ }
+ {% endif -%}
+
+ virtual_ipaddress {
+ {% for addr in group.virtual_addresses -%}
+ {{ addr }}
+ {% endfor -%}
+ }
+
+ {% if group.health_check_script -%}
+ track_script {
+ healthcheck_{{ group.name }}
+ }
+ {% endif -%}
+
+ {% if group.master_script -%}
+ notify_master "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.master_script }} --state master --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+
+ {% if group.backup_script -%}
+ notify_backup "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.backup_script }} --state backup --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+
+ {% if group.fault_script -%}
+ notify_fault "/usr/libexec/vyos/system/vrrp-script-wrapper.py --script {{ group.fault_script }} --state fault --group {{ group.name }} --interface {{ group.interface }}"
+ {% endif -%}
+}
+
+{% endfor -%}
+
+{% for sync_group in sync_groups -%}
+vrrp_sync_group {{ sync_group.name }} {
+ group {
+ {% for member in sync_group.members -%}
+ {{ member }}
+ {% endfor -%}
+ }
+
+ {% if sync_group.conntrack_sync -%}
+ notify_master "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh master {{ sync_group.name }}"
+ notify_backup "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh backup {{ sync_group.name }}"
+ notify_fault "/opt/vyatta/sbin/vyatta-vrrp-conntracksync.sh fault {{ sync_group.name }}"
+ {% endif -%}
+}
+
+{% endfor -%}
+
+"""
+
+def get_config():
+ vrrp_groups = []
+ sync_groups = []
+
+ config = vyos.config.Config()
+
+ # Get the VRRP groups
+ for group_name in config.list_nodes("high-availability vrrp group"):
+ config.set_level("high-availability vrrp group {0}".format(group_name))
+
+ # Retrieve the values
+ group = {"preempt": True, "use_vmac": False, "disable": False}
+
+ if config.exists("disable"):
+ group["disable"] = True
+
+ group["name"] = group_name
+ group["vrid"] = config.return_value("vrid")
+ group["interface"] = config.return_value("interface")
+ group["description"] = config.return_value("description")
+ group["advertise_interval"] = config.return_value("advertise-interval")
+ group["priority"] = config.return_value("priority")
+ group["hello_source"] = config.return_value("hello-source-address")
+ group["peer_address"] = config.return_value("peer-address")
+ group["sync_group"] = config.return_value("sync-group")
+ group["preempt_delay"] = config.return_value("preempt-delay")
+ group["virtual_addresses"] = config.return_values("virtual-address")
+
+ group["auth_password"] = config.return_value("authentication password")
+ group["auth_type"] = config.return_value("authentication type")
+
+ group["health_check_script"] = config.return_value("health-check script")
+ group["health_check_interval"] = config.return_value("health-check interval")
+ group["health_check_count"] = config.return_value("health-check failure-count")
+
+ group["master_script"] = config.return_value("transition-script master")
+ group["backup_script"] = config.return_value("transition-script backup")
+ group["fault_script"] = config.return_value("transition-script fault")
+
+ if config.exists("no-preempt"):
+ group["preempt"] = False
+ if config.exists("rfc3768-compatibility"):
+ group["use_vmac"] = True
+
+ # Substitute defaults where applicable
+ if not group["advertise_interval"]:
+ group["advertise_interval"] = 1
+ if not group["priority"]:
+ group["priority"] = 100
+ if not group["preempt_delay"]:
+ group["preempt_delay"] = 5 * 60
+ if not group["health_check_interval"]:
+ group["health_check_interval"] = 60
+ if not group["health_check_count"]:
+ group["health_check_count"] = 3
+
+ # FIXUP: translate our option for auth type to keepalived's syntax
+ # for simplicity
+ if group["auth_type"]:
+ if group["auth_type"] == "plaintext-password":
+ group["auth_type"] = "PASS"
+ else:
+ group["auth_type"] = "AH"
+
+ vrrp_groups.append(group)
+
+ config.set_level("")
+
+ # Get the sync group used for conntrack-sync
+ conntrack_sync_group = None
+ if config.exists("service conntrack-sync failover-mechanism vrrp"):
+ conntrack_sync_group = config.return_value("service conntrack-sync failover-mechanism vrrp sync-group")
+
+ # Get the sync groups
+ for sync_group_name in config.list_nodes("high-availability vrrp sync-group"):
+ config.set_level("high-availability vrrp sync-group {0}".format(sync_group_name))
+
+ sync_group = {"conntrack_sync": False}
+ sync_group["name"] = sync_group_name
+ sync_group["members"] = config.return_values("member")
+ if conntrack_sync_group:
+ if conntrack_sync_group == sync_group_name:
+ sync_group["conntrack_sync"] = True
+
+ sync_groups.append(sync_group)
+
+ return (vrrp_groups, sync_groups)
+
+def verify(data):
+ vrrp_groups, sync_groups = data
+
+ for group in vrrp_groups:
+ # Check required fields
+ if not group["vrid"]:
+ raise ConfigError("vrid is required but not set in VRRP group {0}".format(group["name"]))
+ if not group["interface"]:
+ raise ConfigError("interface is required but not set in VRRP group {0}".format(group["name"]))
+ if not group["virtual_addresses"]:
+ raise ConfigError("virtual-address is required but not set in VRRP group {0}".format(group["name"]))
+
+ if group["auth_password"] and (not group["auth_type"]):
+ raise ConfigError("authentication type is required but not set in VRRP group {0}".format(group["name"]))
+
+ # Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
+
+ # XXX: filter on map object is destructive, so we force it to list.
+ # Additionally, filter objects always evaluate to True, empty or not,
+ # so we force them to lists as well.
+ vaddrs = list(map(lambda i: ipaddress.ip_interface(i), group["virtual_addresses"]))
+ vaddrs4 = list(filter(lambda x: isinstance(x, ipaddress.IPv4Interface), vaddrs))
+ vaddrs6 = list(filter(lambda x: isinstance(x, ipaddress.IPv6Interface), vaddrs))
+
+ if vaddrs4 and vaddrs6:
+ raise ConfigError("VRRP group {0} mixes IPv4 and IPv6 virtual addresses, this is not allowed. Create separate groups for IPv4 and IPv6".format(group["name"]))
+
+ if vaddrs4:
+ if group["hello_source"]:
+ hsa = ipaddress.ip_address(group["hello_source"])
+ if isinstance(hsa, ipaddress.IPv6Address):
+ raise ConfigError("VRRP group {0} uses IPv4 but its hello-source-address is IPv6".format(group["name"]))
+ if group["peer_address"]:
+ pa = ipaddress.ip_address(group["peer_address"])
+ if isinstance(hsa, ipaddress.IPv6Address):
+ raise ConfigError("VRRP group {0} uses IPv4 but its peer-address is IPv6".format(group["name"]))
+
+ if vaddrs6:
+ if group["hello_source"]:
+ hsa = ipaddress.ip_address(group["hello_source"])
+ if isinstance(hsa, ipaddress.IPv4Address):
+ raise ConfigError("VRRP group {0} uses IPv6 but its hello-source-address is IPv4".format(group["name"]))
+ if group["peer_address"]:
+ pa = ipaddress.ip_address(group["peer_address"])
+ if isinstance(hsa, ipaddress.IPv4Address):
+ raise ConfigError("VRRP group {0} uses IPv6 but its peer-address is IPv4".format(group["name"]))
+
+ # Disallow same VRID on multiple interfaces
+ _groups = sorted(vrrp_groups, key=(lambda x: x["interface"]))
+ count = len(_groups) - 1
+ index = 0
+ while (index < count):
+ if _groups[index]["vrid"] == _groups[index + 1]["vrid"]:
+ raise ConfigError("VRID {0} is used in groups {1} and {2} that both use interface {3}. Groups on the same interface must use different VRIDs".format(
+ _groups[index]["vrid"], _groups[index]["name"], _groups[index + 1]["name"], _groups[index]["interface"]))
+ else:
+ index += 1
+
+ # Check sync groups
+ vrrp_group_names = list(map(lambda x: x["name"], vrrp_groups))
+
+ for sync_group in sync_groups:
+ for m in sync_group["members"]:
+ if not (m in vrrp_group_names):
+ raise ConfigError("VRRP sync-group {0} refers to VRRP group {1}, but group {1} does not exist".format(sync_group["name"], m))
+
+def generate(data):
+ vrrp_groups, sync_groups = data
+
+ # Remove disabled groups from the sync group member lists
+ for sync_group in sync_groups:
+ for member in sync_group["members"]:
+ g = list(filter(lambda x: x["name"] == member, vrrp_groups))[0]
+ if g["disable"]:
+ print("Warning: ignoring disabled VRRP group {0} in sync-group {1}".format(g["name"], sync_group["name"]))
+ # Filter out disabled groups
+ vrrp_groups = list(filter(lambda x: x["disable"] != True, vrrp_groups))
+
+ tmpl = jinja2.Template(config_tmpl)
+ config_text = tmpl.render({"groups": vrrp_groups, "sync_groups": sync_groups})
+
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+ return None
+
+def apply(data):
+ vrrp_groups, sync_groups = data
+ if vrrp_groups:
+ if not vyos.keepalived.vrrp_running():
+ print("Starting the VRRP process")
+ ret = subprocess.call("sudo systemctl restart keepalived.service", shell=True)
+ else:
+ print("Reloading the VRRP process")
+ ret = subprocess.call("sudo systemctl reload keepalived.service", shell=True)
+
+ if ret != 0:
+ raise ConfigError("keepalived failed to start")
+ else:
+ # VRRP is removed in the commit
+ print("Stopping the VRRP process")
+ subprocess.call("sudo systemctl stop keepalived.service", shell=True)
+ os.unlink(config_file)
+
+ return None
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print("VRRP error: {0}".format(str(e)))
+ sys.exit(1)
diff --git a/src/migration-scripts/vrrp/1-to-2 b/src/migration-scripts/vrrp/1-to-2
new file mode 100755
index 000000000..b2e61dd38
--- /dev/null
+++ b/src/migration-scripts/vrrp/1-to-2
@@ -0,0 +1,270 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import re
+import sys
+
+from vyos.configtree import ConfigTree
+
+
+if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+# Convert the old VRRP syntax to the new syntax
+
+# The old approach was to put VRRP groups inside interfaces,
+# as in "interfaces ethernet eth0 vrrp vrrp-group 10 ...".
+# It was supported only under ethernet and bonding and their
+# respective vif, vif-s, and vif-c subinterfaces
+
+def get_vrrp_group(path):
+ group = {"preempt": True, "rfc_compatibility": False, "disable": False}
+
+ if config.exists(path + ["advertise-interval"]):
+ group["advertise_interval"] = config.return_value(path + ["advertise-interval"])
+
+ if config.exists(path + ["description"]):
+ group["description"] = config.return_value(path + ["description"])
+
+ if config.exists(path + ["disable"]):
+ group["disable"] = True
+
+ if config.exists(path + ["hello-source-address"]):
+ group["hello_source"] = config.return_value(path + ["hello-source-address"])
+
+ # 1.1.8 didn't have it, but earlier 1.2.0 did, we don't want to break
+ # configs of early adopters!
+ if config.exists(path + ["peer-address"]):
+ group["peer_address"] = config.return_value(path + ["peer-address"])
+
+ if config.exists(path + ["preempt"]):
+ preempt = config.return_value(path + ["preempt"])
+ if preempt == "false":
+ group["preempt"] = False
+
+ if config.exists(path + ["rfc3768-compatibility"]):
+ group["rfc_compatibility"] = True
+
+ if config.exists(path + ["preempt-delay"]):
+ group["preempt_delay"] = config.return_value(path + ["preempt-delay"])
+
+ if config.exists(path + ["priority"]):
+ group["priority"] = config.return_value(path + ["priority"])
+
+ if config.exists(path + ["sync-group"]):
+ group["sync_group"] = config.return_value(path + ["sync-group"])
+
+ if config.exists(path + ["authentication", "type"]):
+ group["auth_type"] = config.return_value(path + ["authentication", "type"])
+
+ if config.exists(path + ["authentication", "password"]):
+ group["auth_password"] = config.return_value(path + ["authentication", "password"])
+
+ if config.exists(path + ["virtual-address"]):
+ group["virtual_addresses"] = config.return_values(path + ["virtual-address"])
+
+ if config.exists(path + ["run-transition-scripts"]):
+ if config.exists(path + ["run-transition-scripts", "master"]):
+ group["master_script"] = config.return_value(path + ["run-transition-scripts", "master"])
+ if config.exists(path + ["run-transition-scripts", "backup"]):
+ group["backup_script"] = config.return_value(path + ["run-transition-scripts", "backup"])
+ if config.exists(path + ["run-transition-scripts", "fault"]):
+ group["fault_script"] = config.return_value(path + ["run-transition-scripts", "fault"])
+
+ # Also not present in 1.1.8, but supported by earlier 1.2.0
+ if config.exists(path + ["health-check"]):
+ if config.exists(path + ["health-check", "interval"]):
+ group["health_check_interval"] = config.return_value(path + ["health-check", "interval"])
+ if config.exists(path + ["health-check", "failure-count"]):
+ group["health_check_count"] = config.return_value(path + ["health-check", "failure-count"])
+ if config.exists(path + ["health-check", "script"]):
+ group["health_check_script"] = config.return_value(path + ["health-check", "script"])
+
+ return group
+
+# Since VRRP is all over the place, there's no way to just check a path and exit early
+# if it doesn't exist, we have to walk all interfaces and collect VRRP settings from them.
+# Only if no data is collected from any interface we can conclude that VRRP is not configured
+# and exit.
+
+groups = []
+base_paths = []
+
+if config.exists(["interfaces", "ethernet"]):
+ base_paths.append("ethernet")
+if config.exists(["interfaces", "bonding"]):
+ base_paths.append("bonding")
+
+for bp in base_paths:
+ parent_path = ["interfaces", bp]
+
+ parent_intfs = config.list_nodes(parent_path)
+
+ for pi in parent_intfs:
+ # Extract VRRP groups from the parent interface
+ vg_path =[pi, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vg_path):
+ pgroups = config.list_nodes(parent_path + vg_path)
+ for pg in pgroups:
+ g = get_vrrp_group(parent_path + vg_path + [pg])
+ g["interface"] = pi
+ g["vrid"] = pg
+ groups.append(g)
+
+ # Delete the VRRP subtree
+ # If left in place, configs will not load correctly
+ config.delete(parent_path + [pi, "vrrp"])
+
+ # Extract VRRP groups from 802.1q VLAN interfaces
+ if config.exists(parent_path + [pi, "vif"]):
+ vifs = config.list_nodes(parent_path + [pi, "vif"])
+ for vif in vifs:
+ vif_vg_path = [pi, "vif", vif, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vif_vg_path):
+ vifgroups = config.list_nodes(parent_path + vif_vg_path)
+ for vif_group in vifgroups:
+ g = get_vrrp_group(parent_path + vif_vg_path + [vif_group])
+ g["interface"] = "{0}.{1}".format(pi, vif)
+ g["vrid"] = vif_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif", vif, "vrrp"])
+
+ # Extract VRRP groups from 802.3ad QinQ service VLAN interfaces
+ if config.exists(parent_path + [pi, "vif-s"]):
+ vif_ss = config.list_nodes(parent_path + [pi, "vif-s"])
+ for vif_s in vif_ss:
+ vifs_vg_path = [pi, "vif-s", vif_s, "vrrp", "vrrp-group"]
+ if config.exists(parent_path + vifs_vg_path):
+ vifsgroups = config.list_nodes(parent_path + vifs_vg_path)
+ for vifs_group in vifsgroups:
+ g = get_vrrp_group(parent_path + vifs_vg_path + [vifs_group])
+ g["interface"] = "{0}.{1}".format(pi, vif_s)
+ g["vrid"] = vifs_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif-s", vif_s, "vrrp"])
+
+ # Extract VRRP groups from QinQ client VLAN interfaces nested in the vif-s
+ if config.exists(parent_path + [pi, "vif-s", vif_s, "vif-c"]):
+ vif_cs = config.list_nodes(parent_path + [pi, "vif-s", vif_s, "vif-c"])
+ for vif_c in vif_cs:
+ vifc_vg_path = [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp", "vrrp-group"]
+ vifcgroups = config.list_nodes(parent_path + vifc_vg_path)
+ for vifc_group in vifcgroups:
+ g = get_vrrp_group(parent_path + vifc_vg_path + [vifc_group])
+ g["interface"] = "{0}.{1}.{2}".format(pi, vif_s, vif_c)
+ g["vrid"] = vifc_group
+ groups.append(g)
+
+ config.delete(parent_path + [pi, "vif-s", vif_s, "vif-c", vif_c, "vrrp"])
+
+# If nothing was collected before this point, it means the config has no VRRP setup
+if not groups:
+ sys.exit(0)
+
+# Otherwise, there is VRRP to convert
+
+# Now convert the collected groups to the new syntax
+base_group_path = ["high-availability", "vrrp", "group"]
+sync_path = ["high-availability", "vrrp", "sync-group"]
+
+for g in groups:
+ group_name = "{0}-{1}".format(g["interface"], g["vrid"])
+ group_path = base_group_path + [group_name]
+
+ config.set(group_path + ["interface"], value=g["interface"])
+ config.set(group_path + ["vrid"], value=g["vrid"])
+
+ if "advertise_interval" in g:
+ config.set(group_path + ["advertise-interval"], value=g["advertise_interval"])
+
+ if "priority" in g:
+ config.set(group_path + ["priority"], value=g["priority"])
+
+ if not g["preempt"]:
+ config.set(group_path + ["no-preempt"], value=None)
+
+ if "preempt_delay" in g:
+ config.set(group_path + ["preempt-delay"], value=g["preempt_delay"])
+
+ if g["rfc_compatibility"]:
+ config.set(group_path + ["rfc3768-compatibility"], value=None)
+
+ if g["disable"]:
+ config.set(group_path + ["disable"], value=None)
+
+ if "hello_source" in g:
+ config.set(group_path + ["hello-source-address"], value=g["hello_source"])
+
+ if "peer_address" in g:
+ config.set(group_path + ["peer-address"], value=g["peer_address"])
+
+ if "auth_password" in g:
+ config.set(group_path + ["authentication", "password"], value=g["auth_password"])
+ if "auth_type" in g:
+ config.set(group_path + ["authentication", "type"], value=g["auth_type"])
+
+ if "master_script" in g:
+ config.set(group_path + ["transition-script", "master"], value=g["master_script"])
+ if "backup_script" in g:
+ config.set(group_path + ["transition-script", "backup"], value=g["backup_script"])
+ if "fault_script" in g:
+ config.set(group_path + ["transition-script", "fault"], value=g["fault_script"])
+
+ if "health_check_interval" in g:
+ config.set(group_path + ["health-check", "interval"], value=g["health_check_interval"])
+ if "health_check_count" in g:
+ config.set(group_path + ["health-check", "failure-count"], value=g["health_check_count"])
+ if "health_check_script" in g:
+ config.set(group_path + ["health-check", "script"], value=g["health_check_script"])
+
+ # Not that it should ever be absent...
+ if "virtual_addresses" in g:
+ # The new CLI disallows addresses without prefix length
+ # Pre-rewrite configs didn't support IPv6 VRRP, but handle it anyway
+ for va in g["virtual_addresses"]:
+ if not re.search(r'/', va):
+ if re.search(r':', va):
+ va = "{0}/128".format(va)
+ else:
+ va = "{0}/32".format(va)
+ config.set(group_path + ["virtual-address"], value=va, replace=False)
+
+ # Sync group
+ if "sync_group" in g:
+ config.set(sync_path + [g["sync_group"], "member"], value=group_name, replace=False)
+
+# Set the tag flag
+config.set_tag(base_group_path)
+if config.exists(sync_path):
+ config.set_tag(sync_path)
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/op_mode/dns_forwarding_restart.sh b/src/op_mode/dns_forwarding_restart.sh
index 12106fcc1..8e556f2f0 100755
--- a/src/op_mode/dns_forwarding_restart.sh
+++ b/src/op_mode/dns_forwarding_restart.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-if cli-shell-api exists service dns forwarding; then
+if cli-shell-api existsEffective service dns forwarding; then
echo "Restarting the DNS forwarding service"
systemctl restart pdns-recursor
else
diff --git a/src/op_mode/show-configuration-files.sh b/src/op_mode/show_configuration_files.sh
index ad8e0747c..ad8e0747c 100755
--- a/src/op_mode/show-configuration-files.sh
+++ b/src/op_mode/show_configuration_files.sh
diff --git a/src/op_mode/show-disk-format.sh b/src/op_mode/show_disk_format.sh
index 61b15a52b..61b15a52b 100755
--- a/src/op_mode/show-disk-format.sh
+++ b/src/op_mode/show_disk_format.sh
diff --git a/src/op_mode/show-raid.sh b/src/op_mode/show_raid.sh
index ba4174692..ba4174692 100755
--- a/src/op_mode/show-raid.sh
+++ b/src/op_mode/show_raid.sh
diff --git a/src/op_mode/vrrp.py b/src/op_mode/vrrp.py
new file mode 100755
index 000000000..ba8b56de3
--- /dev/null
+++ b/src/op_mode/vrrp.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import sys
+import time
+import argparse
+
+import tabulate
+
+import vyos.keepalived
+import vyos.util
+
+
+def print_summary():
+ try:
+ vyos.keepalived.force_json_dump()
+ # Wait for keepalived to produce the data
+ # Replace with inotify or similar if it proves problematic
+ time.sleep(0.2)
+ json_data = vyos.keepalived.get_json_data()
+ except:
+ print("VRRP information is not available")
+
+ groups = []
+ for group in json_data:
+ data = group["data"]
+
+ name = data["iname"]
+
+ ltrans_timestamp = float(data["last_transition"])
+ ltrans_time = vyos.util.seconds_to_human(int(time.time() - ltrans_timestamp))
+
+ interface = data["ifp_ifname"]
+ vrid = data["vrid"]
+
+ state = vyos.keepalived.decode_state(data["state"])
+
+ row = [name, interface, vrid, state, ltrans_time]
+ groups.append(row)
+
+ headers = ["Name", "Interface", "VRID", "State", "Last Transition"]
+ output = tabulate.tabulate(groups, headers)
+ print(output)
+
+def print_statistics():
+ try:
+ vyos.keepalived.force_stats_dump()
+ time.sleep(0.2)
+ output = vyos.keepalived.get_statistics()
+ print(output)
+ except:
+ print("VRRP statistics are not available")
+
+def print_state_data():
+ try:
+ vyos.keepalived.force_state_data_dump()
+ time.sleep(0.2)
+ output = vyos.keepalived.get_state_data()
+ print(output)
+ except:
+ print("VRRP information is not available")
+
+parser = argparse.ArgumentParser()
+group = parser.add_mutually_exclusive_group()
+group.add_argument("-s", "--summary", action="store_true", help="Print VRRP summary")
+group.add_argument("-t", "--statistics", action="store_true", help="Print VRRP statistics")
+group.add_argument("-d", "--data", action="store_true", help="Print detailed VRRP data")
+
+args = parser.parse_args()
+
+# Exit early if VRRP is dead or not configured
+if not vyos.keepalived.vrrp_running():
+ print("VRRP is not running")
+ sys.exit(0)
+
+if args.summary:
+ print_summary()
+elif args.statistics:
+ print_statistics()
+elif args.data:
+ print_state_data()
+else:
+ parser.print_help()
+ sys.exit(1)
diff --git a/src/system/vrrp-script-wrapper.py b/src/system/vrrp-script-wrapper.py
new file mode 100755
index 000000000..5d6aa6c55
--- /dev/null
+++ b/src/system/vrrp-script-wrapper.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import sys
+import subprocess
+import argparse
+import syslog
+
+import vyos.util
+import vyos.keepalived
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument("-s", "--script", type=str, help="Script to run")
+parser.add_argument("-t", "--state", type=str, help="VRRP state")
+parser.add_argument("-g", "--group", type=str, help="VRRP group")
+parser.add_argument("-i", "--interface", type=str, help="Network interface")
+
+syslog.openlog('vyos-vrrp-wrapper')
+
+args = parser.parse_args()
+if not args.script or not args.state or not args.group \
+ or not args.interface:
+ parser.print_usage()
+ sys.exit(1)
+
+# Get the old state if it exists and compare it to the current state received
+# in command line options to avoid executing scripts if no real transition occured.
+# This is necessary because keepalived does not keep persistent state data even between
+# config reloads and will cheerfully execute everything whether it's required or not.
+
+old_state = vyos.keepalived.get_old_state(args.group)
+
+if (old_state is None) or (old_state != args.state):
+ exitcode = 0
+
+ # Run the script and save the new state
+
+ # Change the process GID to the config owners group to avoid screwing up
+ # running config permissions
+ os.setgid(vyos.util.get_cfg_group_id())
+
+ syslog.syslog(syslog.LOG_NOTICE, 'Running transition script {0} for VRRP group {1}'.format(args.script, args.group))
+ try:
+ ret = subprocess.call([args.script, args.state, args.interface, args.group])
+ if ret != 0:
+ syslog.syslog(syslog.LOG_ERR, "Transition script {0} failed, exit status: {1}".format(args.script, ret))
+ exitcode = ret
+ except Exception as e:
+ syslog.syslog(syslog.LOG_ERR, "Failed to execute transition script {0}: {1}".format(args.script, e))
+ exitcode = 1
+
+ if exitcode == 0:
+ syslog.syslog(syslog.LOG_NOTICE, "Transition script {0} executed successfully".format(args.script))
+
+ vyos.keepalived.save_state(args.group, args.state)
+else:
+ syslog.syslog(syslog.LOG_NOTICE, "State of the group {0} has not changed, not running transition script".format(args.group))
+
+syslog.closelog()
+sys.exit(exitcode)
diff --git a/src/tests/test_initial_setup.py b/src/tests/test_initial_setup.py
new file mode 100644
index 000000000..023a30723
--- /dev/null
+++ b/src/tests/test_initial_setup.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import tempfile
+import unittest
+from unittest import TestCase, mock
+
+import vyos.configtree
+import vyos.initialsetup as vis
+
+
+class TestHostName(TestCase):
+ def setUp(self):
+ with open('tests/data/config.boot.default', 'r') as f:
+ config_string = f.read()
+ self.config = vyos.configtree.ConfigTree(config_string)
+
+ def test_set_user_password(self):
+ vis.set_user_password(self.config, 'vyos', 'vyosvyos')
+
+ # Old password hash from the default config
+ old_pw = '$6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/'
+ new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"])
+
+ # Just check it changed the hash, don't try to check if hash is good
+ self.assertNotEqual(old_pw, new_pw)
+
+ def test_disable_user_password(self):
+ vis.disable_user_password(self.config, 'vyos')
+ new_pw = self.config.return_value(["system", "login", "user", "vyos", "authentication", "encrypted-password"])
+
+ self.assertEqual(new_pw, '!')
+
+ def test_set_ssh_key_with_name(self):
+ test_ssh_key = " ssh-rsa fakedata vyos@vyos "
+ vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key)
+
+ key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "type"])
+ key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos@vyos", "key"])
+
+ self.assertEqual(key_type, 'ssh-rsa')
+ self.assertEqual(key_data, 'fakedata')
+ self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"]))
+
+ def test_set_ssh_key_without_name(self):
+ # If key file doesn't include a name, the function will use user name for the key name
+
+ test_ssh_key = " ssh-rsa fakedata "
+ vis.set_user_ssh_key(self.config, 'vyos', test_ssh_key)
+
+ key_type = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "type"])
+ key_data = self.config.return_value(["system", "login", "user", "vyos", "authentication", "public-keys", "vyos", "key"])
+
+ self.assertEqual(key_type, 'ssh-rsa')
+ self.assertEqual(key_data, 'fakedata')
+ self.assertTrue(self.config.is_tag(["system", "login", "user", "vyos", "authentication", "public-keys"]))
+
+ def test_create_user(self):
+ vis.create_user(self.config, 'jrandomhacker', password='qwerty', key=" ssh-rsa fakedata jrandomhacker@foovax ")
+
+ self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker"]))
+ self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "public-keys", "jrandomhacker@foovax"]))
+ self.assertTrue(self.config.exists(["system", "login", "user", "jrandomhacker", "authentication", "encrypted-password"]))
+ self.assertEqual(self.config.return_value(["system", "login", "user", "jrandomhacker", "level"]), "admin")
+
+ def test_set_hostname(self):
+ vis.set_host_name(self.config, "vyos-test")
+
+ self.assertEqual(self.config.return_value(["system", "host-name"]), "vyos-test")
+
+ def test_set_name_servers(self):
+ vis.set_name_servers(self.config, ["192.0.2.10", "203.0.113.20"])
+ servers = self.config.return_values(["system", "name-server"])
+
+ self.assertIn("192.0.2.10", servers)
+ self.assertIn("203.0.113.20", servers)
+
+ def test_set_gateway(self):
+ vis.set_default_gateway(self.config, '192.0.2.1')
+
+ self.assertTrue(self.config.exists(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop', '192.0.2.1']))
+ self.assertTrue(self.config.is_tag(['protocols', 'static', 'route', '0.0.0.0/0', 'next-hop']))
+ self.assertTrue(self.config.is_tag(['protocols', 'static', 'route']))
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/src/validators/numeric b/src/validators/numeric
index 58a4fac38..ffe84a234 100755
--- a/src/validators/numeric
+++ b/src/validators/numeric
@@ -25,7 +25,8 @@ parser = argparse.ArgumentParser()
parser.add_argument("-f", "--float", action="store_true", help="Accept floating point values")
group = parser.add_mutually_exclusive_group()
group.add_argument("-r", "--range", type=str, help="Check if the number is within range (inclusive), example: 1024-65535")
-group.add_argument("-n", "--non-negative", action="store_true", help="")
+group.add_argument("-n", "--non-negative", action="store_true", help="Check if the number is non-negative (>= 0)")
+group.add_argument("-p", "--positive", action="store_true", help="Check if the number is positive (> 0)")
parser.add_argument("number", type=str, help="Number to validate")
args = parser.parse_args()
@@ -60,3 +61,8 @@ elif args.non_negative:
if number < 0:
print("Number should be non-negative", file=sys.stderr)
sys.exit(1)
+elif args.positive:
+ if number <= 0:
+ print("Number should be positive", file=sys.stderr)
+ sys.exit(1)
+
diff --git a/src/validators/script b/src/validators/script
new file mode 100755
index 000000000..beeba57ae
--- /dev/null
+++ b/src/validators/script
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+#
+# numeric value validator
+#
+# Copyright (C) 2018 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import sys
+
+import vyos.util
+
+
+if len(sys.argv) < 2:
+ print("Please specify script file to check")
+ sys.exit(1)
+
+script = sys.argv[1]
+
+if not os.path.exists(script):
+ print("File {0} does not exist".format(script))
+ sys.exit(1)
+
+if not (os.path.isfile(script) and os.access(script, os.X_OK)):
+ print("File {0} is not an executable file".format(script))
+ sys.exit(1)
+
+# File outside the config dir is just a warning
+res, warning = vyos.util.file_is_persistent(script)
+if not res:
+ print(warning)
diff --git a/tests/data/config.boot.default b/tests/data/config.boot.default
new file mode 100644
index 000000000..0a75716b8
--- /dev/null
+++ b/tests/data/config.boot.default
@@ -0,0 +1,40 @@
+system {
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$QxPS.uk6mfo$9QBSo8u1FkH16gMyAVhus6fU3LOzvLR9Z9.82m3tiHFAxTtIkhaZSWssSgzt4v4dGAL8rhVQxTg0oAG9/q11h/
+ plaintext-password ""
+ }
+ level admin
+ }
+ }
+ syslog {
+ global {
+ facility all {
+ level notice
+ }
+ facility protocols {
+ level debug
+ }
+ }
+ }
+ ntp {
+ server "0.pool.ntp.org"
+ server "1.pool.ntp.org"
+ server "2.pool.ntp.org"
+ }
+ console {
+ device ttyS0 {
+ speed 9600
+ }
+ }
+ config-management {
+ commit-revisions 100
+ }
+}
+
+interfaces {
+ loopback lo {
+ }
+}