public IEnumerable <IServiceConfig> Generate(Type type) { var processors = new Dictionary <Type, Func <IEnumerable <IServiceConfig> > > { { typeof(HTTPProxy), delegate { var storedConfig = _db.Single <Configuration>(x => x.Key == ConfigKeys.HttpConfig); var serviceConfig = JsonConvert.DeserializeObject <HTTPConfig>(storedConfig.Value); if (serviceConfig == null) { _logger.LogError("TG: No listeners could be retrieved from the DB for " + typeof(HTTPProxy) + ", using defaults."); serviceConfig = Defaults.HTTP.Value; } return(new List <IServiceConfig> { serviceConfig }); } }, { typeof(OpenVPN), delegate { // In scope OpenVPN Configuration List. var configs = new List <OpenVPNConfig>(); // Select attributes from local database. var storedOpenVPNConfig = _db.Select <Configuration>(x => x.Key.Contains("vpn.openvpn.")); var storedCryptoConfig = _db.Select <Configuration>(x => x.Key.Contains("crypto.")); // Check if values exist. if (storedOpenVPNConfig == null || storedCryptoConfig == null) { _logger.LogError("TG: Could not fetch OpenVPN or Crypto config from the database. Please re-install, no defaults are possible for the CA/PKI."); return(null); } // Certificate Information Placeholder Object. var certInfo = new CertificateBaseInformation(); // Iterate through each configuration key. foreach (var cryptoConfig in storedCryptoConfig) { switch (cryptoConfig.Key) { case ConfigKeys.ServerPFXChain: certInfo.ServerKeyChainBase64PKCS12 = cryptoConfig.Value; break; case ConfigKeys.ServerCertificate: certInfo.ServerBase64PKCS12 = cryptoConfig.Value; break; case ConfigKeys.ServerCertificatePassword: certInfo.ServerCertificatePassword = cryptoConfig.Value; break; case ConfigKeys.CertificationAuthority: certInfo.CertificateAuthorityBase64PKCS12 = cryptoConfig.Value; break; case ConfigKeys.CeritificationAuthorityPassword: certInfo.CertificateAuthoryPassword = cryptoConfig.Value; break; } } // Check to see if any value is null. if ( certInfo.ServerKeyChainBase64PKCS12.IsNullOrEmpty() || certInfo.CertificateAuthorityBase64PKCS12.IsNullOrEmpty() || certInfo.ServerBase64PKCS12.IsNullOrEmpty() || certInfo.CertificateAuthoryPassword.IsNullOrEmpty() || certInfo.ServerCertificatePassword.IsNullOrEmpty() ) { _logger.LogError("TG: One or more crypto parameters are invalid, please re-install."); return(null); } var certificateAuthory = _cryptoService.LoadCertificate( Convert.FromBase64String(certInfo.CertificateAuthorityBase64PKCS12), certInfo.CertificateAuthoryPassword ); var serverCertificate = _cryptoService.LoadCertificate( Convert.FromBase64String(certInfo.ServerBase64PKCS12), certInfo.ServerCertificatePassword ); var baseOpenVPNConfigInJson = _db.Single <Configuration>(x => x.Key == ConfigKeys.OpenVPNBaseConfig); var baseOpenVPNConfig = JsonConvert.DeserializeObject <OpenVPNConfig>(baseOpenVPNConfigInJson.Value); var listenerConfigInJson = _db.Single <Configuration>(x => x.Key == ConfigKeys.OpenVPNListeners); var listeners = JsonConvert.DeserializeObject <List <OpenVPNListener> >(listenerConfigInJson.Value); // Make sure there's configuration information in the database. if (baseOpenVPNConfig == null || listeners == null || listeners.Count == 0) { _logger.LogError("TG: Could not fetch OpenVPN config from the database. Using defaults."); baseOpenVPNConfig = Defaults.OpenVPN.Value; listeners = Defaults.OpenVPNListeners; } // Write configurations for each. foreach (var listener in listeners) { var localConfig = new OpenVPNConfig(_engine, _identity) { Listener = listener }; configs.Add(localConfig); } // Write objects for each configuration foreach (var cfg in configs) { cfg.CACert = certificateAuthory; cfg.ServerCert = serverCertificate; cfg.PKCS12Certificate = certInfo.ServerKeyChainBase64PKCS12; cfg.AllowMultipleConnectionsFromSameClient = baseOpenVPNConfig.AllowMultipleConnectionsFromSameClient; cfg.ClientToClient = baseOpenVPNConfig.ClientToClient; cfg.MaxClients = baseOpenVPNConfig.MaxClients; cfg.DhcpOptions = baseOpenVPNConfig.DhcpOptions; cfg.RedirectGateway = baseOpenVPNConfig.RedirectGateway; cfg.PushedNetworks = baseOpenVPNConfig.PushedNetworks; } // Return the list of configurations. return(configs); } } }; // Try to return the value. if (processors.TryGetValue(type, out var value)) { return(value()); } // Failed, return null. return(null); }
public async Task <IActionResult> HandleOpenVPNConfigUpdate([FromBody] OpenVPNConfigUpdateRequest config) { if (!ModelState.IsValid || !config.Validate(out var errors)) { _response.Errors.Add(Errors.VALIDATION_FAILED, errors); return(BadRequest(_response)); } // OK bob, the basic schema is valid. Let's do some semantics checks now. // There is also no need to coppy result.ErrorMessages out into our buffer this time. If we're here, that means it all passed already. // Let's check the listeners. var networksAlreadySeen = new List <IPNetwork>(); var listenersAlreadySeen = new Dictionary <int, List <OpenVPNListener> >(); var errorEncountered = false; foreach (var listener in config.Listeners) { var parsedNetwork = IPNetwork.Parse(listener.Network); var parsedAddress = IPAddress.Parse(listener.IPAddress); foreach (var network in networksAlreadySeen) { Logger.LogDebug($"Checking if {network} overlaps with any already defined networks: {networksAlreadySeen.Count} already seen."); // Uh oh, we got an overlap. No bueno. if (!network.Contains(parsedNetwork) && !network.Equals(parsedNetwork)) { continue; } ; _response.Errors.Add(Errors.FIELD_OVERLAP, $"listeners.network:{network},{parsedNetwork}"); errorEncountered = true; break; } networksAlreadySeen.Add(parsedNetwork); // If we got here, that means listeners do not have network overlaps. if (!errorEncountered) { // Now, let's check for port overlaps / same listener being defined multiple times listenersAlreadySeen.TryGetValue(listener.Port.Value, out var listOfexistingListenersOnPort); // OK, there are other listeners on this port. if (listOfexistingListenersOnPort != null) { foreach (var abstractedListener in listOfexistingListenersOnPort) { if (!abstractedListener.Protocol.Equals(listener.Protocol)) { Logger.LogDebug("Protocols do not match, continuing search for conflicts."); continue; } if (abstractedListener.IPAddress.Equals(IPAddress.Any.ToString())) { _response.Errors.Add(Errors.PORT_CONFLICT_FOUND, "0.0.0.0"); errorEncountered = true; break; } // Duplicate listener found if (abstractedListener.IPAddress.Equals(listener.IPAddress)) { _response.Errors.Add(Errors.DUPLICATE_IP_AS_LISTENER_REQUEST, listener.IPAddress); errorEncountered = true; break; } } // If we got here, it was sufficiently unique. listOfexistingListenersOnPort.Add(listener); } else { listOfexistingListenersOnPort = new List <OpenVPNListener> { listener }; listenersAlreadySeen.Add(listener.Port.Value, listOfexistingListenersOnPort); } } } if (HasErrors()) { return(BadRequest(_response)); } var baseConfig = new OpenVPNConfig(null, null) { AllowMultipleConnectionsFromSameClient = config.AllowMultipleConnectionsFromSameClient.Value, ClientToClient = config.ClientToClient.Value, MaxClients = config.MaxClients.Value, PushedNetworks = config.PushedNetworks as List <string>, RedirectGateway = config.RedirectGateway as List <RedirectGatewayOptions>, DhcpOptions = config.DhcpOptions as List <Tuple <DhcpOptions, string> >, }; var allListeners = config.Listeners as List <OpenVPNListener>; // Let's commit it all to DB as required. await ConfigUtils.CreateOrUpdateConfig(Db, ConfigKeys.OpenVPNBaseConfig, JsonConvert.SerializeObject(baseConfig)); await ConfigUtils.CreateOrUpdateConfig(Db, ConfigKeys.OpenVPNListeners, JsonConvert.SerializeObject(allListeners)); // Now, we need to figure out if service restart will be needed. // However, since this is a 3rd party daemon -- our work is way easier. We can't make ANY changes at runtime. // TODO: Do a config diff before doing this, but that's skipped for now. var targetServiceType = typeof(OpenVPN); var service = _serviceManager.GetService(targetServiceType); // Let's get the parsed config out of the DB, and update svc state if needed. var reconciledConfig = _serviceConfigManager.Generate(targetServiceType); if (service.GetState() == ServiceState.Running) { // Yep, restart will be needed. _response.Message = Messages.SERVICE_RESTART_NEEDED; } service.SetConfig(reconciledConfig); _response.Result = config; return(Ok(_response)); }
// TODO: Look into whether making this a responsibility of the service itself (generation) is a more sane appraoch // This would foster loose coupling private async Task <UserServiceResource> GenerateUserServiceResource(User user, Type type, IEnumerable <IServiceConfig> configs) { var serviceReference = new UserServiceResource(); EnsureServiceAccess(user, type); // Giant hack, but hey, it works ┐(´∀`)┌ヤレヤレ switch (true) { case bool _ when type == typeof(HTTPProxy): // HTTPProxy has a global config, and thus only one instance // Guaranteed to not be null, so inspection is not needed. // ReSharper disable once AssignNullToNotNullAttribute var config = (HTTPConfig)configs.First(); var proxies = new List <string>(); foreach (var listener in config.listeners) { // Gotta translate 0.0.0.0 into something people can actually connect to // This is currently replaced by the default outgoing IP, which should work assuming NAT port forwarding succeeded // Or there is no NAT involved. proxies.Add(await _ipResolver.Translate(listener.Item1) + ":" + listener.Item2); } serviceReference.AccessReference = proxies; serviceReference.AccessCredentials = Messages.SPECTERO_USERNAME_PASSWORD; break; case bool _ when type == typeof(OpenVPN): // OpenVPN is a multi-instance service var allListeners = new List <OpenVPNListener>(); OpenVPNConfig sanitizedOpenVPNConfig = null; foreach (var vpnConfig in configs) { var castConfig = vpnConfig as OpenVPNConfig; if (castConfig?.Listener != null) { // ReSharper disable once InconsistentNaming var translatedIP = await _ipResolver.Translate(castConfig.Listener.IPAddress); // This is done to force a translation of local addresses castConfig.Listener.IPAddress = translatedIP.ToString(); allListeners.Add(castConfig.Listener); // Setting it only once would be ideal, but eh -- overhead is low enough to make this work. sanitizedOpenVPNConfig = castConfig; } } if (!allListeners.IsNullOrEmpty()) { serviceReference.AccessConfig = await _razorLightEngine.CompileRenderAsync("OpenVPNUser", new OpenVPNUserConfig { Listeners = allListeners, User = user, Identity = _identityProvider.GetGuid().ToString(), BaseConfig = sanitizedOpenVPNConfig }); serviceReference.AccessCredentials = user.CertKey.IsNullOrEmpty() ? Messages.SPECTERO_USERNAME_PASSWORD : user.CertKey; } break; } return(serviceReference); }