/// <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); } }
/// <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); } }
/// <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()); } }
/// <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); } }
/// <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); } }
/// <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); } }
/// <summary> /// Logs the user out. /// </summary> /// <returns></returns> public async Task <IActionResult> OnGet() { logger.LogDebug($"Logging out."); await HttpContext.SignOutAsync(); return(Redirect("/")); }
/// <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(); } }
/// <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); }; }
/// <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; }
/// <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; }
/// <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); }
/// <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(); }
/// <inheritdoc/> public void LogDebug(object message, string activityId = null) { log.LogDebug(message, activityId); capture.AppendLine($"[DEBUG] {message}"); }
/// <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; }
/// <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(); }
/// <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); }