Commit 2509e0ab authored by Wannes's avatar Wannes
Browse files

Merge pull request #18 from Neutrinet/ldap

Merge LDAP branch
parents e2af8e6c 176009e1
......@@ -17,11 +17,6 @@
<artifactId>ormlite-jdbc</artifactId>
<version>4.48</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.3m</version>
</dependency>
<dependency>
<groupId>org.restlet.jse</groupId>
<artifactId>org.restlet</artifactId>
......@@ -209,6 +204,11 @@
<artifactId>raven-log4j</artifactId>
<version>6.0.0</version>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>2.3.8</version>
</dependency>
</dependencies>
<repositories>
<repository>
......
......@@ -17,14 +17,14 @@ public class Bootstrap {
User user = new User();
user.birthDate = new Date();
user.birthPlace = "Spaaaace!";
user.country = "Botswana";
user.country = "Belgium";
user.email = "boot@strap.be";
user.lastName = "Bobson";
user.name = "Alice";
user.municipality = "Moonbase A6-9K";
user.postalCode = "42";
user.setPassword("password");
Users.dao.create(user);
Users.add(user);
IPAddresses.addv4SubnetToPool("192.168.221.129/25", IPAddress.Purpose.CLIENT_ASSIGN);
}
......
......@@ -19,10 +19,12 @@ package be.neutrinet.ispng;
import be.fedict.eid.applet.service.AppletServiceServlet;
import be.neutrinet.ispng.config.Config;
import be.neutrinet.ispng.external.LDAP;
import be.neutrinet.ispng.mail.Generator;
import be.neutrinet.ispng.monitoring.Agent;
import be.neutrinet.ispng.util.MariaDBType;
import be.neutrinet.ispng.util.MySQLDBType;
import be.neutrinet.ispng.util.PostgreSQLType;
import be.neutrinet.ispng.util.Zookeeper;
import be.neutrinet.ispng.vpn.Manager;
import be.neutrinet.ispng.vpn.api.FlowServlet;
......@@ -49,7 +51,6 @@ import java.util.Optional;
import java.util.Properties;
/**
*
* @author wannes
*/
public class VPN implements Daemon {
......@@ -91,6 +92,7 @@ public class VPN implements Daemon {
Zookeeper.boot(cfg.getProperty("zookeeper.connectionString"));
Config.get().boot(cfg);
LDAP.get().boot();
generator = new Generator();
Optional<String> sentryApiKey = Config.get("sentry/api/dsn");
......@@ -118,6 +120,11 @@ public class VPN implements Daemon {
cfg.getProperty("db.user"),
cfg.getProperty("db.password"),
new MySQLDBType());
} else if (cfg.get("db.uri").toString().contains("postgresql")) {
cs = new JdbcConnectionSource(cfg.getProperty("db.uri"),
cfg.getProperty("db.user"),
cfg.getProperty("db.password"),
new PostgreSQLType());
} else {
cs = new JdbcConnectionSource(cfg.getProperty("db.uri"),
cfg.getProperty("db.user"),
......
......@@ -2,11 +2,11 @@ package be.neutrinet.ispng.dns;
import be.neutrinet.ispng.security.OwnedEntity;
import be.neutrinet.ispng.vpn.Client;
import be.neutrinet.ispng.vpn.User;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.io.Serializable;
import java.util.UUID;
/**
* Created by wannes on 1/27/15.
......@@ -14,6 +14,15 @@ import java.io.Serializable;
@DatabaseTable
public class DNSRecord implements Serializable, OwnedEntity {
public final static String A = "A";
public final static String AAAA = "AAAA";
public final static String PTR = "PTR";
public final static String NS = "NS";
public final static String[] ALLOWED_TYPES = new String[]{A, AAAA, PTR, NS};
@DatabaseField(foreign = true, foreignAutoRefresh = true)
public transient Client client;
@DatabaseField
public long lastModified;
@DatabaseField(generatedId = true)
private int id;
@DatabaseField(canBeNull = false)
......@@ -24,16 +33,6 @@ public class DNSRecord implements Serializable, OwnedEntity {
private int ttl;
@DatabaseField(canBeNull = false)
private String type;
@DatabaseField(foreign = true, foreignAutoRefresh = true)
public transient Client client;
@DatabaseField
public long lastModified;
public final static String A = "A";
public final static String AAAA = "AAAA";
public final static String PTR = "PTR";
public final static String NS = "NS";
public final static String[] ALLOWED_TYPES = new String[]{A, AAAA, PTR, NS};
private DNSRecord() {
......@@ -102,7 +101,7 @@ public class DNSRecord implements Serializable, OwnedEntity {
}
@Override
public boolean isOwnedBy(User user) {
return this.client.user.equals(user);
public boolean isOwnedBy(UUID user) {
return this.client.userId.equals(user);
}
}
......@@ -16,10 +16,9 @@ import java.util.Map;
*/
public class RequestHandler {
private Map<Integer, Cache> caches = new HashMap<>();
static final int FLAG_DNSSECOK = 1;
static final int FLAG_SIGONLY = 2;
private Map<Integer, Cache> caches = new HashMap<>();
public Cache getCache(int dclass) {
Cache c = caches.get(dclass);
......
......@@ -19,15 +19,8 @@ import java.util.*;
public class ZoneBuilder {
private final static String PREFIX = "/ispng/dns/";
private KeptMap zones;
private Map<String, KeptList<DNSRecord>> records = new HashMap<>();
// TODO persist zone serial number suffix
private Map<String, Integer> suffixes = new HashMap<>();
private final static SimpleDateFormat SERIAL_NUMBER_FORMAT = new SimpleDateFormat("YYYYMMDD");
public static Name SAME_AS_ROOT;
private int ttl;
private List<String> nameServers = new ArrayList<>();
private Properties cfg;
static {
try {
......@@ -37,6 +30,14 @@ public class ZoneBuilder {
}
}
private KeptMap zones;
private Map<String, KeptList<DNSRecord>> records = new HashMap<>();
// TODO persist zone serial number suffix
private Map<String, Integer> suffixes = new HashMap<>();
private int ttl;
private List<String> nameServers = new ArrayList<>();
private Properties cfg;
public void boot(Properties cfg) {
this.cfg = cfg;
this.ttl = Integer.parseInt(cfg.getProperty("zone.all.record.TTL"));
......
package be.neutrinet.ispng.external;
import be.neutrinet.ispng.config.Config;
import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.controls.PasswordExpiredControl;
import com.unboundid.ldap.sdk.controls.PasswordExpiringControl;
import com.unboundid.ldap.sdk.persist.LDAPField;
import com.unboundid.util.ssl.SSLUtil;
import com.unboundid.util.ssl.TrustAllTrustManager;
import org.apache.log4j.Logger;
import javax.net.SocketFactory;
import java.lang.reflect.Field;
import java.util.Optional;
/**
* Created by wannes on 27/05/15.
*/
public class LDAP {
private final static LDAP instance = new LDAP();
private LDAPConnection connection;
private Logger logger = Logger.getLogger(getClass());
private SocketFactory socketFactory = null;
private Optional<String> host = null;
private LDAP() {
}
public static LDAP get() {
return instance;
}
public static String findAttributeName(Class clazz, String fieldName) {
assert clazz != null;
assert fieldName != null;
try {
for (Field f : clazz.getDeclaredFields()) {
if (f.getName().equals(fieldName)) {
LDAPField field = f.getAnnotation(LDAPField.class);
if (field != null) {
return field.attribute();
} else {
return fieldName;
}
}
}
} catch (Exception ex) {
Logger.getLogger(LDAP.class).error("Failed to find LDAP attribute for field " + fieldName, ex);
}
return fieldName;
}
public static LDAPConnection connection() {
return instance.connection;
}
public void boot() {
host = Config.get("ldap/host");
if (host.isPresent()) {
try {
SSLUtil sslUtil = new SSLUtil(null, new TrustAllTrustManager());
socketFactory = sslUtil.createSSLSocketFactory();
connection = new LDAPConnection(socketFactory, host.get(), Integer.parseInt(Config.get("ldap/port", "636")));
Optional<String> dn = Config.get("ldap/bind/dn");
Optional<String> password = Config.get("ldap/bind/password");
if (!dn.isPresent() || !password.isPresent()) {
throw new IllegalArgumentException("LDAP bind DN and/or password not present");
}
BindRequest bindRequest = new SimpleBindRequest(dn.get(), password.get());
BindResult bindResult = connection.bind(bindRequest);
PasswordExpiredControl pwdExpired = PasswordExpiredControl.get(bindResult);
if (pwdExpired == null) {
logger.debug("The password expired control was not included in " +
"the LDAP BIND response.");
} else {
logger.error("You must change your LDAP password " +
"before you will be allowed to perform any other operations.");
}
PasswordExpiringControl pwdExpiring = PasswordExpiringControl.get(bindResult);
if (pwdExpiring == null) {
logger.debug("The password expiring control was not included in" +
" the LDAP BIND response.");
} else {
logger.warn("Your LDAP password will expire in " +
pwdExpiring.getSecondsUntilExpiration() + " seconds.");
}
logger.info("Connected to LDAP");
} catch (Exception ex) {
logger.error("Failed to connect to LDAP server", ex);
System.exit(666);
}
}
}
public boolean auth(String dn, String password) {
try {
connection = new LDAPConnection(socketFactory, host.get(), Integer.parseInt(Config.get("ldap/port", "636")));
BindResult bind = connection.bind(dn, password);
boolean success = bind.getResultCode().equals(ResultCode.SUCCESS);
connection.close();
return success;
} catch (Exception ex) {
Logger.getLogger(getClass()).debug("Failed to auth user " + dn, ex);
}
return false;
}
}
......@@ -11,7 +11,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author wannes
*/
public class I18N {
......
......@@ -26,7 +26,6 @@ import java.util.Iterator;
import java.util.Map;
/**
*
* @author wannes
*/
public class Generator {
......@@ -42,14 +41,14 @@ public class Generator {
public void sendRegistrationConfirmation(Registration r) {
try {
MimeMessage msg = this.postman.createNewMessage();
msg.addRecipients(Message.RecipientType.TO, r.user.email);
msg.addRecipients(Message.RecipientType.TO, r.user().email);
msg.setSubject("Registration confirmation");
HashMap<String, String> content = new HashMap<>();
content.put("title", "Confirmation");
content.put("preview", "You successfully created your Neutrinet account");
content.put("email", r.user.email);
content.put("name", r.user.name + " " + r.user.lastName);
content.put("email", r.user().email);
content.put("name", r.user().name + " " + r.user().lastName);
content.put("reg-id", r.getId().toString());
if (r.ipv4Id != 0) {
content.put("ipv4", IPAddresses.dao.queryForId("" + r.ipv4Id).address);
......@@ -92,7 +91,7 @@ public class Generator {
if (c.subnetLeases != null) {
for (Iterator<SubnetLease> it = c.subnetLeases.iterator(); it.hasNext(); ) {
MimeMessage msg = this.postman.createNewMessage();
msg.addRecipients(Message.RecipientType.TO, c.user.email);
msg.addRecipients(Message.RecipientType.TO, c.user().email);
msg.setSubject("Your new IPv6 subnet");
HashMap<String, String> content = new HashMap<>();
......
......@@ -3,16 +3,18 @@ package be.neutrinet.ispng.mail;
import be.neutrinet.ispng.VPN;
import com.sun.mail.smtp.SMTPTransport;
import java.security.Security;
import java.util.Date;
import java.util.Properties;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.security.Security;
import java.util.Date;
import java.util.Properties;
/**
* TODO: find out if there are any external services with similar functionality and remove this mail piece
*
* @author wannes
*/
......@@ -58,9 +60,9 @@ public class Postman {
* Create MimeMessage using Gandi session
*
* @return MimeMessage created message
* @throws AddressException if the email address parse failed
* @throws AddressException if the email address parse failed
* @throws MessagingException if the connection is dead or not in the
* connected state or if the message is not a MimeMessage
* connected state or if the message is not a MimeMessage
*/
public MimeMessage createNewMessage() throws AddressException, MessagingException {
if (session == null) {
......
......@@ -15,7 +15,6 @@ import java.util.HashMap;
import java.util.Map;
/**
*
* @author wannes
*/
public class Renderer {
......@@ -28,16 +27,16 @@ public class Renderer {
public String renderInTemplate(String segmentName, Map<String, String> content, boolean plaintext) {
fillInDefaults(content);
String rseg = render(segmentName, content, plaintext);
HashMap<String, String> ct = new HashMap<>();
content.remove("body");
ct.putAll(content);
ct.put("body", rseg);
return render(BASE_TEMPLATE, ct, plaintext);
}
protected void fillInDefaults(Map<String, String> content) {
if (!content.containsKey("header-img-src")) {
content.put("header-img-src", VPN.cfg.getProperty("mail.headerImageURL"));
......
......@@ -15,9 +15,9 @@ import java.util.concurrent.ConcurrentLinkedQueue;
* Created by wannes on 10/2/14.
*/
public class Agent {
public static int MAX_BACKLOG = 1000;
protected OpenTSDB api;
protected ConcurrentLinkedQueue<DataPoint> queue;
public static int MAX_BACKLOG = 1000;
protected Timer timer;
protected boolean busy;
......
......@@ -2,7 +2,6 @@ package be.neutrinet.ispng.openvpn;
import be.neutrinet.ispng.VPN;
import be.neutrinet.ispng.config.Config;
import be.neutrinet.ispng.monitoring.DataPoint;
import be.neutrinet.ispng.vpn.*;
import be.neutrinet.ispng.vpn.ip.SubnetLease;
import com.google.common.collect.LinkedListMultimap;
......@@ -26,9 +25,11 @@ public class DefaultServiceListener implements ServiceListener {
protected ManagementInterface vpn;
protected boolean acceptNewConnections, acceptConnections;
protected HashMap<Integer, Connection> pendingConnections;
protected Monitoring monitoringAgent;
public DefaultServiceListener() {
pendingConnections = new HashMap<>();
monitoringAgent = new Monitoring();
Config.get().getAndWatch("OpenVPN/connections/accept", YES, value -> acceptConnections = YES.equals(value));
Config.get().getAndWatch("OpenVPN/connections/acceptNew", YES, value -> acceptNewConnections = YES.equals(value));
......@@ -43,7 +44,10 @@ public class DefaultServiceListener implements ServiceListener {
be.neutrinet.ispng.vpn.Client userClient = be.neutrinet.ispng.vpn.Client.match(client).orElseGet(() -> be.neutrinet.ispng.vpn.Client.create(client));
if (!userClient.enabled) vpn.denyClient(client.id, client.kid, "Client is disabled");
if (!userClient.enabled) {
vpn.denyClient(client.id, client.kid, "Client is disabled");
return;
}
try {
User user = Users.authenticate(client.username, client.password);
......@@ -55,10 +59,10 @@ public class DefaultServiceListener implements ServiceListener {
else
ipv4 = userClient.leases.stream().filter(addr -> addr.ipVersion == 4).findFirst();
if (!ipv4.isPresent() && userClient.subnetLeases.isEmpty()) {
/*if (!ipv4.isPresent() && userClient.subnetLeases.isEmpty()) {
vpn.denyClient(client.id, client.kid, "No IP address or subnet leases assigned");
return null;
}
}*/
Connection c = new Connection(userClient);
c.openvpnInstance = vpn.getInstanceId();
......@@ -98,6 +102,18 @@ public class DefaultServiceListener implements ServiceListener {
// Why /64? See https://community.openvpn.net/openvpn/ticket/264
options.put("ifconfig-ipv6-push", interconnect.address + "/64" + " " + VPN.cfg.getProperty("vpn.ipv6.interconnect"));
if (!ipv4.isPresent()) {
/* because OpenVPN does not acknowledge that IPv6-only connectivity is a thing now, we need
to assign an ephemeral IPv4 address.
*/
}
if (user.settings().get("ip.route.ipv6.defaultRoute", true).equals(true)) {
//options.put("push redirect-gateway-ipv6", "def1");
options.put("push route-ipv6", "2000::/3");
}
if (!userClient.subnetLeases.isEmpty()) {
for (SubnetLease lease : userClient.subnetLeases) {
options.put("push route-ipv6", VPN.cfg.getProperty("vpn.ipv6.network") + "/" + VPN.cfg.getProperty("vpn.ipv6.prefix")
......@@ -106,11 +122,6 @@ public class DefaultServiceListener implements ServiceListener {
options.put("iroute-ipv6", lease.subnet.subnet);
options.put("setenv-safe DELEGATED_IPv6_PREFIX", lease.subnet.subnet);
}
if (user.settings().get("ip.route.ipv6.defaultRoute").isPresent()) {
//options.put("push redirect-gateway-ipv6", "def1");
options.put("push route-ipv6", "2000::/3");
}
}
if (VPN.cfg.containsKey("openvpn.ping")) {
......@@ -228,25 +239,7 @@ public class DefaultServiceListener implements ServiceListener {
@Override
public void bytecount(Client client, long bytesIn, long bytesOut) {
HashMap<String, String> tags = new HashMap<>();
tags.put("client", "" + client.id);
tags.put("connection", "" + client.kid);
tags.put("vpnInstance", "" + vpn.getInstanceId().replace(':', '-'));
DataPoint bytesInDataPoint = new DataPoint();
bytesInDataPoint.metric = "vpn.client.bytesIn";
bytesInDataPoint.timestamp = System.currentTimeMillis();
bytesInDataPoint.value = bytesIn;
bytesInDataPoint.tags = tags;
DataPoint bytesOutDataPoint = new DataPoint();
bytesOutDataPoint.metric = "vpn.client.bytesOut";
bytesOutDataPoint.timestamp = System.currentTimeMillis();
bytesOutDataPoint.value = bytesOut;
bytesOutDataPoint.tags = tags;
VPN.monitoringAgent.addDataPoint(bytesInDataPoint);
VPN.monitoringAgent.addDataPoint(bytesOutDataPoint);
monitoringAgent.byteCount(client, bytesIn, bytesOut, vpn.getInstanceId().replace(':', '-'));
}
@Override
......
......@@ -270,7 +270,7 @@ public class ManagementInterface implements Runnable {
Logger.getLogger(getClass()).warn("Recovering from management interface failure");
// wait one second 'til recover attempt
Thread.sleep(1000);
Thread.sleep(10000000);
}
}
} catch (Exception ex) {
......
......@@ -24,7 +24,6 @@ import java.lang.reflect.Field;
import java.util.Map;
/**
*
* @author wannes
*/
public class ObjectMarshall {
......
package be.neutrinet.ispng.security;
import be.neutrinet.ispng.vpn.User;
import java.util.UUID;
/**
* Created by wannes on 12/20/14.
*/
public interface OwnedEntity {
public boolean isOwnedBy(User user);
public boolean isOwnedBy(UUID user);
}
package be.neutrinet.ispng.security;
import be.neutrinet.ispng.VPN;
import be.neutrinet.ispng.vpn.User;
import be.neutrinet.ispng.vpn.Users;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
......@@ -17,59 +17,23 @@ public class Policy {
return INSTANCE;
}
public static <T extends OwnedEntity> List<T> filterAccessible(User user, List<T> entities) {
public static <T extends OwnedEntity> List<T> filterAccessible(UUID user, List<T> entities) {
if (entities == null) return new ArrayList<>();
return entities.parallelStream().filter(e -> INSTANCE.canAccess(user, e)).collect(Collectors.toList());
}
public static <T extends OwnedEntity> List<T> filterModifiable(User user, List<T> entities) {
public static <T extends OwnedEntity> List<T> filterModifiable(UUID user, List<T> entities) {
if (entities == null) return new ArrayList<>();
return entities.parallelStream().filter(e -> INSTANCE.canModify(user, e)).collect(Collectors.toList());
}
public boolean canAccess(User user, OwnedEntity entity) {
if (entity == null || user == null) return false;
if (isAdmin(user)) return true;
if (entity.isOwnedBy(user)) return true;
return false;
}
public boolean canM