Пример #1
0
 /// <summary>
 /// Logs a <b>debug</b> message.
 /// </summary>
 /// <param name="message">The message.</param>
 public void Debug(string message)
 {
     if (log != null)
     {
         log.LogDebug(message, Id);
     }
 }
Пример #2
0
 /// <summary>
 /// Logs a debug exception.
 /// </summary>
 /// <param name="log">The log.</param>
 /// <param name="e">The exception.</param>
 public static void LogDebug(this INeonLogger log, Exception e)
 {
     if (log.IsDebugEnabled)
     {
         log.LogDebug(null, e);
     }
 }
Пример #3
0
 /// <summary>
 /// Logs a debug message retrieved via a message function.
 /// </summary>
 /// <param name="log">The log.</param>
 /// <param name="messageFunc">The message function.</param>
 /// <remarks>
 /// This method is intended mostly to enable the efficient use of interpolated C# strings.
 /// </remarks>
 public static void LogDebug(this INeonLogger log, Func <object> messageFunc)
 {
     if (log.IsDebugEnabled)
     {
         log.LogDebug(messageFunc());
     }
 }
Пример #4
0
 /// <summary>
 /// Logs a debug exception.
 /// </summary>
 /// <param name="log">The log.</param>
 /// <param name="e">The exception.</param>
 /// <param name="activityId">The optional activity ID.</param>
 public static void LogDebug(this INeonLogger log, Exception e, string activityId = null)
 {
     if (log.IsLogDebugEnabled)
     {
         log.LogDebug(null, e, activityId);
     }
 }
Пример #5
0
 /// <summary>
 /// Logs a <b>debug</b> message.
 /// </summary>
 /// <param name="message">The object that will be serialized into the message.</param>
 public void Debug(object message)
 {
     if (log != null)
     {
         log.LogDebug(message, Id);
     }
 }
Пример #6
0
 /// <summary>
 /// Logs a debug message retrieved via a message function.
 /// </summary>
 /// <param name="log">The log.</param>
 /// <param name="messageFunc">The message function.</param>
 /// <param name="activityId">The optional activity ID.</param>
 /// <remarks>
 /// This method is intended mostly to enable the efficient use of interpolated C# strings.
 /// </remarks>
 public static void LogDebug(this INeonLogger log, Func <string> messageFunc, string activityId = null)
 {
     if (log.IsLogDebugEnabled)
     {
         log.LogDebug(messageFunc(), activityId);
     }
 }
Пример #7
0
        /// <summary>
        /// Logs the user out.
        /// </summary>
        /// <returns></returns>
        public async Task <IActionResult> OnGet()
        {
            logger.LogDebug($"Logging out.");

            await HttpContext.SignOutAsync();

            return(Redirect("/"));
        }
Пример #8
0
        /// <summary>
        /// Add a subscription to the store.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="connection"></param>
        /// <param name="subscribeMethod"></param>
        /// <returns></returns>
        public async Task AddSubscriptionAsync(string id, HubConnectionContext connection, Func <string, HubConnectionStore, Task <IAsyncSubscription> > subscribeMethod)
        {
            await _lock.WaitAsync();

            logger?.LogDebug($"Subscribing to subject [Subject={id}].");

            try
            {
                // Avoid adding subscription if connection is closing/closed
                // We're in a lock and ConnectionAborted is triggered before OnDisconnectedAsync is called so this is guaranteed to be safe when adding while connection is closing and removing items
                if (connection.ConnectionAborted.IsCancellationRequested)
                {
                    return;
                }

                var subscription = subscriptions.GetOrAdd(id, _ => new HubConnectionStore());

                subscription.Add(connection);

                // Subscribe once
                if (subscription.Count == 1)
                {
                    var sAsync = await subscribeMethod(id, subscription);

                    sAsync.Start();
                    natsSubscriptions.GetOrAdd(id, _ => sAsync);
                }
            }
            catch (Exception e)
            {
                logger?.LogError(e);
                logger?.LogDebug($"Subscribing failed. [Subject={id}] [Connection={connection.ConnectionId}]");
            }
            finally
            {
                _lock.Release();
            }
        }
