Beispiel #1
0
        /// <summary>
        /// Lists the VPN user certificates.
        /// </summary>
        private void UserList()
        {
            DirectNotAllowed();
            RootLogin();

            var columnWidths = new int[] { "Revoked ".Length, "MM-dd-yyyy HH:mm:ss".Length, "A901C8F59E261D83".Length, "Username".Length };

            Console.WriteLine();
            Console.WriteLine($"{PadRight("Status", columnWidths[0])}   {PadRight("Valid Until", columnWidths[1])}   {PadRight("Thumbprint", columnWidths[2])}   {PadRight("Username", columnWidths[3])}");
            Console.WriteLine($"{new string('-', columnWidths[0])}   {new string('-', columnWidths[1])}   {new string('-', columnWidths[2])}   {new string('-', columnWidths[3])}");

            try
            {
                foreach (var cert in ListCerts(GetVpnCaFiles())
                         .OrderBy(c => c.Name.ToLowerInvariant())
                         .ThenBy(c => c.ValidUntil))
                {
                    if (cert.Name == "ca" || cert.Name == "server")
                    {
                        continue;
                    }

                    var status = cert.IsValid ? "Valid" : "Revoked";

                    Console.WriteLine($"{PadRight(status, columnWidths[0])}   {PadRight(cert.ValidUntil.ToString("MM-dd-yyyy HH:mm:ss"), columnWidths[1])}   {PadRight(cert.Thumbprint, columnWidths[2])}   {cert.Name}");
                }
            }
            finally
            {
                HiveHelper.CloseHive();
            }
        }
