package org.ovirt.engine.core.notifier.transport.snmp; import java.io.IOException; import java.net.InetAddress; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.EventNotificationMethod; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.notifier.NotificationServiceException; import org.ovirt.engine.core.notifier.dao.DispatchResult; import org.ovirt.engine.core.notifier.filter.AuditLogEvent; import org.ovirt.engine.core.notifier.transport.Transport; import org.ovirt.engine.core.notifier.utils.NotificationProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.CommunityTarget; import org.snmp4j.PDU; import org.snmp4j.ScopedPDU; import org.snmp4j.Target; import org.snmp4j.TransportMapping; import org.snmp4j.UserTarget; import org.snmp4j.mp.SnmpConstants; import org.snmp4j.security.AuthMD5; import org.snmp4j.security.AuthSHA; import org.snmp4j.security.PrivAES128; import org.snmp4j.security.PrivAES192; import org.snmp4j.security.PrivAES256; import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.snmp4j.smi.TimeTicks; import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; public class Snmp extends Transport { private static final Logger log = LoggerFactory.getLogger(Snmp.class); private static final String SNMP_MANAGERS = "SNMP_MANAGERS"; private static final String SNMP_COMMUNITY = "SNMP_COMMUNITY"; private static final String SNMP_ENGINE_ID = "SNMP_ENGINE_ID"; private static final String SNMP_USERNAME = "SNMP_USERNAME"; private static final String SNMP_AUTH_PROTOCOL = "SNMP_AUTH_PROTOCOL"; private static final String SNMP_AUTH_PASSPHRASE = "SNMP_AUTH_PASSPHRASE"; private static final String SNMP_PRIVACY_PROTOCOL = "SNMP_PRIVACY_PROTOCOL"; private static final String SNMP_PRIVACY_PASSPHRASE = "SNMP_PRIVACY_PASSPHRASE"; private static final String SNMP_SECURITY_LEVEL = "SNMP_SECURITY_LEVEL"; private static final String SNMP_OID = "SNMP_OID"; private static final String SNMP_VERSION = "SNMP_VERSION"; private static final int ENTERPRISE_SPECIFIC = 6; private static final Pattern PROFILE_PATTERN = Pattern.compile(SNMP_MANAGERS + "(|_(?<profile>.*))"); /** OIDs - See OVIRT-MIB.txt */ public static final int AUDIT = 1; private static final OID OBJECTS_AUDIT = new OID(new int[] {2, 1}); public static final int INSTANCE_ID = 1; public static final int NAME = 2; public static final int ID = 3; public static final int SEVERITY = 4; public static final int MESSAGE = 5; public static final int STATUS = 6; public static final int DATETIME = 7; public static final int USERNAME = 100; public static final int USER_ID = 101; public static final int VM_NAME = 102; public static final int VM_ID = 103; public static final int VDS_NAME = 104; public static final int VDS_ID = 105; public static final int VM_TEMPLATE_NAME = 106; public static final int VM_TEMPLATE_ID = 107; public static final int STORAGE_POOL_NAME = 108; public static final int STORAGE_POOL_ID = 109; public static final int STORAGE_DOMAIN_NAME = 110; public static final int STORAGE_DOMAIN_ID = 111; private final Map<String, Profile> profiles = new HashMap<>(); private static final String ISO8601 = "yyyy-MM-dd'T'HH:mm'Z'"; private boolean active = false; private static long nanoStart = System.nanoTime(); private Map<Profile, org.snmp4j.Snmp> profileSnmpMap = new HashMap<>(); public Snmp(NotificationProperties props) { for (Map.Entry<String, String> entry : props.getProperties().entrySet()) { Matcher m = PROFILE_PATTERN.matcher(entry.getKey()); if (m.matches()) { String profile = m.group("profile") == null ? "" : m.group("profile"); String managers = props.getProperty(SNMP_MANAGERS, profile, false); if (!StringUtils.isBlank(managers)) { int snmpVersion = getSnmpVersion(props.getProperty(SNMP_VERSION, false)); int securityLevel = Integer.parseInt(props.getProperty(SNMP_SECURITY_LEVEL, profile, false)); if (snmpVersion == SnmpConstants.version3) { profiles.put( profile, Profile.buildProfile( managers, props.getProperty(SNMP_ENGINE_ID, profile, false), props.getProperty(SNMP_USERNAME, profile, false), securityLevel == 1 ? null : getAuthProtocol(props.getProperty(SNMP_AUTH_PROTOCOL, profile, false)), props.getProperty(SNMP_AUTH_PASSPHRASE, profile, securityLevel == 1), securityLevel != 3 ? null : getPrivProtocol(props.getProperty(SNMP_PRIVACY_PROTOCOL, profile, false)), props.getProperty(SNMP_PRIVACY_PASSPHRASE, profile, securityLevel != 3), securityLevel, props.getProperty(SNMP_OID, profile, false), snmpVersion ) ); } else { profiles.put( profile, Profile.buildProfile( managers, props.getProperty(SNMP_COMMUNITY, profile, false), props.getProperty(SNMP_OID, profile, false), snmpVersion ) ); } } } } if (!profiles.isEmpty()) { active = true; } } private int getSnmpVersion(String ver) { int version; switch(ver) { case "2": version = SnmpConstants.version2c; break; case "3": version = SnmpConstants.version3; break; default: throw new NotificationServiceException("Unknown SNMP_VERSION in properties file " + ver); } return version; } private OID getAuthProtocol(String authProtocol) { switch(authProtocol) { case "MD5": return AuthMD5.ID; case "SHA": return AuthSHA.ID; default: throw new NotificationServiceException("Unknown SNMP_AUTH_PROTOCOL in properties file " + authProtocol); } } private OID getPrivProtocol(String privProtocol) { switch(privProtocol) { case "AES128": return PrivAES128.ID; case "AES192": return PrivAES192.ID; case "AES256": return PrivAES256.ID; default: throw new NotificationServiceException("Unknown SNMP_PRIVACY_PROTOCOL in properties file " + privProtocol); } } @Override public String getName() { return "snmp"; } @Override public boolean isActive() { return active; } @Override public void dispatchEvent(AuditLogEvent event, String address) { Profile profile = profiles.get(address); if (profile == null) { profile = profiles.get(""); if (profile == null) { log.warn("Could not find snmp profile: {}", address); return; } } PDU pdu; org.snmp4j.Snmp snmpForProfile = getSnmpForProfile(profile); if (profile.version == SnmpConstants.version3) { // Create PDU for V3 pdu = new ScopedPDU(); ((ScopedPDU) pdu).setContextEngineID(profile.engineId); } else { // PDU class is for SNMPv2c units pdu = new PDU(); } pdu.setType(PDU.TRAP); addPayload(pdu, event, profile); Target target = createTarget(profile); for (Host host : profile.hosts) { try { log.info("Generate an snmp trap for event: {} to address: {} ", event, host.name); target.setAddress( new UdpAddress( InetAddress.getByName(host.name), host.port ) ); snmpForProfile.send(pdu, target); notifyObservers(DispatchResult.success(event, address, EventNotificationMethod.SNMP)); } catch (Exception e) { log.error(e.getMessage()); notifyObservers(DispatchResult.failure(event, address, EventNotificationMethod.SNMP, e.getMessage())); } } } private synchronized org.snmp4j.Snmp getSnmpForProfile(Profile profile) { if (!profileSnmpMap.containsKey(profile)) { profileSnmpMap.put(profile, profile.version == SnmpConstants.version3 ? createSnmp3(profile) : createSnmp()); } return profileSnmpMap.get(profile); } private org.snmp4j.Snmp createSnmp() { try { // Create a new session and define it's transport. return new org.snmp4j.Snmp(new DefaultUdpTransportMapping()); } catch (IOException e) { throw new NotificationServiceException("error creating " + getClass().getName()); } } private org.snmp4j.Snmp createSnmp3(Profile profile) { try { TransportMapping<?> transport = new DefaultUdpTransportMapping(); org.snmp4j.Snmp snmp = new org.snmp4j.Snmp(transport); SecurityProtocols securityProtocols = SecurityProtocols.getInstance(); securityProtocols.addDefaultProtocols(); securityProtocols.addAuthenticationProtocol(new AuthMD5()); securityProtocols.addAuthenticationProtocol(new AuthSHA()); securityProtocols.addPrivacyProtocol(new PrivAES128()); securityProtocols.addPrivacyProtocol(new PrivAES192()); securityProtocols.addPrivacyProtocol(new PrivAES256()); USM usm = new USM(securityProtocols, profile.engineId, 0); ((org.snmp4j.mp.MPv3) snmp.getMessageProcessingModel(org.snmp4j.mp.MPv3.ID)) .setLocalEngineID(profile.engineId.getValue()); ((org.snmp4j.mp.MPv3) snmp.getMessageProcessingModel(org.snmp4j.mp.MPv3.ID)) .getSecurityModels().addSecurityModel(usm); SecurityModels.getInstance().addSecurityModel( usm); transport.listen(); snmp.getUSM().addUser( profile.username, getUsmUser(profile)); return snmp; } catch (IOException e) { throw new NotificationServiceException("error creating version 3 snmp " + getClass().getName()); } } private UsmUser getUsmUser(Profile profile) { OID authenticationProtocol = null; OctetString authenticationPassphrase = null; OID privacyProtocol = null; OctetString privacyPassphrase = null; switch(profile.securityLevel) { case SecurityLevel.AUTH_NOPRIV: authenticationProtocol = profile.authProtocol; authenticationPassphrase = profile.authPassphrase; break; case SecurityLevel.AUTH_PRIV: authenticationProtocol = profile.authProtocol; authenticationPassphrase = profile.authPassphrase; privacyProtocol = profile.privProtocol; privacyPassphrase = profile.privacyPassphrase; break; } return new UsmUser(profile.username, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); } private Target createTarget(Profile profile) { Target target; if (profile.version == SnmpConstants.version2c) { target = new CommunityTarget(); ((CommunityTarget) target).setCommunity(profile.community); } else { target = new UserTarget(); ((UserTarget) target).setAuthoritativeEngineID(profile.engineId.getValue()); target.setSecurityName(profile.username); target.setSecurityLevel(profile.securityLevel); } target.setVersion(profile.version); return target; } private void addPayload(PDU pdu, AuditLogEvent event, Profile profile) { pdu.add(new VariableBinding(SnmpConstants.sysUpTime, new TimeTicks((System.nanoTime() - nanoStart) / 10000000))); // { [baseoid] notifications(0) audit(1) } pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID, SnmpConstants.getTrapOID(new OID(profile.oid), ENTERPRISE_SPECIFIC, AUDIT))); int auditLogId = AuditLogType.UNASSIGNED.getValue(); try { auditLogId = AuditLogType.valueOf(event.getName()).getValue(); } catch (IllegalArgumentException e) { log.warn("Could not find event: {} in auditLogTypes", event.getName()); } // { [baseoid] objects(2) audit(1) } OID auditObjects = new OID(profile.oid).append(OBJECTS_AUDIT); addString(pdu, auditObjects, INSTANCE_ID, Long.toString(event.getId()), true); addString(pdu, auditObjects, NAME, event.getName(), true); addInt(pdu, auditObjects, ID, auditLogId, true); addInt(pdu, auditObjects, SEVERITY, event.getSeverity().getValue(), true); addString(pdu, auditObjects, MESSAGE, event.getMessage(), true); addInt(pdu, auditObjects, STATUS, event.getType().getValue(), true); addString(pdu, auditObjects, DATETIME, new SimpleDateFormat(ISO8601).format(event.getLogTime()), true); // Optional pdu: addString(pdu, auditObjects, USERNAME, event.getUserName(), false); addUuid(pdu, auditObjects, USER_ID, event.getUserId()); addString(pdu, auditObjects, VM_NAME, event.getVmName(), false); addUuid(pdu, auditObjects, VM_ID, event.getVmId()); addString(pdu, auditObjects, VDS_NAME, event.getVdsName(), false); addUuid(pdu, auditObjects, VDS_ID, event.getVdsId()); addString(pdu, auditObjects, VM_TEMPLATE_NAME, event.getVmTemplateName(), false); addUuid(pdu, auditObjects, VM_TEMPLATE_ID, event.getVmTemplateId()); addString(pdu, auditObjects, STORAGE_POOL_NAME, event.getStoragePoolName(), false); addUuid(pdu, auditObjects, STORAGE_POOL_ID, event.getStoragePoolId()); addString(pdu, auditObjects, STORAGE_DOMAIN_NAME, event.getStorageDomainName(), false); addUuid(pdu, auditObjects, STORAGE_DOMAIN_ID, event.getStorageDomainId()); } private void addString(PDU pdu, OID prefix, int suffix, String val, boolean allowEmpty) { if (allowEmpty || !StringUtils.isEmpty(val)) { pdu.add(new VariableBinding(new OID(prefix).append(suffix), new OctetString(val == null ? "" : val))); } } private void addInt(PDU pdu, OID prefix, int suffix, Integer val, boolean allowEmpty) { if (allowEmpty || val != null) { pdu.add(new VariableBinding(new OID(prefix).append(suffix), new Integer32(val == null ? 0 : val))); } } private void addUuid(PDU pdu, final OID prefix, int suffix, Guid val) { if (!Guid.isNullOrEmpty(val)) { addString(pdu, prefix, suffix, val.toString(), false); } } static class Host { public String name; public int port = 162; public Host(String name, String port) { this.name = name; if (port != null) { try { this.port = Integer.parseInt(port); if (this.port <= 0 || this.port > 0xffff) { throw new Exception("Bad port"); } } catch (Exception e) { throw new IllegalArgumentException( String.format( "Invalid port %s for snmp host %s", port, name ) ); } } } } static class Profile { private static final Pattern HOST_PATTERN = Pattern.compile( "(?<host>(([^\\[:\\s]+)|(\\[[^\\]]+\\])))(:(?<port>[^\\s]*))?"); public List<Host> hosts = new LinkedList<>(); public int version; public OID oid; // Version 2 specific variables public OctetString community; // Version 3 specific variables public OctetString engineId; public OctetString username; public OID authProtocol; public OctetString authPassphrase; public OID privProtocol; public OctetString privacyPassphrase; public int securityLevel; public Profile(String managers, String oid, int version) { Matcher m = HOST_PATTERN.matcher(managers); while (m.find()) { hosts.add(new Host(m.group("host"), m.group("port"))); } this.oid = new OID(oid); if (!this.oid.isValid()) { throw new IllegalArgumentException( String.format( "Invalid oid '%s'", oid ) ); } this.version = version; } public static Profile buildProfile(String managers, String community, String oid, int version) { Profile profile = new Profile(managers, oid, version); profile.community = new OctetString(community); return profile; } public static Profile buildProfile(String managers, String engineId, String username, OID authProtocol, String authPassphrase, OID privProtocol, String privacyPassphrase, int securityLevel, String oid, int version) { Profile profile = new Profile(managers, oid, version); profile.engineId = OctetString.fromHexString(engineId); profile.username = new OctetString(username); profile.authProtocol = authProtocol; profile.authPassphrase = new OctetString(authPassphrase); profile.privProtocol = privProtocol; profile.privacyPassphrase = new OctetString(privacyPassphrase); profile.securityLevel = securityLevel; return profile; } } }