Пример #9
0
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="neonLogger">The Neon base logger.</param>
        public HiveEasyMQLogProvider(INeonLogger neonLogger)
        {
            Covenant.Requires <ArgumentNullException>(neonLogger != null);

            this.neonLogger = neonLogger;
            this.loggerFunc =
                (logLevel, messageFunc, exception, formatParameters) =>
            {
                if (messageFunc == null)
                {
                    return(true);
                }

                var message = LogMessageFormatter.FormatStructuredMessage(messageFunc(), formatParameters, out _);

                switch (logLevel)
                {
                case EasyNetQ.Logging.LogLevel.Trace:

                // NOTE: Neon logging doesn't have a TRACE level so we'll
                // map these to DEBUG.

                case EasyNetQ.Logging.LogLevel.Debug:

                    if (neonLogger.IsDebugEnabled)
                    {
                        if (exception == null)
                        {
                            neonLogger.LogDebug(message);
                        }
                        else
                        {
                            neonLogger.LogDebug(message, exception);
                        }
                    }
                    break;

                case EasyNetQ.Logging.LogLevel.Error:

                    if (neonLogger.IsErrorEnabled)
                    {
                        if (exception == null)
                        {
                            neonLogger.LogError(message);
                        }
                        else
                        {
                            neonLogger.LogError(message, exception);
                        }
                    }
                    break;

                case EasyNetQ.Logging.LogLevel.Fatal:

                    if (neonLogger.IsCriticalEnabled)
                    {
                        if (exception == null)
                        {
                            neonLogger.LogCritical(message);
                        }
                        else
                        {
                            neonLogger.LogCritical(message, exception);
                        }
                    }
                    break;

                case EasyNetQ.Logging.LogLevel.Info:

                    if (neonLogger.IsInfoEnabled)
                    {
                        if (exception == null)
                        {
                            neonLogger.LogInfo(message);
                        }
                        else
                        {
                            neonLogger.LogInfo(message, exception);
                        }
                    }
                    break;

                case EasyNetQ.Logging.LogLevel.Warn:

                    if (neonLogger.IsWarnEnabled)
                    {
                        if (exception == null)
                        {
                            neonLogger.LogWarn(message);
                        }
                        else
                        {
                            neonLogger.LogWarn(message, exception);
                        }
                    }
                    break;
                }

                return(true);
            };
        }
Пример #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;
        }
Пример #11
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;
        }
