summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2018-06-04 20:25:30 +0200
committerChristian Poessinger <christian@poessinger.com>2018-06-04 20:25:30 +0200
commitef8f08812efbc85a8f4d53e3770c5bd0047e9d89 (patch)
tree0b85ef73647f4c1d59e6cdfcf92f3079d36bd446
parentf1601cde2d66d6a4bf19d1d46f3c22de026d17e4 (diff)
parent6900ac9213903f239f915624553d986f4fdbe0d4 (diff)
downloadvyos-1x-ef8f08812efbc85a8f4d53e3770c5bd0047e9d89.tar.gz
vyos-1x-ef8f08812efbc85a8f4d53e3770c5bd0047e9d89.zip
Merge branch 't652-rewrite-snmp' into current
* t652-rewrite-snmp: T652: Add SNMPv3 TSM handling and commit verification T655: Add support for SNMPv3 'noAuthNoPriv' security level T652: read SNMPv3 config into python dictionary snmp.py: refactor listen-address config generation T654: Support IPv6 configuration for SNMP listen address T652: first SNMP version using XML interface definition
-rw-r--r--interface-definitions/snmp.xml606
-rwxr-xr-xsrc/conf_mode/snmp.py709
2 files changed, 1315 insertions, 0 deletions
diff --git a/interface-definitions/snmp.xml b/interface-definitions/snmp.xml
new file mode 100644
index 000000000..7928de5d7
--- /dev/null
+++ b/interface-definitions/snmp.xml
@@ -0,0 +1,606 @@
+<?xml version="1.0"?>
+<!-- SNMP forwarder configuration -->
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="snmp" owner="${vyos_conf_scripts_dir}/snmp.py">
+ <properties>
+ <help>Simple Network Management Protocol (SNMP)</help>
+ <priority>980</priority>
+ </properties>
+ <children>
+ <tagNode name="community">
+ <properties>
+ <help>Community name [REQUIRED]</help>
+ <constraint>
+ <regex>^[^%]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Community string may not contain '%'</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="authorization">
+ <properties>
+ <help>Authorization type (default: 'ro')</help>
+ <valueHelp>
+ <format>ro</format>
+ <description>read only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rw</format>
+ <description>read write</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ro|rw)</regex>
+ </constraint>
+ <constraintErrorMessage>Authorization type must be either 'rw' or 'ro'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="client">
+ <properties>
+ <help>IP address of SNMP client allowed to contact system</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="network">
+ <properties>
+ <help>Subnet of SNMP client(s) allowed to contact system</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IP address and prefix length</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 address and prefix length</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-prefix"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="contact">
+ <properties>
+ <help>Contact information</help>
+ <constraint>
+ <regex>.{1,255}</regex>
+ </constraint>
+ <constraintErrorMessage>Contact information is limited to 255 characters or less</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="description">
+ <properties>
+ <help>Description information</help>
+ <constraint>
+ <regex>.{1,255}</regex>
+ </constraint>
+ <constraintErrorMessage>Description is limited to 255 characters or less</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <tagNode name="listen-address">
+ <properties>
+ <help>IP address to listen for incoming SNMP requests</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address to listen for incoming SNMP requests</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address to listen for incoming SNMP requests</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="port">
+ <properties>
+ <help>Port for SNMP service (default: '161')</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="location">
+ <properties>
+ <help>Location information</help>
+ <constraint>
+ <regex>.{1,255}</regex>
+ </constraint>
+ <constraintErrorMessage>Location is limited to 255 characters or less</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="smux-peer">
+ <properties>
+ <help>Register a subtree for SMUX-based processing</help>
+ <valueHelp>
+ <format>oid</format>
+ <description>Object Identifier</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="trap-source">
+ <properties>
+ <help>SNMP trap source address</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <tagNode name="trap-target">
+ <properties>
+ <help>Address of trap target</help>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="community">
+ <properties>
+ <help>Community used when sending trap information</help>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Destination port used for trap notification</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="v3">
+ <properties>
+ <help>Simple Network Management Protocol (SNMP) v3</help>
+ </properties>
+ <children>
+ <leafNode name="engineid">
+ <properties>
+ <help>Specifies the EngineID that uniquely identify an agent (e.g. 0xff42)</help>
+ <constraint>
+ <regex>^(0x){0,1}([0-9a-f][0-9a-f]){1,18}$</regex>
+ </constraint>
+ <constraintErrorMessage>ID must contain an even number (from 2 to 36) of hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <tagNode name="group">
+ <properties>
+ <help>Specifies the group with name groupname</help>
+ </properties>
+ <children>
+ <leafNode name="mode">
+ <properties>
+ <help>Define group access permission (default: 'ro')</help>
+ <valueHelp>
+ <format>ro</format>
+ <description>read only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rw</format>
+ <description>read write</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ro|rw)</regex>
+ </constraint>
+ <constraintErrorMessage>Authorization type must be either 'rw' or 'ro'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="seclevel">
+ <properties>
+ <help>Security levels</help>
+ <valueHelp>
+ <format>noauth</format>
+ <description>Messages not authenticated and not encrypted (noAuthNoPriv)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auth</format>
+ <description>Messages are authenticated but not encrypted (AuthNoPriv)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>priv</format>
+ <description>Messages are authenticated and encrypted (AuthPriv)</description>
+ </valueHelp>
+ <constraint>
+ <regex>(noauth|auth|priv)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="view">
+ <properties>
+ <help>Defines the name of view</help>
+ <completionHelp>
+ <path>service snmp v3 view</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <tagNode name="trap-target">
+ <properties>
+ <help>Defines SNMP target for inform or traps for IP</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of trap target</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of trap target</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="auth">
+ <properties>
+ <help>Defines the privacy</help>
+ </properties>
+ <children>
+ <leafNode name="encrypted-key">
+ <properties>
+ <help>Defines the encrypted key for authentication</help>
+ <constraint>
+ <regex>^0x[0-9a-f]*$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must start from '0x' and contain hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="plaintext-key">
+ <properties>
+ <help>Defines the clear text key for authentication</help>
+ <constraint>
+ <regex>^.{8,}$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Defines the protocol used for authentication (default: 'md5')</help>
+ <valueHelp>
+ <format>md5</format>
+ <description>Message Digest 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha</format>
+ <description>Secure Hash Algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(md5|sha)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="engineid">
+ <properties>
+ <help>Specifies the EngineID that uniquely identify an agent (e.g. 0xff42)</help>
+ <constraint>
+ <regex>^(0x){0,1}([0-9a-f][0-9a-f]){1,18}$</regex>
+ </constraint>
+ <constraintErrorMessage>ID must contain from 2 to 36 hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Specifies TCP/UDP port of destination SNMP traps/informs (default: '162')</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="privacy">
+ <properties>
+ <help>Defines the privacy</help>
+ </properties>
+ <children>
+ <leafNode name="encrypted-key">
+ <properties>
+ <help>Defines the encrypted key for privacy protocol</help>
+ <constraint>
+ <regex>^0x[0-9a-f]*$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must start from '0x' and contain hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="plaintext-key">
+ <properties>
+ <help>Defines the clear text key for privacy protocol</help>
+ <constraint>
+ <regex>^.{8,}$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Defines the protocol for privacy (default: 'des')</help>
+ <valueHelp>
+ <format>des</format>
+ <description>Data Encryption Standard</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes</format>
+ <description>Advanced Encryption Standard</description>
+ </valueHelp>
+ <constraint>
+ <regex>(des|aes)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="protocol">
+ <properties>
+ <help>Defines protocol for notification between TCP and UDP</help>
+ <valueHelp>
+ <format>tcp</format>
+ <description>Use Transmission Control Protocol for notifications</description>
+ </valueHelp>
+ <valueHelp>
+ <format>udp</format>
+ <description>Use User Datagram Protocol for notifications</description>
+ </valueHelp>
+ <constraint>
+ <regex>(tcp|udp)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Specifies the type of notification between inform and trap (default: 'inform')</help>
+ <valueHelp>
+ <format>inform</format>
+ <description>Use INFORM</description>
+ </valueHelp>
+ <valueHelp>
+ <format>trap</format>
+ <description>Use TRAP</description>
+ </valueHelp>
+ <constraint>
+ <regex>(inform|trap)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="user">
+ <properties>
+ <help>Defines username for authentication</help>
+ <completionHelp>
+ <path>service snmp v3 user</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="tsm">
+ <properties>
+ <help>Specifies that SNMPv3 uses the Transport Security Model (TSM)</help>
+ </properties>
+ <children>
+ <leafNode name="local-key">
+ <properties>
+ <help>Fingerprint of a TSM server certificate</help>
+ <constraint>
+ <regex>^[0-9A-F]{2}(:[0-9A-F]{2}){19}$</regex>
+ </constraint>
+ <constraintErrorMessage>Value can be finger print key or filename in /config/snmp/tls/certs</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="port">
+ <properties>
+ <help>Defines the port used for TSM (default: '10161')</help>
+ <valueHelp>
+ <format>1-65535</format>
+ <description>Numeric IP port</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65535"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1 to 65535</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <tagNode name="user">
+ <properties>
+ <help>Specifies the user with name username</help>
+ <constraint>
+ <regex>^[^\(\)\|\-]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Illegal characters in name</constraintErrorMessage>
+ </properties>
+ <children>
+ <node name="auth">
+ <properties>
+ <help>Specifies the auth</help>
+ </properties>
+ <children>
+ <leafNode name="encrypted-key">
+ <properties>
+ <help>Defines the encrypted key for authentication</help>
+ <constraint>
+ <regex>^0x[0-9a-f]*$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must start from '0x' and contain hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="plaintext-key">
+ <properties>
+ <help>Defines the clear text key for authentication</help>
+ <constraint>
+ <regex>^.{8,}$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Defines the protocol used for authentication (default: 'md5')</help>
+ <valueHelp>
+ <format>md5</format>
+ <description>Message Digest 5</description>
+ </valueHelp>
+ <valueHelp>
+ <format>sha</format>
+ <description>Secure Hash Algorithm</description>
+ </valueHelp>
+ <constraint>
+ <regex>(md5|sha)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="engineid">
+ <properties>
+ <help>Specifies the EngineID that uniquely identify an agent (e.g. 0xff42)</help>
+ <constraint>
+ <regex>^(0x){0,1}([0-9a-f][0-9a-f]){1,18}$</regex>
+ </constraint>
+ <constraintErrorMessage>ID must contain from 2 to 36 hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="group">
+ <properties>
+ <help>Specifies group for user name</help>
+ <completionHelp>
+ <path>service snmp v3 group</path>
+ </completionHelp>
+ </properties>
+ </leafNode>
+ <leafNode name="mode">
+ <properties>
+ <help>Define users access permission (default: 'ro')</help>
+ <valueHelp>
+ <format>ro</format>
+ <description>read only</description>
+ </valueHelp>
+ <valueHelp>
+ <format>rw</format>
+ <description>read write</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ro|rw)</regex>
+ </constraint>
+ <constraintErrorMessage>Authorization type must be either 'rw' or 'ro'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <node name="privacy">
+ <properties>
+ <help>Defines the privacy</help>
+ </properties>
+ <children>
+ <leafNode name="encrypted-key">
+ <properties>
+ <help>Defines the encrypted key for privacy protocol</help>
+ <constraint>
+ <regex>^0x[0-9a-f]*$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must start from '0x' and contain hex digits</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="plaintext-key">
+ <properties>
+ <help>Defines the clear text key for privacy protocol</help>
+ <constraint>
+ <regex>^.{8,}$</regex>
+ </constraint>
+ <constraintErrorMessage>Key must contain 8 or more characters</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="type">
+ <properties>
+ <help>Defines the protocol for privacy (default: 'des')</help>
+ <valueHelp>
+ <format>des</format>
+ <description>Data Encryption Standard</description>
+ </valueHelp>
+ <valueHelp>
+ <format>aes</format>
+ <description>Advanced Encryption Standard</description>
+ </valueHelp>
+ <constraint>
+ <regex>(des|aes)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="tsm-key">
+ <properties>
+ <help>Specifies finger print or file name of TSM certificate</help>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ <tagNode name="view">
+ <properties>
+ <help>Specifies the view with name viewname</help>
+ <constraint>
+ <regex>^[^\(\)\|\-]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Illegal characters in name</constraintErrorMessage>
+ </properties>
+ <children>
+ <tagNode name="oid">
+ <properties>
+ <help>Specifies the oid</help>
+ <constraint>
+ <regex>^[0-9]+(\\.[0-9]+)*$</regex>
+ </constraint>
+ <constraintErrorMessage>OID must start from a number</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="exclude">
+ <properties>
+ <help>Exclude is an optional argument</help>
+ </properties>
+ </leafNode>
+ <leafNode name="mask">
+ <properties>
+ <help>Defines a bit-mask that is indicating which subidentifiers of the associated subtree OID should be regarded as significant</help>
+ <constraint>
+ <regex>^[0-9a-f]{2}([\\.:][0-9a-f]{2})*$</regex>
+ </constraint>
+ <constraintErrorMessage>MASK is a list of hex octets, separated by '.' or ':'</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
new file mode 100755
index 000000000..429181550
--- /dev/null
+++ b/src/conf_mode/snmp.py
@@ -0,0 +1,709 @@
+#!/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 stat
+import pwd
+
+import jinja2
+import ipaddress
+import random
+import binascii
+import re
+
+import vyos.version
+
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file_client = r'/etc/snmp/snmp.conf'
+config_file_daemon = r'/etc/snmp/snmpd.conf'
+config_file_access = r'/usr/share/snmp/snmpd.conf'
+config_file_user = r'/var/lib/snmp/snmpd.conf'
+
+# SNMPS template - be careful if you edit the template.
+client_config_tmpl = """
+### Autogenerated by snmp.py ###
+{% if trap_source -%}
+clientaddr {{ trap_source }}
+{% endif %}
+
+"""
+
+# SNMPS template - be careful if you edit the template.
+access_config_tmpl = """
+### Autogenerated by snmp.py ###
+{% if v3_users %}
+{% for u in v3_users %}
+{{ u.mode }}user {{ u.name }}
+{% endfor %}
+{% endif -%}
+rwuser {{ vyos_user }}
+
+"""
+
+# SNMPS template - be careful if you edit the template.
+user_config_tmpl = """
+### Autogenerated by snmp.py ###
+# user
+{% if v3_users %}
+{% for u in v3_users %}
+createUser {{ u.name }} {{ u.authProtocol | upper }} {% if u.authPassword %} "{{ u.authPassword }}" {% elif u.authMasterKey %} "{{ u.authMasterKey }}"{% endif %} {{ u.privProtocol | upper }}{% if u.privPassword %} {{ u.privPassword }}{% elif u.privMasterKey %} {{ u.privMasterKey }}{% endif %}
+{% endfor %}
+{% endif %}
+
+createUser {{ vyos_user }} MD5 "{{ vyos_user_pass }}" DES
+"""
+
+# SNMPS template - be careful if you edit the template.
+daemon_config_tmpl = """
+### Autogenerated by snmp.py ###
+
+# non configurable defaults
+sysObjectID 1.3.6.1.4.1.44641
+sysServices 14
+master agentx
+agentXPerms 0755 0755
+pass .1.3.6.1.2.1.31.1.1.1.18 /opt/vyatta/sbin/if-mib-alias
+smuxpeer .1.3.6.1.2.1.83
+smuxpeer .1.3.6.1.2.1.157
+smuxpeer .1.3.6.1.4.1.3317.1.2.2
+smuxpeer .1.3.6.1.4.1.3317.1.2.3
+smuxpeer .1.3.6.1.4.1.3317.1.2.5
+smuxpeer .1.3.6.1.4.1.3317.1.2.8
+smuxpeer .1.3.6.1.4.1.3317.1.2.9
+smuxsocket localhost
+
+# linkUp/Down configure the Event MIB tables to monitor
+# the ifTable for network interfaces being taken up or down
+# for making internal queries to retrieve any necessary information
+iquerySecName {{ vyos_user }}
+
+# Modified from the default linkUpDownNotification
+# to include more OIDs and poll more frequently
+notificationEvent linkUpTrap linkUp ifIndex ifDescr ifType ifAdminStatus ifOperStatus
+notificationEvent linkDownTrap linkDown ifIndex ifDescr ifType ifAdminStatus ifOperStatus
+monitor -r 10 -e linkUpTrap "Generate linkUp" ifOperStatus != 2
+monitor -r 10 -e linkDownTrap "Generate linkDown" ifOperStatus == 2
+
+########################
+# configurable section #
+########################
+
+{% if v3_tsm_key %}
+[snmp] localCert {{ v3_tsm_key }}
+{% endif %}
+
+# Default system description is VyOS version
+sysDescr VyOS {{ version }}
+
+{% if description -%}
+# Description
+SysDescr {{ description }}
+{% endif %}
+
+# Listen
+agentaddress unix:/run/snmpd.socket{% for li in listen_on %},{{ li }}{% endfor %}{% if v3_tsm_key %},tlstcp:{{ v3_tsm_port }},dtlsudp::{{ v3_tsm_port }}{% endif %}
+
+
+# SNMP communities
+{% if communities -%}
+{% for c in communities %}
+{% if c.network -%}
+{% for network in c.network %}
+{{ c.authorization }}community {{ c.name }} {{ network }}
+{% endfor %}
+{% else %}
+{{ c.authorization }}community {{ c.name }}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{% if contact -%}
+# system contact information
+SysContact {{ contact }}
+{% endif %}
+
+{% if location -%}
+# system location information
+SysLocation {{ location }}
+{% endif %}
+
+{% if smux_peers -%}
+# additional smux peers
+{% for sp in smux_peers %}
+smuxpeer {{ sp }}
+{% endfor %}
+{% endif %}
+
+{% if trap_targets -%}
+# if there is a problem - tell someone!
+{% for t in trap_targets %}
+trap2sink {{ t.target }}{% if t.port -%}:{{ t.port }}{% endif %} {{ t.community }}
+{% endfor %}
+{% endif %}
+
+#
+# SNMPv3 stuff goes here
+#
+{% if v3_enabled %}
+
+# views
+{% if v3_views -%}
+{% for v in v3_views %}
+{% for oid in v.oids %}
+view {{ v.name }} included .{{ oid.oid }}
+{% endfor %}
+{% endfor %}
+{% endif %}
+
+# access
+# context sec.model sec.level match read write notif
+{% if v3_groups -%}
+{% for g in v3_groups %}
+access {{ g.name }} "" usm {{ g.seclevel }} exact {{ g.view }} none none
+access {{ g.name }} "" tsm {{ g.seclevel }} exact {{ g.view }} none none
+{% endfor -%}
+{% endif %}
+
+# trap-target
+{% if v3_traps -%}
+{% for t in v3_traps %}
+trapsess -v 3 {{ '-Ci' if t.type == 'inform' }} -e {{ t.engineID }} -u {{ t.secName }} -l {{ t.secLevel }} -a {{ t.authProtocol }} {% if t.authPassword %}-A {{ t.authPassword }}{% elif t.authMasterKey %}-3m {{ t.authMasterKey }}{% endif %} -x {{ t.privProtocol }} {% if t.privPassword %}-X {{ t.privPassword }}{% elif t.privMasterKey %}-3M {{ t.privMasterKey }}{% endif %} {{ t.ipProto }}:{{ t.ipAddr }}:{{ t.ipPort }}
+{% endfor -%}
+{% endif %}
+
+# group
+{% if v3_users -%}
+{% for u in v3_users %}
+group {{ u.group }} usm {{ u.name }}
+group {{ u.group }} tsm {{ u.name }}
+{% endfor %}
+{% endif %}
+
+{% endif %}
+
+"""
+
+default_config_data = {
+ 'listen_on': [],
+ 'communities': [],
+ 'smux_peers': [],
+ 'location' : '',
+ 'description' : '',
+ 'contact' : '',
+ 'trap_source': '',
+ 'trap_targets': [],
+ 'vyos_user': '',
+ 'vyos_user_pass': '',
+ 'version': '999',
+ 'v3_enabled': 'False',
+ 'v3_engineid': '',
+ 'v3_groups': [],
+ 'v3_traps': [],
+ 'v3_tsm_key': '',
+ 'v3_tsm_port': '10161',
+ 'v3_users': [],
+ 'v3_views': []
+}
+
+def rmfile(file):
+ if os.path.isfile(file):
+ os.unlink(file)
+
+def get_config():
+ snmp = default_config_data
+ conf = Config()
+ if not conf.exists('service snmp'):
+ return None
+ else:
+ conf.set_level('service snmp')
+
+ version_data = vyos.version.get_version_data()
+ snmp['version'] = version_data['version']
+
+ # create an internal snmpv3 user of the form 'vyattaxxxxxxxxxxxxxxxx'
+ # os.urandom(8) returns 8 bytes of random data
+ snmp['vyos_user'] = 'vyatta' + binascii.hexlify(os.urandom(8)).decode('utf-8')
+ snmp['vyos_user_pass'] = binascii.hexlify(os.urandom(16)).decode('utf-8')
+
+ if conf.exists('community'):
+ for name in conf.list_nodes('community'):
+ community = {
+ 'name': name,
+ 'authorization': 'ro',
+ 'network': []
+ }
+
+ if conf.exists('community {0} authorization'.format(name)):
+ community['authorization'] = conf.return_value('community {0} authorization'.format(name))
+
+ if conf.exists('community {0} network'.format(name)):
+ community['network'] = conf.return_values('community {0} network'.format(name))
+
+ snmp['communities'].append(community)
+
+ if conf.exists('contact'):
+ snmp['contact'] = conf.return_value('contact')
+
+ if conf.exists('description'):
+ snmp['description'] = conf.return_value('description')
+
+ if conf.exists('listen-address'):
+ for addr in conf.list_nodes('listen-address'):
+ listen = ''
+ port = '161'
+ if conf.exists('listen-address {0} port'.format(addr)):
+ port = conf.return_value('listen-address {0} port'.format(addr))
+
+ if ipaddress.ip_address(addr).version == 4:
+ # udp:127.0.0.1:161
+ listen = 'udp:' + addr + ':' + port
+ elif ipaddress.ip_address(addr).version == 6:
+ # udp6:[::1]:161
+ listen = 'udp6:' + '[' + addr + ']' + ':' + port
+ else:
+ raise ConfigError('Invalid IP address version')
+
+ snmp['listen_on'].append(listen)
+
+ if conf.exists('location'):
+ snmp['location'] = conf.return_value('location')
+
+ if conf.exists('smux-peer'):
+ snmp['smux_peers'] = conf.return_values('smux-peer')
+
+ if conf.exists('trap-source'):
+ snmp['trap_source'] = conf.return_value('trap-source')
+
+ if conf.exists('trap-target'):
+ for target in conf.list_nodes('trap-target'):
+ trap_tgt = {
+ 'target': target,
+ 'community': '',
+ 'port': ''
+ }
+
+ if conf.exists('trap-target {0} community'.format(target)):
+ trap_tgt['community'] = conf.return_value('trap-target {0} community'.format(target))
+
+ if conf.exists('trap-target {0} port'.format(target)):
+ trap_tgt['port'] = conf.return_value('trap-target {0} port'.format(target))
+
+ snmp['trap_targets'].append(trap_tgt)
+
+ #########################################################################
+ # ____ _ _ __ __ ____ _____ #
+ # / ___|| \ | | \/ | _ \ __ _|___ / #
+ # \___ \| \| | |\/| | |_) | \ \ / / |_ \ #
+ # ___) | |\ | | | | __/ \ V / ___) | #
+ # |____/|_| \_|_| |_|_| \_/ |____/ #
+ # #
+ # now take care about the fancy SNMP v3 stuff, or bail out eraly #
+ #########################################################################
+ if not conf.exists('v3'):
+ return snmp
+ else:
+ snmp['v3_enabled'] = True
+
+ #
+ # 'set service snmp v3 engineid'
+ #
+ if conf.exists('v3 engineid'):
+ snmp['v3_engineid'] = conf.return_value('v3 engineid')
+
+ #
+ # 'set service snmp v3 group'
+ #
+ if conf.exists('v3 group'):
+ for group in conf.list_nodes('v3 group'):
+ v3_group = {
+ 'name': group,
+ 'mode': 'ro',
+ 'seclevel': 'auth',
+ 'view': ''
+ }
+
+ if conf.exists('v3 group {0} mode'.format(group)):
+ v3_group['mode'] = conf.return_value('v3 group {0} mode'.format(group))
+
+ if conf.exists('v3 group {0} seclevel'.format(group)):
+ v3_group['seclevel'] = conf.return_value('v3 group {0} seclevel'.format(group))
+
+ if conf.exists('v3 group {0} view'.format(group)):
+ v3_group['view'] = conf.return_value('v3 group {0} view'.format(group))
+
+ snmp['v3_groups'].append(v3_group)
+
+ #
+ # 'set service snmp v3 trap-target'
+ #
+ if conf.exists('v3 trap-target'):
+ for trap in conf.list_nodes('v3 trap-target'):
+ trap_cfg = {
+ 'ipAddr': trap,
+ 'engineID': '',
+ 'secName': '',
+ 'authProtocol': 'md5',
+ 'authPassword': '',
+ 'authMasterKey': '',
+ 'privProtocol': 'des',
+ 'privPassword': '',
+ 'privMasterKey': '',
+ 'ipProto': 'udp',
+ 'ipPort': '162',
+ 'type': '',
+ 'secLevel': 'noAuthNoPriv'
+ }
+
+ if conf.exists('v3 trap-target {0} engineid'.format(trap)):
+ # Set the context engineID used for SNMPv3 REQUEST messages scopedPdu.
+ # If not specified, this will default to the authoritative engineID.
+ trap_cfg['engineID'] = conf.return_value('v3 trap-target {0} engineid'.format(trap))
+
+ if conf.exists('v3 trap-target {0} user'.format(trap)):
+ # Set the securityName used for authenticated SNMPv3 messages.
+ trap_cfg['secName'] = conf.return_value('v3 trap-target {0} user'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth type'.format(trap)):
+ # Set the authentication protocol (MD5 or SHA) used for authenticated SNMPv3 messages
+ # cmdline option '-a'
+ trap_cfg['authProtocol'] = conf.return_value('v3 trap-target {0} auth type'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth plaintext-key'.format(trap)):
+ # Set the authentication pass phrase used for authenticated SNMPv3 messages.
+ # cmdline option '-A'
+ trap_cfg['authPassword'] = conf.return_value('v3 trap-target {0} auth plaintext-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} auth encrypted-key'.format(trap)):
+ # Sets the keys to be used for SNMPv3 transactions. These options allow you to set the master authentication keys.
+ # cmdline option '-3m'
+ trap_cfg['authMasterKey'] = conf.return_value('v3 trap-target {0} auth encrypted-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy type'.format(trap)):
+ # Set the privacy protocol (DES or AES) used for encrypted SNMPv3 messages.
+ # cmdline option '-x'
+ trap_cfg['privProtocol'] = conf.return_value('v3 trap-target {0} privacy type'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy plaintext-key'.format(trap)):
+ # Set the privacy pass phrase used for encrypted SNMPv3 messages.
+ # cmdline option '-X'
+ trap_cfg['privPassword'] = conf.return_value('v3 trap-target {0} privacy plaintext-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} privacy encrypted-key'.format(trap)):
+ # Sets the keys to be used for SNMPv3 transactions. These options allow you to set the master encryption keys.
+ # cmdline option '-3M'
+ trap_cfg['privMasterKey'] = conf.return_value('v3 trap-target {0} privacy encrypted-key'.format(trap))
+
+ if conf.exists('v3 trap-target {0} protocol'.format(trap)):
+ trap_cfg['ipProto'] = conf.return_value('v3 trap-target {0} protocol'.format(trap))
+
+ if conf.exists('v3 trap-target {0} port'.format(trap)):
+ trap_cfg['ipPort'] = conf.return_value('v3 trap-target {0} port'.format(trap))
+
+ if conf.exists('v3 trap-target {0} type'.format(trap)):
+ trap_cfg['type'] = conf.return_value('v3 trap-target {0} type'.format(trap))
+
+ # Determine securityLevel used for SNMPv3 messages (noAuthNoPriv|authNoPriv|authPriv).
+ # Appropriate pass phrase(s) must provided when using any level higher than noAuthNoPriv.
+ if trap_cfg['authPassword'] or trap_cfg['authMasterKey']:
+ if trap_cfg['privProtocol'] or trap_cfg['privPassword']:
+ trap_cfg['secLevel'] = 'authPriv'
+ else:
+ trap_cfg['secLevel'] = 'authNoPriv'
+
+ snmp['v3_traps'].append(trap_cfg)
+
+ #
+ # 'set service snmp v3 tsm'
+ #
+ if conf.exists('v3 tsm'):
+ if conf.exists('v3 tsm local-key'):
+ snmp['v3_tsm_key'] = conf.return_value('v3 tsm local-key')
+
+ if conf.exists('v3 tsm port'):
+ snmp['v3_tsm_port'] = conf.return_value('v3 tsm port')
+
+ #
+ # 'set service snmp v3 user'
+ #
+ if conf.exists('v3 user'):
+ for user in conf.list_nodes('v3 user'):
+ user_cfg = {
+ 'name': user,
+ 'authMasterKey': '',
+ 'authPassword': '',
+ 'authProtocol': '',
+ 'engineID': '',
+ 'group': '',
+ 'mode': 'ro',
+ 'privMasterKey': '',
+ 'privPassword': '',
+ 'privTsmKey': '',
+ 'privProtocol': ''
+ }
+
+ #
+ # v3 user {0} auth
+ #
+ if conf.exists('v3 user {0} auth encrypted-key'.format(user)):
+ user_cfg['authMasterKey'] = conf.return_value('v3 user {0} auth encrypted-key'.format(user))
+
+ if conf.exists('v3 user {0} auth plaintext-key'.format(user)):
+ user_cfg['authPassword'] = conf.return_value('v3 user {0} auth plaintext-key'.format(user))
+
+ if conf.exists('v3 user {0} auth type'.format(user)):
+ user_cfg['authProtocol'] = conf.return_value('v3 user {0} auth type'.format(user))
+
+ #
+ # v3 user {0} engineid
+ #
+ if conf.exists('v3 user {0} engineid'.format(user)):
+ user_cfg['engineID'] = conf.return_value('v3 user {0} engineid'.format(user))
+
+ #
+ # v3 user {0} group
+ #
+ if conf.exists('v3 user {0} group'.format(user)):
+ user_cfg['group'] = conf.return_value('v3 user {0} group'.format(user))
+
+ #
+ # v3 user {0} mode
+ #
+ if conf.exists('v3 user {0} mode'.format(user)):
+ user_cfg['mode'] = conf.return_value('v3 user {0} mode'.format(user))
+
+ #
+ # v3 user {0} privacy
+ #
+ if conf.exists('v3 user {0} privacy encrypted-key'.format(user)):
+ user_cfg['privMasterKey'] = conf.return_value('v3 user {0} privacy encrypted-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy plaintext-key'.format(user)):
+ user_cfg['privPassword'] = conf.return_value('v3 user {0} privacy plaintext-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy tsm-key'.format(user)):
+ user_cfg['privTsmKey'] = conf.return_value('v3 user {0} privacy tsm-key'.format(user))
+
+ if conf.exists('v3 user {0} privacy type'.format(user)):
+ user_cfg['privProtocol'] = conf.return_value('v3 user {0} privacy type'.format(user))
+
+ snmp['v3_users'].append(user_cfg)
+
+ #
+ # 'set service snmp v3 view'
+ #
+ if conf.exists('v3 view'):
+ for view in conf.list_nodes('v3 view'):
+ view_cfg = {
+ 'name': view,
+ 'oids': []
+ }
+
+ if conf.exists('v3 view {0} oid'.format(view)):
+ for oid in conf.list_nodes('v3 view {0} oid'.format(view)):
+ oid_cfg = {
+ 'oid': oid
+ }
+ view_cfg['oids'].append(oid_cfg)
+ snmp['v3_views'].append(view_cfg)
+
+ return snmp
+
+def verify(snmp):
+ if snmp is None:
+ return None
+
+ # bail out early if SNMP v3 is not configured
+ if not snmp['v3_enabled']:
+ return None
+
+ tsmKeyPattern = re.compile('^[0-9A-F]{2}(:[0-9A-F]{2}){19}$', re.IGNORECASE)
+
+ if snmp['v3_tsm_key']:
+ if not tsmKeyPattern.match(snmp['v3_tsm_key']):
+ if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ raise ConfigError('TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder')
+
+ if 'v3_groups' in snmp.keys():
+ for group in snmp['v3_groups']:
+ #
+ # A view must exist prior to mapping it into a group
+ #
+ if 'view' in group.keys():
+ error = True
+ if 'v3_views' in snmp.keys():
+ for view in snmp['v3_views']:
+ if view['name'] == group['view']:
+ error = False
+ if error:
+ raise ConfigError('You must create view "{0}" first'.format(group['view']))
+ else:
+ raise ConfigError('"view" must be specified')
+
+ if not 'mode' in group.keys():
+ raise ConfigError('"mode" must be specified')
+
+ if not 'seclevel' in group.keys():
+ raise ConfigError('"seclevel" must be specified')
+
+
+ if 'v3_traps' in snmp.keys():
+ for trap in snmp['v3_traps']:
+ if trap['authPassword'] and trap['authMasterKey']:
+ raise ConfigError('Must specify only one of encrypted-key/plaintext-key for trap auth')
+
+ if trap['authPassword'] == '' and trap['authMasterKey'] == '':
+ raise ConfigError('Must specify encrypted-key or plaintext-key for trap auth')
+
+ if trap['privPassword'] and trap['privMasterKey']:
+ raise ConfigError('Must specify only one of encrypted-key/plaintext-key for trap privacy')
+
+ if trap['privPassword'] == '' and trap['privMasterKey'] == '':
+ raise ConfigError('Must specify encrypted-key or plaintext-key for trap privacy')
+
+ if not 'type' in trap.keys():
+ raise ConfigError('v3 trap: "type" must be specified')
+
+ if not 'authPassword' and 'authMasterKey' in trap.keys():
+ raise ConfigError('v3 trap: "auth" must be specified')
+
+ if not 'authProtocol' in trap.keys():
+ raise ConfigError('v3 trap: "protocol" must be specified')
+
+ if not 'privPassword' and 'privMasterKey' in trap.keys():
+ raise ConfigError('v3 trap: "user" must be specified')
+
+ if 'type' in trap.keys():
+ if trap['type'] == 'trap' and trap['engineID'] == '':
+ raise ConfigError('must specify engineid if type is "trap"')
+ else:
+ raise ConfigError('"type" must be specified')
+
+
+ if 'v3_users' in snmp.keys():
+ for user in snmp['v3_users']:
+ if user['authPassword'] and user['authMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user auth')
+
+ if user['privPassword'] and user['privMasterKey']:
+ raise ConfigError('Can not mix "encrypted-key" and "plaintext-key" for user privacy')
+
+ if user['privPassword'] == '' and user['privMasterKey'] == '':
+ raise ConfigError('Must specify encrypted-key or plaintext-key for user privacy')
+
+ if user['authPassword'] == '' and user['authMasterKey'] == '' and user['privTsmKey'] == '':
+ raise ConfigError('Must specify auth or tsm-key for user auth')
+
+ if user['privProtocol'] == '':
+ raise ConfigError('Must specify privacy type')
+
+ if user['mode'] == '':
+ raise ConfigError('Must specify user mode ro/rw')
+
+ if user['privTsmKey']:
+ if not tsmKeyPattern.match(snmp['v3_tsm_key']):
+ if not os.path.isfile('/etc/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ if not os.path.isfile('/config/snmp/tls/certs/' + snmp['v3_tsm_key']):
+ raise ConfigError('User TSM key must be fingerprint or filename in "/config/snmp/tls/certs/" folder')
+
+ if user['group']:
+ #
+ # Group must exist prior to mapping it into a group
+ #
+ error = True
+ if 'v3_groups' in snmp.keys():
+ for group in snmp['v3_groups']:
+ if group['name'] == user['group']:
+ error = False
+ if error:
+ raise ConfigError('You must create group "{0}" first'.format(user['group']))
+
+
+ if 'v3_views' in snmp.keys():
+ for view in snmp['v3_views']:
+ if not view['oids']:
+ raise ConfigError('Must configure an oid')
+
+ return None
+
+def generate(snmp):
+ #
+ # As we are manipulating the snmpd user database we have to stop it first!
+ # This is even save if service is going to be removed
+ os.system("sudo systemctl stop snmpd.service")
+ rmfile(config_file_client)
+ rmfile(config_file_daemon)
+ rmfile(config_file_access)
+ rmfile(config_file_user)
+
+ if snmp is None:
+ return None
+
+ # Write client config file
+ tmpl = jinja2.Template(client_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_client, 'w') as f:
+ f.write(config_text)
+
+ # Write server config file
+ tmpl = jinja2.Template(daemon_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_daemon, 'w') as f:
+ f.write(config_text)
+
+ # Write access rights config file
+ tmpl = jinja2.Template(access_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_access, 'w') as f:
+ f.write(config_text)
+
+ # Write access rights config file
+ tmpl = jinja2.Template(user_config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(snmp)
+ with open(config_file_user, 'w') as f:
+ f.write(config_text)
+
+ return None
+
+def apply(snmp):
+ if snmp is not None:
+
+ if not os.path.exists('/config/snmp/tls'):
+ os.makedirs('/config/snmp/tls')
+ os.chmod('/config/snmp/tls', stat.S_IWUSR | stat.S_IRUSR)
+ # get uid for user 'snmp'
+ snmp_uid = pwd.getpwnam('snmp').pw_uid
+ os.chown('/config/snmp/tls', snmp_uid, -1)
+
+ # start SNMP daemon
+ os.system("sudo systemctl restart snmpd.service")
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)