diff options
-rw-r--r-- | interface-definitions/snmp.xml | 606 | ||||
-rwxr-xr-x | src/conf_mode/snmp.py | 709 |
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) |