Пример #12
0
        /// <summary>
        /// Transforms the response before returning it to the client.
        ///
        /// <para>
        /// This method will add a <see cref="Cookie"/> to each response containing relevant information
        /// about the current authentication flow. It also intercepts redirects from Dex and saves any relevant
        /// tokens to a cache for reuse.
        /// </para>
        /// </summary>
        /// <param name="httpContext"></param>
        /// <param name="proxyResponse"></param>
        /// <returns></returns>
        public override async ValueTask <bool> TransformResponseAsync(HttpContext httpContext,
                                                                      HttpResponseMessage proxyResponse)
        {
            await base.TransformResponseAsync(httpContext, proxyResponse);

            Cookie cookie = null;

            if (httpContext.Request.Cookies.TryGetValue(Service.SessionCookieName, out var requestCookieBase64))
            {
                try
                {
                    logger.LogDebug($"Decrypting existing cookie.");
                    cookie = NeonHelper.JsonDeserialize <Cookie>(cipher.DecryptBytesFrom(requestCookieBase64));
                }
                catch (Exception e)
                {
                    logger.LogError(e);
                    cookie = new Cookie();
                }
            }
            else
            {
                logger.LogDebug($"Cookie not present.");
                cookie = new Cookie();
            }

            // If we're being redirected, intercept request and save token to cookie.
            if (httpContext.Response.Headers.Location.Count > 0 &&
                Uri.IsWellFormedUriString(httpContext.Response.Headers.Location.Single(), UriKind.Absolute))
            {
                var location = new Uri(httpContext.Response.Headers.Location.Single());
                var code     = HttpUtility.ParseQueryString(location.Query).Get("code");
                if (!string.IsNullOrEmpty(code))
                {
                    if (cookie != null)
                    {
                        var redirect = cookie.RedirectUri;

                        var token = await dexClient.GetTokenAsync(cookie.ClientId, code, redirect, "authorization_code");

                        await cache.SetAsync(code, cipher.EncryptToBytes(NeonHelper.JsonSerializeToBytes(token)), cacheOptions);

                        logger.LogDebug(NeonHelper.JsonSerialize(token));
                        cookie.TokenResponse = token;

                        httpContext.Response.Cookies.Append(
                            Service.SessionCookieName,
                            cipher.EncryptToBase64(NeonHelper.JsonSerialize(cookie)),
                            new CookieOptions()
                        {
                            Path     = "/",
                            Expires  = DateTime.UtcNow.AddSeconds(token.ExpiresIn.Value).AddMinutes(-60),
                            Secure   = true,
                            SameSite = SameSiteMode.Strict
                        });

                        return(true);
                    }
                }
            }

            // Add query parameters to the cookie.
            if (httpContext.Request.Query.TryGetValue("client_id", out var clientId))
            {
                logger.LogDebug($"Client ID: [{clientId}]");
                cookie.ClientId = clientId;
            }

            if (httpContext.Request.Query.TryGetValue("state", out var state))
            {
                logger.LogDebug($"State: [{state}]");
                cookie.State = state;
            }

            if (httpContext.Request.Query.TryGetValue("redirect_uri", out var redirectUri))
            {
                logger.LogDebug($"Redirect Uri: [{redirectUri}]");
                cookie.RedirectUri = redirectUri;
            }

            if (httpContext.Request.Query.TryGetValue("scope", out var scope))
            {
                logger.LogDebug($"Scope: [{scope}]");
                cookie.Scope = scope;
            }

            if (httpContext.Request.Query.TryGetValue("response_type", out var responseType))
            {
                logger.LogDebug($"Response Type: [{responseType}]");
                cookie.ResponseType = responseType;
            }

            httpContext.Response.Cookies.Append(
                Service.SessionCookieName,
                cipher.EncryptToBase64(NeonHelper.JsonSerialize(cookie)),
                new CookieOptions()
            {
                Path     = "/",
                Expires  = DateTime.UtcNow.AddHours(24),
                Secure   = true,
                SameSite = SameSiteMode.Strict
            });

            return(true);
        }