Beispiel #2
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            // Create process terminator to handle termination signals.

            terminator = new ProcessTerminator(log);

            try
            {
                var commandLine = new CommandLine(args);
                var command     = commandLine.Arguments.ElementAtOrDefault(0);

                if (command == null)
                {
                    log.LogError("usage: vegomatic COMMAND ARGS...");
                    Program.Exit(1, immediate: true);
                }

                switch (command)
                {
                case "cephfs":

                    await new CephFS().ExecAsync(commandLine.Shift(1));
                    break;

                case "issue-mntc":

                    await new IssueMntc().ExecAsync(commandLine.Shift(1));
                    break;

                default:
                case "test-server":

                    await new TestServer().ExecAsync(commandLine.Shift(1));
                    break;
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #3
0
        /// <summary>
        /// Copies the current CRL to eack of the OpenVPN servers.
        /// </summary>
        private void UpdateCRL()
        {
            DirectNotAllowed();
            RootLogin();

            try
            {
                var vpnCaFiles = GetVpnCaFiles();
                // Initialize the file paths.
                //
                // IMPORTANT:
                //
                // Do not change these file names because the [VpnCaFiles] class
                // depends on this naming convention.

                Directory.CreateDirectory(caFolder);

                vpnCaFiles.Extract(caFolder);

                var indexPath     = Path.Combine(caFolder, "index.txt");
                var caSignCnfPath = Path.Combine(caFolder, "ca-sign.cnf");
                var caCnfPath     = Path.Combine(caFolder, "ca.cnf");
                var caKeyPath     = Path.Combine(caFolder, "ca.key");
                var caReqPath     = Path.Combine(caFolder, "ca.req");
                var caCrtPath     = Path.Combine(caFolder, "ca.crt");
                var dhParamPath   = Path.Combine(caFolder, "dhparam.pem");
                var serverCnfPath = Path.Combine(caFolder, "server.cnf");
                var serverKeyPath = Path.Combine(caFolder, "server.key");
                var serverReqPath = Path.Combine(caFolder, "server.req");
                var serverCrtPath = Path.Combine(caFolder, "server.crt");
                var taKeyPath     = Path.Combine(caFolder, "ta.key");
                var crlnumberPath = Path.Combine(caFolder, "crlnumber");
                var crlPath       = Path.Combine(caFolder, "crl.pem");

                // Write the updated CRL to each manager.

                var crlText = vpnCaFiles.GetFile("crl.pem");

                Console.WriteLine();

                foreach (var manager in hive.Managers)
                {
                    Console.WriteLine($"*** {manager.Name}: Updating");
                    manager.UploadText("/etc/openvpn/crl.pem", crlText);
                    manager.SudoCommand("chmod 664 /etc/openvpn/crl.pem");
                }
            }
            finally
            {
                HiveHelper.CloseHive();
            }
        }
Beispiel #4
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            terminator = new ProcessTerminator(log);

            try
            {
                // Establish the hive connections.

                if (NeonHelper.IsDevWorkstation)
                {
                    HiveHelper.OpenHiveRemote();
                }
                else
                {
                    HiveHelper.OpenHive();
                }

                await RunAsync();
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
        }
Beispiel #5
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));

            // Create process terminator to handle termination signals.

            terminator = new ProcessTerminator(log);

            terminator.AddHandler(
                () =>
            {
                // Cancel any operations in progress.

                terminator.CancellationTokenSource.Cancel();
            });

            // Read the environment variables.

            // $hack(jeff.lill:
            //
            // We're going to scan the Consul configuration key to determine whether this
            // instance is managing the public or private proxy (or bridges) so we'll
            // be completely compatible with existing deployments.
            //
            // In theory, we could have passed a new environment variable but that's not
            // worth the trouble.

            configKey = Environment.GetEnvironmentVariable("CONFIG_KEY");

            if (string.IsNullOrEmpty(configKey))
            {
                log.LogError("[CONFIG_KEY] environment variable is required.");
                Program.Exit(1, immediate: true);
            }

            isPublic = configKey.Contains("/public/");

            var proxyName = isPublic ? "public" : "private";

            serviceName = $"neon-proxy-{proxyName}:{GitVersion}";

            log.LogInfo(() => $"Starting [{serviceName}]");

            configHashKey = Environment.GetEnvironmentVariable("CONFIG_HASH_KEY");

            if (string.IsNullOrEmpty(configHashKey))
            {
                log.LogError("[CONFIG_HASH_KEY] environment variable is required.");
                Program.Exit(1, immediate: true);
            }

            vaultCredentialsName = Environment.GetEnvironmentVariable("VAULT_CREDENTIALS");

            if (string.IsNullOrEmpty(vaultCredentialsName))
            {
                log.LogWarn("HTTPS routes are not supported because VAULT_CREDENTIALS is not specified or blank.");
            }

            var warnSeconds = Environment.GetEnvironmentVariable("WARN_SECONDS");

            if (string.IsNullOrEmpty(warnSeconds) || !double.TryParse(warnSeconds, out var warnSecondsValue))
            {
                warnInterval = TimeSpan.FromSeconds(300);
            }
            else
            {
                warnInterval = TimeSpan.FromSeconds(warnSecondsValue);
            }

            var startSeconds = Environment.GetEnvironmentVariable("START_SECONDS");

            if (string.IsNullOrEmpty(startSeconds) || !double.TryParse(startSeconds, out var startSecondsValue))
            {
                startDelay = TimeSpan.FromSeconds(10);
            }
            else
            {
                startDelay = TimeSpan.FromSeconds(startSecondsValue);
            }

            var maxHAProxyCountString = Environment.GetEnvironmentVariable("MAX_HAPROXY_COUNT");

            if (!int.TryParse(maxHAProxyCountString, out maxHAProxyCount))
            {
                maxHAProxyCount = 10;
            }

            if (maxHAProxyCount < 0)
            {
                maxHAProxyCount = 0;
            }

            debugMode = "true".Equals(Environment.GetEnvironmentVariable("DEBUG"), StringComparison.InvariantCultureIgnoreCase);

            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");
            log.LogInfo(() => $"CONFIG_KEY={configKey}");
            log.LogInfo(() => $"CONFIG_HASH_KEY={configHashKey}");
            log.LogInfo(() => $"VAULT_CREDENTIALS={vaultCredentialsName}");
            log.LogInfo(() => $"WARN_SECONDS={warnInterval}");
            log.LogInfo(() => $"START_SECONDS={startDelay}");
            log.LogInfo(() => $"MAX_HAPROXY_COUNT={maxHAProxyCount}");
            log.LogInfo(() => $"DEBUG={debugMode}");

            // Ensure that the required directories exist.

            Directory.CreateDirectory(tmpfsFolder);
            Directory.CreateDirectory(configFolder);
            Directory.CreateDirectory(configUpdateFolder);

            // Establish the hive connections.

            if (NeonHelper.IsDevWorkstation)
            {
                throw new NotImplementedException("This service works only within a Linux container with HAProxy installed.");

                //var vaultCredentialsSecret = "neon-proxy-manager-credentials";

                //Environment.SetEnvironmentVariable("VAULT_CREDENTIALS", vaultCredentialsSecret);

                //hive = HiveHelper.OpenHiveRemote(new DebugSecrets().VaultAppRole(vaultCredentialsSecret, $"neon-proxy-{proxyName}"));
            }
            else
            {
                hive = HiveHelper.OpenHive();
            }

            try
            {
                // Log into Vault using the Vault credentials persisted as a Docker
                // secret, if one was specified.  We won't open Vault otherwise.

                if (!string.IsNullOrEmpty(vaultCredentialsName))
                {
                    var vaultSecret = HiveHelper.GetSecret(vaultCredentialsName);

                    if (string.IsNullOrEmpty(vaultSecret))
                    {
                        log.LogCritical($"Cannot read Docker secret [{vaultCredentialsName}].");
                        Program.Exit(1, immediate: true);
                    }

                    var vaultCredentials = HiveCredentials.ParseJson(vaultSecret);

                    if (vaultCredentials == null)
                    {
                        log.LogCritical($"Cannot parse Docker secret [{vaultCredentialsName}].");
                        Program.Exit(1, immediate: true);
                    }

                    log.LogInfo(() => $"Connecting: Vault");
                    vault = HiveHelper.OpenVault(vaultCredentials);
                }
                else
                {
                    vault = null;

                    // $hack(jeff.lill):
                    //
                    // This is a bit of backwards compatible hack.  Instances started without the
                    // VAULT_CREDENTIALS environment variable are assumed to be proxy bridges.

                    isBridge = true;
                }

                // Open Consul and then start the service tasks.

                log.LogInfo(() => $"Connecting: Consul");

                using (consul = HiveHelper.OpenConsul())
                {
                    log.LogInfo(() => $"Connecting: {HiveMQChannels.ProxyNotify} channel");

                    // Verify that the required Consul keys exist or loop to wait until they
                    // are created.  This will allow the service wait for pending hive setup
                    // operations to be completed.

                    while (!await consul.KV.Exists(configKey))
                    {
                        log.LogWarn(() => $"Waiting for [{configKey}] key to be present in Consul.");
                        await Task.Delay(TimeSpan.FromSeconds(5));
                    }

                    while (!await consul.KV.Exists(configHashKey))
                    {
                        log.LogWarn(() => $"Waiting for [{configHashKey}] key to be present in Consul.");
                        await Task.Delay(TimeSpan.FromSeconds(5));
                    }

                    // Crank up the service tasks.

                    await NeonHelper.WaitAllAsync(
                        ErrorPollerAsync(),
                        HAProxShim());
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #6
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            // Create process terminator to handle process termination signals.

            terminator = new ProcessTerminator(log);

            try
            {
                // Establish the hive connections.

                if (NeonHelper.IsDevWorkstation)
                {
                    var secrets = new DebugSecrets();

                    // NOTE:
                    //
                    // Add your target hive's Vault credentials here for
                    // manual debugging.  Take care not to commit sensitive
                    // credentials for production hives.
                    //
                    // You'll find this information in the ROOT hive login
                    // for the target hive.

                    secrets.Add("neon-hive-manager-vaultkeys",
                                new VaultCredentials()
                    {
                        RootToken    = "cd5831fa-86ec-cc22-b1f3-051f88147382",
                        KeyThreshold = 1,
                        UnsealKeys   = new List <string>()
                        {
                            "8SgwdO/GwqJ7nyxT2tK2n1CCR3084kQVh7gEy8jNQh8="
                        }
                    });

                    hive = HiveHelper.OpenHiveRemote(secrets);
                }
                else
                {
                    hive = HiveHelper.OpenHive(sshCredentialsSecret: "neon-ssh-credentials");
                }

                // Ensure that we're running on a manager node.  We won't be able
                // to query swarm status otherwise.

                var nodeRole = Environment.GetEnvironmentVariable("NEON_NODE_ROLE");

                if (string.IsNullOrEmpty(nodeRole))
                {
                    log.LogCritical(() => "Service does not appear to be running on a neonHIVE.");
                    Program.Exit(1, immediate: true);
                }

                if (!string.Equals(nodeRole, NodeRole.Manager, StringComparison.OrdinalIgnoreCase))
                {
                    log.LogCritical(() => $"[neon-hive-manager] service is running on a [{nodeRole}] hive node.  Running on only [{NodeRole.Manager}] nodes are supported.");
                    Program.Exit(1, immediate: true);
                }

                // Open the hive data services and then start the main service task.

                log.LogDebug(() => $"Connecting: Consul");

                using (consul = HiveHelper.OpenConsul())
                {
                    log.LogDebug(() => $"Connecting: Docker");

                    using (docker = HiveHelper.OpenDocker())
                    {
                        log.LogInfo(() => $"Connecting: {HiveMQChannels.ProxyNotify} channel");

                        // We're passing [useBootstrap=true] here so that the HiveMQ client will
                        // connect directly to the HiveMQ cluster nodes as opposed to routing
                        // traffic through the private traffic manager.  This is necessary because
                        // the load balancers rely on HiveMQ to broadcast update notifications.
                        //
                        // One consequence of this is that this service will need to be restarted
                        // whenever HiveMQ instances are relocated to different hive hosts.
                        // We're going to monitor for changes to the HiveMQ bootstrap settings
                        // and gracefully terminate the process when this happens.  We're then
                        // depending on Docker to restart the process so we'll be able to pick
                        // up the change.

                        hive.HiveMQ.Internal.HiveMQBootstrapChanged +=
                            (s, a) =>
                        {
                            log.LogInfo("HiveMQ bootstrap settings change detected.  Terminating service with [exitcode=-1] expecting that Docker will restart it.");

                            // Use ExitCode=-1 so that we'll restart even if the service/container
                            // was not configured with [restart=always].

                            terminator.Exit(-1);
                        };

                        using (proxyNotifyChannel = hive.HiveMQ.Internal.GetProxyNotifyChannel(useBootstrap: true).Open())
                        {
                            await RunAsync();
                        }
                    }
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #7
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            // Create process terminator to handle process termination signals.

            terminator = new ProcessTerminator(log);

            terminator.AddHandler(
                () =>
            {
                // Cancel any operations in progress.

                exit = true;

                terminator.CancellationTokenSource.Cancel();

                // This gracefully closes the [proxyNotifyChannel] so HiveMQ will
                // promptly remove the associated queue.

                if (proxyNotifyChannel != null)
                {
                    proxyNotifyChannel.Dispose();
                    proxyNotifyChannel = null;
                }

                try
                {
                    NeonHelper.WaitFor(() => !processingConfigs, terminator.Timeout);
                    log.LogInfo(() => "Tasks stopped gracefully.");
                }
                catch (TimeoutException)
                {
                    log.LogWarn(() => $"Tasks did not stop within [{terminator.Timeout}].");
                }
            });

            // Establish the hive connections.

            if (NeonHelper.IsDevWorkstation)
            {
                var vaultCredentialsSecret = "neon-proxy-manager-credentials";

                Environment.SetEnvironmentVariable("VAULT_CREDENTIALS", vaultCredentialsSecret);

                hive = HiveHelper.OpenHiveRemote(new DebugSecrets().VaultAppRole(vaultCredentialsSecret, "neon-proxy-manager"));
            }
            else
            {
                hive = HiveHelper.OpenHive();
            }

            // [neon-proxy-manager] requires access to the [IHostingManager] implementation for the
            // current environment, so we'll need to initialize the hosting loader.

            HostingLoader.Initialize();

            try
            {
                // Log into Vault using a Docker secret.

                var vaultCredentialsSecret = Environment.GetEnvironmentVariable("VAULT_CREDENTIALS");

                if (string.IsNullOrEmpty(vaultCredentialsSecret))
                {
                    log.LogCritical("[VAULT_CREDENTIALS] environment variable does not exist.");
                    Program.Exit(1, immediate: true);
                }

                var vaultSecret = HiveHelper.GetSecret(vaultCredentialsSecret);

                if (string.IsNullOrEmpty(vaultSecret))
                {
                    log.LogCritical($"Cannot read Docker secret [{vaultCredentialsSecret}].");
                    Program.Exit(1, immediate: true);
                }

                var vaultCredentials = HiveCredentials.ParseJson(vaultSecret);

                if (vaultCredentials == null)
                {
                    log.LogCritical($"Cannot parse Docker secret [{vaultCredentialsSecret}].");
                    Program.Exit(1, immediate: true);
                }

                // Open the hive data services and then start the main service task.

                log.LogInfo(() => $"Connecting: Vault");

                using (vault = HiveHelper.OpenVault(vaultCredentials))
                {
                    log.LogInfo(() => $"Connecting: Consul");

                    using (consul = HiveHelper.OpenConsul())
                    {
                        log.LogInfo(() => $"Connecting: Docker");

                        using (docker = HiveHelper.OpenDocker())
                        {
                            log.LogInfo(() => $"Connecting: {HiveMQChannels.ProxyNotify} channel");

                            // NOTE:
                            //
                            // We're passing [useBootstrap=true] here so that the HiveMQ client will
                            // connect directly to the HiveMQ cluster nodes as opposed to routing
                            // traffic through the private traffic manager.  This is necessary because
                            // the load balancers rely on HiveMQ to broadcast update notifications.
                            //
                            // One consequence of this is that this service will need to be restarted
                            // whenever HiveMQ instances are relocated to different hive hosts.
                            // We're going to monitor for changes to the HiveMQ bootstrap settings
                            // and gracefully terminate the process when this happens.  We're then
                            // depending on Docker to restart the process so we'll be able to pick
                            // up the change.

                            hive.HiveMQ.Internal.HiveMQBootstrapChanged +=
                                (s, a) =>
                            {
                                log.LogInfo("HiveMQ bootstrap settings change detected.  Terminating service with [exitcode=-1] expecting that Docker will restart it.");

                                // Use ExitCode=-1 so that we'll restart even if the service/container
                                // was not configured with [restart=always].

                                terminator.Exit(-1);
                            };

                            using (proxyNotifyChannel = hive.HiveMQ.Internal.GetProxyNotifyChannel(useBootstrap: true).Open())
                            {
                                // Read the service settings, initializing their default values
                                // if they don't already exist.

                                if (!await consul.KV.Exists(certWarnDaysKey))
                                {
                                    log.LogInfo($"Persisting setting [{certWarnDaysKey}=30.0]");
                                    await consul.KV.PutDouble(certWarnDaysKey, 30.0);
                                }

                                if (!await consul.KV.Exists(cacheRemoveSecondsKey))
                                {
                                    log.LogInfo($"Persisting setting [{cacheRemoveSecondsKey}=300.0]");
                                    await consul.KV.PutDouble(cacheRemoveSecondsKey, 300.0);
                                }

                                if (!await consul.KV.Exists(failsafeSecondsKey))
                                {
                                    log.LogInfo($"Persisting setting [{failsafeSecondsKey}=120.0]");
                                    await consul.KV.PutDouble(failsafeSecondsKey, 120);
                                }

                                certWarnTime     = TimeSpan.FromDays(await consul.KV.GetDouble(certWarnDaysKey));
                                cacheRemoveDelay = TimeSpan.FromDays(await consul.KV.GetDouble(cacheRemoveSecondsKey));
                                failsafeInterval = TimeSpan.FromSeconds(await consul.KV.GetDouble(failsafeSecondsKey));

                                log.LogInfo(() => $"Using setting [{certWarnDaysKey}={certWarnTime.TotalSeconds}]");
                                log.LogInfo(() => $"Using setting [{cacheRemoveSecondsKey}={cacheRemoveDelay.TotalSeconds}]");
                                log.LogInfo(() => $"Using setting [{failsafeSecondsKey}={failsafeInterval.TotalSeconds}]");

                                // Run the service tasks.

                                var tasks = new List <Task>();

                                tasks.Add(ConfigGeneratorAsync());
                                tasks.Add(FailsafeBroadcasterAsync());
                                await NeonHelper.WaitAllAsync(tasks);
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #8
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));

            // Create process terminator to handle termination signals.

            terminator = new ProcessTerminator(log);

            terminator.AddHandler(
                () =>
            {
                // Cancel any operations in progress.

                terminator.CancellationTokenSource.Cancel();
            });

            // Read the environment variables.

            // $hack(jeff.lill:
            //
            // We're going to scan the Consul configuration key to determine whether this
            // instance is managing the public or private proxy (or bridges) so we'll
            // be completely compatible with existing deployments.
            //
            // In theory, we could have passed a new environment variable but that's not
            // worth the trouble.

            configKey = Environment.GetEnvironmentVariable("CONFIG_KEY");

            if (string.IsNullOrEmpty(configKey))
            {
                log.LogError("[CONFIG_KEY] environment variable is required.");
                Program.Exit(1, immediate: true);
            }

            isPublic = configKey.Contains("/public/");

            var proxyName = isPublic ? "public" : "private";

            serviceName = $"neon-proxy-{proxyName}-cache:{GitVersion}";

            log.LogInfo(() => $"Starting [{serviceName}]");

            configHashKey = Environment.GetEnvironmentVariable("CONFIG_HASH_KEY");

            if (string.IsNullOrEmpty(configHashKey))
            {
                log.LogError("[CONFIG_HASH_KEY] environment variable is required.");
                Program.Exit(1, immediate: true);
            }

            var memoryLimitValue = Environment.GetEnvironmentVariable("MEMORY_LIMIT");

            if (string.IsNullOrEmpty(memoryLimitValue))
            {
                memoryLimitValue = DefMemoryLimitString;
            }

            if (!NeonHelper.TryParseCount(memoryLimitValue, out var memoryLimitDouble))
            {
                memoryLimitDouble = DefMemoryLimit;
            }

            if (memoryLimitDouble < MinMemoryLimit)
            {
                log.LogWarn(() => $"[MEMORY_LIMIT={memoryLimitValue}] is to small.  Using [{MinMemoryLimitString}] instead.");
                memoryLimitDouble = MinMemoryLimit;
            }

            memoryLimit = (long)memoryLimitDouble;

            var warnSeconds = Environment.GetEnvironmentVariable("WARN_SECONDS");

            if (string.IsNullOrEmpty(warnSeconds) || !double.TryParse(warnSeconds, out var warnSecondsValue))
            {
                warnInterval = TimeSpan.FromSeconds(300);
            }
            else
            {
                warnInterval = TimeSpan.FromSeconds(warnSecondsValue);
            }

            debugMode = "true".Equals(Environment.GetEnvironmentVariable("DEBUG"), StringComparison.InvariantCultureIgnoreCase);

            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");
            log.LogInfo(() => $"CONFIG_KEY={configKey}");
            log.LogInfo(() => $"CONFIG_HASH_KEY={configHashKey}");
            log.LogInfo(() => $"MEMORY_LIMIT={memoryLimit}");
            log.LogInfo(() => $"WARN_SECONDS={warnInterval}");
            log.LogInfo(() => $"DEBUG={debugMode}");

            // Ensure that the required directories exist.

            Directory.CreateDirectory(tmpfsFolder);
            Directory.CreateDirectory(configFolder);
            Directory.CreateDirectory(configUpdateFolder);

            // Establish the hive connections.

            if (NeonHelper.IsDevWorkstation)
            {
                throw new NotImplementedException("This service works only within a Linux container with Varnish installed.");

                //var vaultCredentialsSecret = "neon-proxy-manager-credentials";

                //Environment.SetEnvironmentVariable("VAULT_CREDENTIALS", vaultCredentialsSecret);

                //hive = HiveHelper.OpenHiveRemote(new DebugSecrets().VaultAppRole(vaultCredentialsSecret, $"neon-proxy-{proxyName}"));
            }
            else
            {
                hive = HiveHelper.OpenHive();
            }

            try
            {
                // Open Consul and then start the service tasks.

                log.LogInfo(() => $"Connecting: Consul");

                using (consul = HiveHelper.OpenConsul())
                {
                    log.LogInfo(() => $"Connecting: {HiveMQChannels.ProxyNotify} channel");

                    // Verify that the required Consul keys exist or loop to wait until they
                    // are created.  This will allow the service wait for pending hive setup
                    // operations to be completed.

                    while (!await consul.KV.Exists(configKey))
                    {
                        log.LogWarn(() => $"Waiting for [{configKey}] key to be present in Consul.");
                        await Task.Delay(TimeSpan.FromSeconds(5));
                    }

                    while (!await consul.KV.Exists(configHashKey))
                    {
                        log.LogWarn(() => $"Waiting for [{configHashKey}] key to be present in Consul.");
                        await Task.Delay(TimeSpan.FromSeconds(5));
                    }

                    // Crank up the service tasks.

                    log.LogInfo(() => $"Starting service tasks.");

                    await NeonHelper.WaitAllAsync(
                        CacheWarmer(),
                        ErrorPollerAsync(),
                        VarnishShim());
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #9
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            // Parse the environment variable settings.

            var environment = new EnvironmentParser(log);

            pollInterval   = environment.Get("POLL_INTERVAL", TimeSpan.FromSeconds(5), validator: v => v > TimeSpan.Zero);
            verifyInterval = environment.Get("VERIFY_INTERVAL", TimeSpan.FromMinutes(5), validator: v => v > TimeSpan.Zero);

            // Create process terminator to handle process termination signals.

            terminator = new ProcessTerminator(log);

            try
            {
                // Establish the hive connections.

                if (NeonHelper.IsDevWorkstation)
                {
                    hive = HiveHelper.OpenHiveRemote();

                    // For testing and development, we're going to write a test
                    // hosts file to [%NF_TEMP\neon-dns-hosts.txt] so we can see
                    // what's happening outside of a hive.

                    powerDnsHostsPath = Environment.ExpandEnvironmentVariables("%NF_TEMP%\\neon-dns-hosts.txt");

                    File.WriteAllText(powerDnsHostsPath,
                                      $@"# PowerDNS Recursor authoritatively answers for [*.HIVENAME.nhive.io] hostnames.
# on the local node using these mappings.

10.0.0.30       {HiveHelper.Hive.Definition.Hostnames.Consul}

# Internal hive Vault mappings:

10.0.0.30       {HiveHelper.Hive.Definition.Hostnames.Vault}
10.0.0.30       {HiveHelper.Hive.FirstManager.Name}.{HiveHelper.Hive.Definition.Hostnames.Vault}

# Internal hive registry cache related mappings:

10.0.0.30       {HiveHelper.Hive.FirstManager.Name}.{HiveHelper.Hive.Definition.Hostnames.RegistryCache}

# Internal hive log pipeline related mappings:

10.0.0.30       {HiveHelper.Hive.Definition.Hostnames.LogEsData}
");
                    // We're also going to create a temporary folder for the reload signal.

                    reloadSignalPath = Environment.ExpandEnvironmentVariables("%NF_TEMP%\\neon-dns\\reload");

                    Directory.CreateDirectory(Path.GetDirectoryName(reloadSignalPath));
                }
                else
                {
                    hive = HiveHelper.OpenHive();
                }

                // Ensure that we're running on a manager node.  This is required because
                // we need to be able to update the [/etc/powerdns/hosts] files deployed
                // on the managers.

                var nodeRole = Environment.GetEnvironmentVariable("NEON_NODE_ROLE");

                if (string.IsNullOrEmpty(nodeRole))
                {
                    log.LogCritical(() => "Service does not appear to be running on a neonHIVE.");
                    Program.Exit(1, immediate: true);
                }

                if (!string.Equals(nodeRole, NodeRole.Manager, StringComparison.OrdinalIgnoreCase))
                {
                    log.LogCritical(() => $"[neon-dns] service is running on a [{nodeRole}] hive node.  Only [{NodeRole.Manager}] nodes are supported.");
                    Program.Exit(1, immediate: true);
                }

                // Ensure that the [/etc/powerdns/hosts] file was mapped into the container.

                if (!File.Exists(powerDnsHostsPath))
                {
                    log.LogCritical(() => $"[neon-dns] service cannot locate [{powerDnsHostsPath}] on the host manager.  Was this mounted to the container as read/write?");
                    Program.Exit(1, immediate: true);
                }

                // Open Consul and then start the main service task.

                log.LogDebug(() => $"Connecting: Consul");

                using (consul = HiveHelper.OpenConsul())
                {
                    await RunAsync();
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #10
0
        /// <summary>
        /// Application entry point.
        /// </summary>
        /// <param name="args">Command line arguments.</param>
        public static async Task Main(string[] args)
        {
            LogManager.Default.SetLogLevel(Environment.GetEnvironmentVariable("LOG_LEVEL"));
            log = LogManager.Default.GetLogger(typeof(Program));
            log.LogInfo(() => $"Starting [{serviceName}]");
            log.LogInfo(() => $"LOG_LEVEL={LogManager.Default.LogLevel.ToString().ToUpper()}");

            // Parse the environment variable settings.

            var environment = new EnvironmentParser(log);

            nameservers  = environment.Get("NAMESERVERS", "8.8.8.8,8.8.4.4").Split(',');
            pingTimeout  = environment.Get("PING_TIMEOUT", TimeSpan.FromSeconds(1.5), validator: v => v > TimeSpan.Zero);
            pollInterval = environment.Get("POLL_INTERVAL", TimeSpan.FromSeconds(5), validator: v => v > TimeSpan.Zero);
            warnInterval = environment.Get("WARN_INTERVAL", TimeSpan.FromMinutes(5), validator: v => v > TimeSpan.Zero);

            // Create a timer so we'll avoid spamming the logs with warnings.

            warnTimer = new PolledTimer(warnInterval, autoReset: true);
            warnTimer.FireNow();    // Set so that the first warnings detected will be reported immediately.

            // Create the object that will actually perform the hostname lookups
            // and health pings.  This object caches things to improve performance.

            healthResolver = new HealthResolver(nameservers);

            // Create process terminator to handle termination signals.

            terminator = new ProcessTerminator(log);

            try
            {
                // Establish the hive connections.

                if (NeonHelper.IsDevWorkstation)
                {
                    hive = HiveHelper.OpenHiveRemote();
                }
                else
                {
                    hive = HiveHelper.OpenHive();
                }

                // Open Consul and then start the main service task.

                log.LogDebug(() => $"Connecting: Consul");

                using (consul = HiveHelper.OpenConsul())
                {
                    await RunAsync();
                }
            }
            catch (Exception e)
            {
                log.LogCritical(e);
                Program.Exit(1);
                return;
            }
            finally
            {
                HiveHelper.CloseHive();
                terminator.ReadyToExit();
            }

            Program.Exit(0);
            return;
        }
Beispiel #11
0
        /// <summary>
        /// Creates a new hive login.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void UserCreate(CommandLine commandLine)
        {
            DirectNotAllowed();

            var username = commandLine.Arguments.FirstOrDefault();

            if (string.IsNullOrEmpty(username))
            {
                Console.Error.WriteLine("***ERROR: USER argument is required.");
                Program.Exit(1);
            }

            var isUserValid = true;

            foreach (var ch in username)
            {
                if (!(char.IsLetterOrDigit(ch) || ch == '.' || ch == '_' || ch == '-'))
                {
                    isUserValid = false;
                    break;
                }
            }

            if (!isUserValid)
            {
                Console.WriteLine($"***ERROR: USER [{username}] is not valid.  Only letters, digits, periods, underscores, or dashes are allowed.");
                Program.Exit(1);
            }

            switch (username.ToLowerInvariant())
            {
            case "ca":
            case "dhparam":
            case "server":

                Console.WriteLine($"***ERROR: USER [{username}] is reserved by neonHIVE.  Please choose another name.");
                Program.Exit(1);
                break;
            }

            var daysOption = commandLine.GetOption("--days", "365");
            int days       = 365;

            if (string.IsNullOrEmpty(daysOption) || !int.TryParse(daysOption, out days) || days <= 0)
            {
                Console.WriteLine($"***ERROR: [--days={daysOption}] is not valid.  This must be a positive integer.");
                Program.Exit(1);
            }

            var rootPrivileges = commandLine.HasOption("--root");

            RootLogin();

            Directory.CreateDirectory(caFolder);

            try
            {
                // Retrieve the VPN certificate authority ZIP archive from Vault and extract
                // its contents to a temporary folder.

                var caZipBytes = hive.Vault.Client.ReadBytesAsync("neon-secret/vpn/ca.zip.encrypted").Result;
                var vpnCaFiles = VpnCaFiles.LoadZip(caZipBytes, hiveLogin.VpnCredentials.CaZipKey);

                vpnCaFiles.Extract(caFolder);

                // Initialize the file paths.
                //
                // IMPORTANT:
                //
                // Do not change these file names because the [VpnCaFiles] class
                // depends on this naming convention.

                var indexPath     = Path.Combine(caFolder, "index.txt");
                var caSignCnfPath = Path.Combine(caFolder, "ca-sign.cnf");
                var caCnfPath     = Path.Combine(caFolder, "ca.cnf");
                var caKeyPath     = Path.Combine(caFolder, "ca.key");
                var caReqPath     = Path.Combine(caFolder, "ca.req");
                var caCrtPath     = Path.Combine(caFolder, "ca.crt");
                var dhParamPath   = Path.Combine(caFolder, "dhparam.pem");
                var serverCnfPath = Path.Combine(caFolder, "server.cnf");
                var serverKeyPath = Path.Combine(caFolder, "server.key");
                var serverReqPath = Path.Combine(caFolder, "server.req");
                var serverCrtPath = Path.Combine(caFolder, "server.crt");
                var userCnfPath   = Path.Combine(caFolder, $"{username}.cnf");
                var userReqPath   = Path.Combine(caFolder, $"{username}.req");
                var userKeyPath   = Path.Combine(caFolder, $"{username}.key");
                var userCrtPath   = Path.Combine(caFolder, $"{username}.crt");
                var taKeyPath     = Path.Combine(caFolder, "ta.key");
                var crlnumberPath = Path.Combine(caFolder, "crlnumber");
                var crlPath       = Path.Combine(caFolder, "crl.pem");

                // Build the new user client login.

                File.WriteAllText(userCnfPath, GetClientConfig(hive.Definition, username, rootPrivileges));

                Program.Execute("openssl", "req", "-new",
                                "-config", userCnfPath,
                                "-keyout", userKeyPath,
                                "-out", userReqPath);

                Program.Execute("openssl", "ca", "-batch",
                                "-config", caSignCnfPath,
                                "-days", days,
                                "-out", userCrtPath,
                                "-in", userReqPath);

                // Generate the new hive login file and also write its name
                // to [new-login.txt] so the outer shim will know what it is.

                var newLogin = hiveLogin.Clone();

                newLogin.Username = username;
                newLogin.VpnCredentials.UserCert = VpnCaFiles.NormalizePem(File.ReadAllText(userCrtPath));
                newLogin.VpnCredentials.UserKey  = File.ReadAllText(userKeyPath);

                if (!rootPrivileges)
                {
                    newLogin.ClearRootSecrets();
                }

                File.WriteAllText($"{username}@{newLogin.HiveName}.login.json", NeonHelper.JsonSerialize(newLogin, Formatting.Indented));
                File.WriteAllText("new-login.txt", $"{username}@{newLogin.HiveName}.login.json");

                // ZIP the CA files and store them to the hive Vault.

                vpnCaFiles = VpnCaFiles.LoadFolder(caFolder);

                vpnCaFiles.Clean();
                hive.Vault.Client.WriteBytesAsync("neon-secret/vpn/ca.zip.encrypted", vpnCaFiles.ToZipBytes()).Wait();
            }
            finally
            {
                Directory.Delete(caFolder, recursive: true);
                HiveHelper.CloseHive();
            }
        }
Beispiel #12
0
        /// <summary>
        /// Revokes a user certificate.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void UserRevoke(CommandLine commandLine)
        {
            DirectNotAllowed();

            var restartVpn = commandLine.HasOption("--restart-vpn");
            var thumbprint = commandLine.Arguments.FirstOrDefault();

            if (string.IsNullOrEmpty(thumbprint))
            {
                Console.Error.WriteLine("*** ERROR: THUMPRINT expected.");
                Program.Exit(1);
            }

            thumbprint = thumbprint.ToLowerInvariant();

            RootLogin();

            try
            {
                var vpnCaFiles = GetVpnCaFiles();
                var certInfo   = ListCerts(vpnCaFiles).Where(c => c.Thumbprint.ToLowerInvariant() == thumbprint).FirstOrDefault();

                if (certInfo == null)
                {
                    Console.Error.WriteLine($"*** ERROR: Certificate with thumbprint [{thumbprint}] is not known.");
                    Program.Exit(1);
                }

                if (!certInfo.IsValid)
                {
                    Console.Error.WriteLine($"*** ERROR: Certificate with thumbprint [{thumbprint}] is already revoked.");
                    Program.Exit(1);
                }

                // Initialize the file paths.
                //
                // IMPORTANT:
                //
                // Do not change these file names because the [VpnCaFiles] class
                // depends on this naming convention.

                Directory.CreateDirectory(caFolder);

                vpnCaFiles.Extract(caFolder);

                var indexPath     = Path.Combine(caFolder, "index.txt");
                var caSignCnfPath = Path.Combine(caFolder, "ca-sign.cnf");
                var caCnfPath     = Path.Combine(caFolder, "ca.cnf");
                var caKeyPath     = Path.Combine(caFolder, "ca.key");
                var caReqPath     = Path.Combine(caFolder, "ca.req");
                var caCrtPath     = Path.Combine(caFolder, "ca.crt");
                var dhParamPath   = Path.Combine(caFolder, "dhparam.pem");
                var serverCnfPath = Path.Combine(caFolder, "server.cnf");
                var serverKeyPath = Path.Combine(caFolder, "server.key");
                var serverReqPath = Path.Combine(caFolder, "server.req");
                var serverCrtPath = Path.Combine(caFolder, "server.crt");
                var taKeyPath     = Path.Combine(caFolder, "ta.key");
                var crlnumberPath = Path.Combine(caFolder, "crlnumber");
                var crlPath       = Path.Combine(caFolder, "crl.pem");

                // Mark the certificate as revoked.

                Program.Execute("openssl", "ca",
                                "-config", caSignCnfPath,
                                "-crl_reason", "unspecified",
                                "-revoke", $"{Path.Combine(caFolder, thumbprint.ToUpperInvariant())}.pem",
                                "-cert", caCrtPath,
                                "-keyfile", caKeyPath);

                // Generate the new CRL file.

                Program.Execute("openssl", "ca",
                                "-config", caSignCnfPath,
                                "-gencrl",
                                "-out", crlPath);

                // Save the CA files back to the hive Vault.

                vpnCaFiles = VpnCaFiles.LoadFolder(caFolder);

                hive.Vault.Client.WriteBytesAsync("neon-secret/vpn/ca.zip.encrypted", vpnCaFiles.ToZipBytes()).Wait();

                // Write the updated CRL to each manager.

                var crlText = vpnCaFiles.GetFile("crl.pem");

                Console.WriteLine();

                foreach (var manager in hive.Managers)
                {
                    Console.WriteLine($"*** {manager.Name}: Revoking");
                    manager.UploadText("/etc/openvpn/crl.pem", crlText);
                    manager.SudoCommand("chmod 664 /etc/openvpn/crl.pem");
                }

                // Restart OpenVPN on each manager if requested.

                if (restartVpn)
                {
                    Console.WriteLine();

                    foreach (var manager in hive.Managers)
                    {
                        Console.WriteLine($"*** {manager.Name}: Restarting OpenVPN");
                        manager.SudoCommand("systemctl restart openvpn");
                        Thread.Sleep(TimeSpan.FromSeconds(5));
                    }
                }
            }
            finally
            {
                HiveHelper.CloseHive();
            }
        }