Пример #13
0
        /// <summary>
        /// Implements the service as a <see cref="Task"/>.
        /// </summary>
        /// <returns>The <see cref="Task"/>.</returns>
        private static async Task RunAsync()
        {
            var localMD5    = string.Empty;
            var remoteMD5   = "[unknown]";
            var verifyTimer = new PolledTimer(verifyInterval, autoReset: true);

            var periodicTask =
                new AsyncPeriodicTask(
                    pollInterval,
                    onTaskAsync:
                    async() =>
            {
                log.LogDebug(() => "Starting poll");
                log.LogDebug(() => "Fetching DNS answers MD5 from Consul.");

                remoteMD5 = await consul.KV.GetStringOrDefault(HiveConst.ConsulDnsHostsMd5Key, terminator.CancellationToken);

                if (remoteMD5 == null)
                {
                    remoteMD5 = "[unknown]";
                }

                var verify = verifyTimer.HasFired;

                if (verify)
                {
                    // Under normal circumstances, we should never see the reload signal file
                    // here because the [neon-dns-loader] service should have deleted it after
                    // handling the last change signal.
                    //
                    // This probably means that [neon-dns-loader] is not running or if this service
                    // is configured with POLL_INTERVAL being so short that [neon-dns-loader]
                    // hasn't had a chance to handle the previous signal.

                    if (File.Exists(reloadSignalPath))
                    {
                        log.LogWarn("[neon-dns-loader] service doesn't appear to be running because the reload signal file is present.");
                    }
                }

                if (!verify && localMD5 == remoteMD5)
                {
                    log.LogDebug(() => "DNS answers are unchanged.");
                }
                else
                {
                    if (localMD5 == remoteMD5)
                    {
                        log.LogDebug(() => "DNS answers have not changed but we're going to verify that we have the correct hosts anyway.");
                    }
                    else
                    {
                        log.LogDebug(() => "DNS answers have changed.");
                    }

                    log.LogDebug(() => "Fetching DNS answers.");

                    var hostsTxt = await consul.KV.GetStringOrDefault(HiveConst.ConsulDnsHostsKey, terminator.CancellationToken);

                    if (hostsTxt == null)
                    {
                        log.LogWarn(() => "DNS answers do not exist on Consul.  Is [neon-dns-mon] functioning properly?");
                    }
                    else
                    {
                        var marker = "# -------- NEON-DNS --------";

                        // We have the host entries from Consul.  We need to add these onto the
                        // end [/etc/powserdns/hosts], replacing any host entries written during
                        // a previous run.
                        //
                        // We're going to use the special marker line:
                        //
                        //  # ---DYNAMIC-HOSTS---
                        //
                        // to separate the built-in hosts (above the line) from the dynamic hosts
                        // we're generating here (which will be below the line).  Note that this
                        // line won't exist the first time this service runs, so we'll just add it.
                        //
                        // Note that it's possible that the PowerDNS Recursor might be reading this
                        // file while we're trying to write it.  We're going to treat these as a
                        // transient errors and retry.

                        var retry = new LinearRetryPolicy(typeof(IOException), maxAttempts: 5, retryInterval: TimeSpan.FromSeconds(1));

                        await retry.InvokeAsync(
                            async() =>
                        {
                            using (var stream = new FileStream(powerDnsHostsPath, FileMode.Open, FileAccess.ReadWrite))
                            {
                                // Read a copy of the hosts file as bytes so we can compare
                                // the old version with the new one generated below for changes.

                                var orgHostBytes = stream.ReadToEnd();

                                stream.Position = 0;

                                // Generate the new hosts file.

                                var sbHosts = new StringBuilder();

                                // Read the hosts file up to but not including the special marker
                                // line (if it's present).

                                using (var reader = new StreamReader(stream, Encoding.UTF8, true, 32 * 1024, leaveOpen: true))
                                {
                                    foreach (var line in reader.Lines())
                                    {
                                        if (line.StartsWith(marker))
                                        {
                                            break;
                                        }

                                        sbHosts.AppendLine(line);
                                    }
                                }

                                // Strip any trailing whitespace from the hosts file so we'll
                                // be able to leave a nice blank line between the end of the
                                // original file and the special marker line.

                                var text = sbHosts.ToString().TrimEnd();

                                sbHosts.Clear();
                                sbHosts.AppendLine(text);

                                // Append the marker line, followed by dynamic host
                                // entries we downloaded from Consul.

                                sbHosts.AppendLine();
                                sbHosts.AppendLine(marker);
                                sbHosts.AppendLine();
                                sbHosts.Append(hostsTxt);

                                // Generate the new host file bytes, taking care to ensure that
                                // we're using Linux style line endings and then update the
                                // hosts file if anything changed.

                                var hostsText    = NeonHelper.ToLinuxLineEndings(sbHosts.ToString());
                                var newHostBytes = Encoding.UTF8.GetBytes(hostsText);

                                if (NeonHelper.ArrayEquals(orgHostBytes, newHostBytes))
                                {
                                    log.LogDebug(() => $"[{powerDnsHostsPath}] file is up-to-date.");
                                }
                                else
                                {
                                    log.LogDebug(() => $"[{powerDnsHostsPath}] is being updated.");

                                    stream.Position = 0;
                                    stream.SetLength(0);
                                    stream.Write(newHostBytes);

                                    // Signal to the local [neon-dns-loader] systemd service that it needs
                                    // to have PowerDNS Recursor reload the hosts file.

                                    File.WriteAllText(reloadSignalPath, "reload now");
                                }
                            }

                            log.LogDebug(() => "Finished poll");
                            await Task.CompletedTask;
                        });

                        // We've successfully synchronized the local hosts file with
                        // the Consul DNS settings.

                        localMD5 = remoteMD5;
                    }
                }

                return(await Task.FromResult(false));
            },
                    onExceptionAsync:
                    async e =>
            {
                log.LogError(e);
                return(await Task.FromResult(false));
            },
                    onTerminateAsync:
                    async() =>
            {
                log.LogInfo(() => "Terminating");
                await Task.CompletedTask;
            });

            terminator.AddDisposable(periodicTask);
            await periodicTask.Run();
        }
Пример #14
0
 /// <inheritdoc/>
 public void LogDebug(object message, string activityId = null)
 {
     log.LogDebug(message, activityId);
     capture.AppendLine($"[DEBUG] {message}");
 }
Пример #15
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;
        }
Пример #16
0
        /// <summary>
        /// Implements the service as a <see cref="Task"/>.
        /// </summary>
        /// <returns>The <see cref="Task"/>.</returns>
        private static async Task RunAsync()
        {
            var periodicTask =
                new AsyncPeriodicTask(
                    pollInterval,
                    onTaskAsync:
                    async() =>
            {
                log.LogDebug(() => "Starting poll");

                // We're going to collect the [hostname --> address] mappings into
                // a specialized (semi-threadsafe) dictionary.

                var hostAddresses = new HostAddresses();

                // Retrieve the current hive definition from Consul if we don't already
                // have it or it's different from what we've cached.

                hiveDefinition = await HiveHelper.GetDefinitionAsync(hiveDefinition, terminator.CancellationToken);

                log.LogDebug(() => $"Hive has [{hiveDefinition.NodeDefinitions.Count}] nodes.");

                // Add the [NAME.HIVENAME.nhive.io] definitions for each cluster node.

                foreach (var node in hiveDefinition.Nodes)
                {
                    hostAddresses.Add($"{node.Name}.{hiveDefinition.Name}.nhive.io", IPAddress.Parse(node.PrivateAddress));
                }

                // Read the DNS entry definitions from Consul and add the appropriate
                // host/addresses based on health checks, etc.

                var targetsResult = (await consul.KV.ListOrDefault <DnsEntry>(HiveConst.ConsulDnsEntriesKey + "/", terminator.CancellationToken));

                List <DnsEntry> targets;

                if (targetsResult == null)
                {
                    // The targets key wasn't found in Consul, so we're
                    // going to assume that there are no targets.

                    targets = new List <DnsEntry>();
                }
                else
                {
                    targets = targetsResult.ToList();
                }

                log.LogDebug(() => $"Consul has [{targets.Count()}] DNS targets.");

                await ResolveTargetsAsync(hostAddresses, targets);

                // Generate a canonical [hosts.txt] file by sorting host entries by
                // hostname and then by IP address.
                //
                // Unhealthy hosts will be assigned the unrouteable [0.0.0.0] address.
                // The reason for this is subtle but super important.
                //
                // If we didn't do this, the DNS host would likely be resolved by a
                // public DNS service, perhaps returning the IP address of a production
                // endpoint.
                //
                // This could cause a disaster if the whole purpose of having a local
                // DNS host defined to redirect test traffic to a test service.  If
                // the test service endpoints didn't report as healthy and [0.0.0.0]
                // wasn't set, then test traffic could potentially hit the production
                // endpoint and do serious damage.

                var sbHosts      = new StringBuilder();
                var mappingCount = 0;

                foreach (var host in hostAddresses.OrderBy(h => h.Key))
                {
                    foreach (var address in host.Value.OrderBy(a => a.ToString()))
                    {
                        sbHosts.AppendLineLinux($"{address,-15} {host.Key}");
                        mappingCount++;
                    }
                }

                var unhealthyTargets = targets.Where(t => !hostAddresses.ContainsKey(t.Hostname) || hostAddresses[t.Hostname].Count == 0).ToList();

                if (unhealthyTargets.Count > 0)
                {
                    sbHosts.AppendLine();
                    sbHosts.AppendLine($"# [{unhealthyTargets.Count}] unhealthy DNS hosts:");
                    sbHosts.AppendLine();

                    var unhealthyAddress = "0.0.0.0";

                    foreach (var target in unhealthyTargets.OrderBy(h => h))
                    {
                        sbHosts.AppendLineLinux($"{unhealthyAddress,-15} {target.Hostname}");
                    }
                }

                // Compute the MD5 hash and compare it to the hash persisted to
                // Consul (if any) to determine whether we need to update the
                // answers in Consul.

                var hostsTxt   = sbHosts.ToString();
                var hostsMD5   = NeonHelper.ComputeMD5(hostsTxt);
                var currentMD5 = await consul.KV.GetStringOrDefault(HiveConst.ConsulDnsHostsMd5Key, terminator.CancellationToken);

                if (currentMD5 == null)
                {
                    currentMD5 = string.Empty;
                }

                if (hostsMD5 != currentMD5)
                {
                    log.LogDebug(() => $"DNS answers have changed.");
                    log.LogDebug(() => $"Writing [{mappingCount}] DNS answers to Consul.");

                    // Update the Consul keys using a transaction.

                    var operations = new List <KVTxnOp>()
                    {
                        new KVTxnOp(HiveConst.ConsulDnsHostsMd5Key, KVTxnVerb.Set)
                        {
                            Value = Encoding.UTF8.GetBytes(hostsMD5)
                        },
                        new KVTxnOp(HiveConst.ConsulDnsHostsKey, KVTxnVerb.Set)
                        {
                            Value = Encoding.UTF8.GetBytes(hostsTxt)
                        }
                    };

                    await consul.KV.Txn(operations, terminator.CancellationToken);
                }

                log.LogDebug(() => "Finished poll");
                return(await Task.FromResult(false));
            },
                    onExceptionAsync:
                    async e =>
            {
                log.LogError(e);
                return(await Task.FromResult(false));
            },
                    onTerminateAsync:
                    async() =>
            {
                log.LogInfo(() => "Terminating");
                await Task.CompletedTask;
            });

            terminator.AddDisposable(periodicTask);
            await periodicTask.Run();
        }
Пример #17
0
        /// <summary>
        /// <para>
        /// Entrypoint called as part of the request pipeline.
        /// </para>
        /// <para>
        /// This method is responsible for intercepting token requests from clients.
        /// If the client has a valid cookie with a token response in it, we save the
        /// token to cache and redirect them back with a code referencing the token in
        /// the cache.
        /// </para>
        /// </summary>
        public async Task InvokeAsync(
            HttpContext context,
            Service NeonSsoSessionProxyService,
            IDistributedCache cache,
            AesCipher cipher,
            DistributedCacheEntryOptions cacheOptions,
            INeonLogger logger)
        {
            try
            {
                if (context.Request.Cookies.TryGetValue(Service.SessionCookieName, out var requestCookieBase64))
                {
                    var requestCookie = NeonHelper.JsonDeserialize <Cookie>(cipher.DecryptBytesFrom(requestCookieBase64));

                    if (requestCookie.TokenResponse != null)
                    {
                        var code = NeonHelper.GetCryptoRandomPassword(10);
                        await cache.SetAsync(code, cipher.EncryptToBytes(NeonHelper.JsonSerializeToBytes(requestCookie.TokenResponse)), cacheOptions);

                        var query = new Dictionary <string, string>()
                        {
                            { "code", code }
                        };

                        if (context.Request.Query.TryGetValue("state", out var state))
                        {
                            query["state"] = state;
                        }

                        if (context.Request.Query.TryGetValue("redirect_uri", out var redirectUri))
                        {
                            if (context.Request.Query.TryGetValue("client_id", out var clientId))
                            {
                                if (!NeonSsoSessionProxyService.Config.StaticClients.Where(client => client.Id == clientId).First().RedirectUris.Contains(redirectUri))
                                {
                                    logger.LogError("Invalid redirect URI");

                                    throw new HttpRequestException("Invalid redirect URI.");
                                }
                                context.Response.StatusCode       = StatusCodes.Status302Found;
                                context.Response.Headers.Location = QueryHelpers.AddQueryString(redirectUri, query);
                                logger.LogDebug($"Client and Redirect URI confirmed. [ClientID={clientId}] [RedirectUri={redirectUri}]");

                                return;
                            }
                            else
                            {
                                logger.LogError("No Client ID specified.");

                                throw new HttpRequestException("Invalid Client ID.");
                            }
                        }
                        else
                        {
                            throw new HttpRequestException("No redirect_uri specified.");
                        }
                    }
                }
            }
            catch (Exception e)
            {
                NeonSsoSessionProxyService.Log.LogError(e);
            }

            await _next(context);
        }