Beispiel #1
0
        /// <summary>
        /// Removes a dashboard.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void Remove(CommandLine commandLine)
        {
            var name = commandLine.Arguments.ElementAtOrDefault(1);

            if (string.IsNullOrEmpty(name))
            {
                Console.Error.WriteLine("*** ERROR: Expected a NAME argument.");
                Program.Exit(1);
            }

            if (!HiveDefinition.IsValidName(name) || reserved.Contains(name))
            {
                Console.Error.WriteLine($"*** ERROR: [{name}] is not a valid dashboard name.");
                Program.Exit(1);
            }

            name = name.ToLowerInvariant();

            var existingDashboard = hive.Dashboard.Get(name);

            if (existingDashboard == null)
            {
                Console.Error.WriteLine($"*** ERROR: Dashboard [{name}] does not exist.");
                Program.Exit(1);
            }

            hive.Dashboard.Remove(name);
            Console.WriteLine($"Removed [{name}] dashboard.");
        }
Beispiel #2
0
        /// <summary>
        /// Sets a dashboard.
        /// </summary>
        /// <param name="commandLine">The command line.</param>
        private void Set(CommandLine commandLine)
        {
            var name = commandLine.Arguments.ElementAtOrDefault(1);
            var url  = commandLine.Arguments.ElementAtOrDefault(2);

            if (string.IsNullOrEmpty(name))
            {
                Console.Error.WriteLine("*** ERROR: Expected a NAME argument.");
                Program.Exit(1);
            }

            if (!HiveDefinition.IsValidName(name) || reserved.Contains(name))
            {
                Console.Error.WriteLine($"*** ERROR: [{name}] is not a valid dashboard name.");
                Program.Exit(1);
            }

            name = name.ToLowerInvariant();

            if (string.IsNullOrEmpty(url))
            {
                Console.Error.WriteLine("*** ERROR: Expected a URL argument.");
                Program.Exit(1);
            }

            var title       = commandLine.GetOption("--title");
            var folder      = commandLine.GetOption("--folder");
            var description = commandLine.GetOption("--description");

            var dashboard = new HiveDashboard()
            {
                Name        = name,
                Title       = title,
                Folder      = folder,
                Url         = url,
                Description = description
            };

            var errors = dashboard.Validate(hive.Definition);

            if (errors.Count > 0)
            {
                foreach (var error in errors)
                {
                    Console.Error.WriteLine($"*** ERROR: {error}");
                }

                Program.Exit(1);
            }

            hive.Dashboard.Set(dashboard);

            Console.WriteLine();
            Console.WriteLine($"Saved [{name}] dashboard.");
        }
Beispiel #3
0
        /// <inheritdoc/>
        public void Run(ModuleContext context)
        {
            var hive = HiveHelper.Hive;

            if (!context.ValidateArguments(context.Arguments, validModuleArgs))
            {
                context.Failed = true;
                return;
            }

            // Obtain common arguments.

            if (!context.Arguments.TryGetValue <string>("name", out var name))
            {
                throw new ArgumentException($"[name] module argument is required.");
            }

            if (!HiveDefinition.IsValidName(name))
            {
                throw new ArgumentException($"[name={name}] is not a valid certificate name.");
            }

            if (!context.Arguments.TryGetValue <string>("state", out var state))
            {
                state = "present";
            }

            state = state.ToLowerInvariant();

            if (!context.Arguments.TryGetValue <bool>("force", out var force))
            {
                force = false;
            }

            if (context.HasErrors)
            {
                return;
            }

            // We have the required arguments, so perform the operation.

            if (!context.Login.HasVaultRootCredentials)
            {
                throw new ArgumentException("Access Denied: Root Vault credentials are required.");
            }

            switch (state)
            {
            case "absent":

                context.WriteLine(AnsibleVerbosity.Trace, $"Vault: checking for [{name}] certificate");

                if (hive.Certificate.Get(name) != null)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Vault: [{name}] certificate exists");

                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Certificate [{name}] will be removed when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Removing [{name}] certyificate.");
                        hive.Certificate.Remove(name);
                        context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate removed");
                    }

                    context.Changed = !context.CheckMode;
                }
                else
                {
                    context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate does not exist");
                }
                break;

            case "present":

                if (!context.Arguments.TryGetValue <string>("value", out var value))
                {
                    throw new ArgumentException($"[value] module argument is required.");
                }

                var certificate = TlsCertificate.Parse(value);        // This validates the certificate/private key

                context.WriteLine(AnsibleVerbosity.Trace, $"Reading [{name}] certificate");

                var existingCert = hive.Certificate.Get(name);
                var changed      = false;

                if (existingCert == null)
                {
                    context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate does not exist");
                    context.Changed = !context.CheckMode;

                    changed = true;
                }
                else if (!NeonHelper.JsonEquals(existingCert, certificate) || force)
                {
                    context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate does exists but is different");
                    context.Changed = !context.CheckMode;

                    changed = true;
                }
                else
                {
                    context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate is unchanged");
                }

                if (changed)
                {
                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Certificate [{name}] will be updated when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Saving [{name}] certificate");
                        hive.Certificate.Set(name, certificate);
                        context.WriteLine(AnsibleVerbosity.Info, $"[{name}] certificate saved");
                    }
                }

                break;

            default:

                throw new ArgumentException($"[state={state}] is not one of the valid choices: [present] or [absent].");
            }
        }
Beispiel #4
0
        /// <inheritdoc/>
        public void Run(ModuleContext context)
        {
            TrafficManager trafficManager = null;
            bool           isPublic       = false;
            string         name           = null;
            string         ruleName       = null;
            bool           deferUpdate    = false;

            if (!context.ValidateArguments(context.Arguments, validModuleArgs))
            {
                context.Failed = true;
                return;
            }

            // Obtain common arguments.

            context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [state]");

            if (!context.Arguments.TryGetValue <string>("state", out var state))
            {
                state = "present";
            }

            state = state.ToLowerInvariant();

            if (context.HasErrors)
            {
                return;
            }

            context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [name]");

            if (!context.Arguments.TryGetValue <string>("name", out name))
            {
                throw new ArgumentException($"[name] module argument is required.");
            }

            switch (name)
            {
            case "private":

                trafficManager = HiveHelper.Hive.PrivateTraffic;
                isPublic       = false;
                break;

            case "public":

                trafficManager = HiveHelper.Hive.PublicTraffic;
                isPublic       = true;
                break;

            default:

                throw new ArgumentException($"[name={name}] is not a one of the valid traffic manager names: [private] or [public].");
            }

            if (state == "present" || state == "absent")
            {
                context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [rule_name]");

                if (!context.Arguments.TryGetValue <string>("rule_name", out ruleName))
                {
                    throw new ArgumentException($"[rule_name] module argument is required.");
                }

                if (!HiveDefinition.IsValidName(ruleName))
                {
                    throw new ArgumentException($"[rule_name={ruleName}] is not a valid traffic manager rule name.");
                }

                context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [defer_update]");

                if (!context.Arguments.TryGetValue <bool>("defer_update", out deferUpdate))
                {
                    deferUpdate = false;
                }
            }

            // We have the required arguments, so perform the operation.

            switch (state)
            {
            case "absent":

                context.WriteLine(AnsibleVerbosity.Trace, $"Check if rule [{ruleName}] exists.");

                if (trafficManager.GetRule(ruleName) != null)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] does exist.");
                    context.WriteLine(AnsibleVerbosity.Info, $"Deleting rule [{ruleName}].");

                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Rule [{ruleName}] will be deleted when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        trafficManager.RemoveRule(ruleName, deferUpdate: deferUpdate);
                        context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] deleted.");
                        context.Changed = true;
                    }
                }
                else
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Rule [{ruleName}] does not exist.");
                }
                break;

            case "present":

                context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [rule]");

                if (!context.Arguments.TryGetValue <JObject>("rule", out var routeObject))
                {
                    throw new ArgumentException($"[rule] module argument is required when [state={state}].");
                }

                var ruleText = routeObject.ToString();

                context.WriteLine(AnsibleVerbosity.Trace, "Parsing rule");

                var newRule = TrafficRule.Parse(ruleText, strict: true);

                context.WriteLine(AnsibleVerbosity.Trace, "Rule parsed successfully");

                // Use the name argument if the deserialized rule doesn't
                // have a name.  This will make it easier on operators because
                // they won't need to specify the name twice.

                if (string.IsNullOrWhiteSpace(newRule.Name))
                {
                    newRule.Name = ruleName;
                }

                // Ensure that the name passed as an argument and the
                // name within the rule definition match.

                if (!string.Equals(ruleName, newRule.Name, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new ArgumentException($"The [rule_name={ruleName}] argument and the rule's [{nameof(TrafficRule.Name)}={newRule.Name}] property are not the same.");
                }

                context.WriteLine(AnsibleVerbosity.Trace, "Rule name matched.");

                // Validate the rule.

                context.WriteLine(AnsibleVerbosity.Trace, "Validating rule.");

                var proxySettings     = trafficManager.GetSettings();
                var validationContext = new TrafficValidationContext(name, proxySettings);

                // $hack(jeff.lill):
                //
                // This ensures that [proxySettings.Resolvers] is initialized with
                // the built-in Docker DNS resolver.

                proxySettings.Validate(validationContext);

                // Load the TLS certificates into the validation context so we'll
                // be able to verify that any referenced certificates mactually exist.

                // $todo(jeff.lill):
                //
                // This code assumes that the operator is currently logged in with
                // root Vault privileges.  We'll have to do something else for
                // non-root logins.
                //
                // One idea might be to save two versions of the certificates.
                // The primary certificate with private key in Vault and then
                // just the public certificate in Consul and then load just
                // the public ones here.
                //
                // A good time to make this change might be when we convert to
                // use the .NET X.509 certificate implementation.

                if (!context.Login.HasVaultRootCredentials)
                {
                    throw new ArgumentException("Access Denied: Root Vault credentials are required.");
                }

                context.WriteLine(AnsibleVerbosity.Trace, "Reading hive certificates.");

                using (var vault = HiveHelper.OpenVault(Program.HiveLogin.VaultCredentials.RootToken))
                {
                    // List the certificate key/names and then fetch each one
                    // to capture details like the expiration date and covered
                    // hostnames.

                    foreach (var certName in vault.ListAsync("neon-secret/cert").Result)
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Reading: {certName}");

                        var certificate = vault.ReadJsonAsync <TlsCertificate>(HiveHelper.GetVaultCertificateKey(certName)).Result;

                        validationContext.Certificates.Add(certName, certificate);
                    }
                }

                context.WriteLine(AnsibleVerbosity.Trace, $"[{validationContext.Certificates.Count}] hive certificates downloaded.");

                // Actually perform the rule validation.

                newRule.Validate(validationContext);

                if (validationContext.HasErrors)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"[{validationContext.Errors.Count}] Route validation errors.");

                    foreach (var error in validationContext.Errors)
                    {
                        context.WriteLine(AnsibleVerbosity.Important, error);
                        context.WriteErrorLine(error);
                    }

                    context.Failed = true;
                    return;
                }

                context.WriteLine(AnsibleVerbosity.Trace, "Rule is valid.");

                // Try reading any existing rule with this name and then determine
                // whether the two versions of the rule are actually different.

                context.WriteLine(AnsibleVerbosity.Trace, $"Looking for existing rule [{ruleName}]");

                var existingRule = trafficManager.GetRule(ruleName);
                var changed      = false;

                if (existingRule != null)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Rule exists: checking for differences.");

                    // Normalize the new and existing rules so the JSON text comparision
                    // will work properly.

                    newRule.Normalize(isPublic);
                    existingRule.Normalize(isPublic);

                    changed = !NeonHelper.JsonEquals(newRule, existingRule);

                    if (changed)
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Rules are different.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Rules are the same.  No need to update.");
                    }
                }
                else
                {
                    changed = true;
                    context.WriteLine(AnsibleVerbosity.Trace, $"Rule [name={ruleName}] does not exist.");
                }

                if (changed)
                {
                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Rule [{ruleName}] will be updated when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Writing rule [{ruleName}].");
                        trafficManager.SetRule(newRule);
                        context.WriteLine(AnsibleVerbosity.Info, $"Rule updated.");
                        context.Changed = !context.CheckMode;
                    }
                }
                break;

            case "update":

                trafficManager.Update();
                context.Changed = true;
                context.WriteLine(AnsibleVerbosity.Info, $"Update signalled.");
                break;

            case "purge":

                var purgeItems         = context.ParseStringArray("purge_list");
                var purgeCaseSensitive = context.ParseBool("purge_case_sensitive");

                if (!purgeCaseSensitive.HasValue)
                {
                    purgeCaseSensitive = false;
                }

                if (purgeItems.Count == 0)
                {
                    context.WriteLine(AnsibleVerbosity.Important, $"[purge_list] is missing or empty.");
                    break;
                }

                trafficManager.Purge(purgeItems.ToArray(), caseSensitive: purgeCaseSensitive.Value);

                context.Changed = true;
                context.WriteLine(AnsibleVerbosity.Info, $"Purge request submitted.");
                break;

            default:

                throw new ArgumentException($"[state={state}] is not one of the valid choices: [present], [absent], or [update].");
            }
        }
Beispiel #5
0
        /// <summary>
        /// Implements the built-in <b>neon_dashboard</b> module.
        /// </summary>
        /// <param name="context">The module context.</param>
        public void Run(ModuleContext context)
        {
            var hive   = HiveHelper.Hive;
            var consul = HiveHelper.Consul;

            if (!context.ValidateArguments(context.Arguments, validModuleArgs))
            {
                context.Failed = true;
                return;
            }

            // Obtain common arguments.

            context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [name]");

            if (!context.Arguments.TryGetValue <string>("name", out var name))
            {
                throw new ArgumentException($"[name] module argument is required.");
            }

            if (!HiveDefinition.IsValidName(name))
            {
                throw new ArgumentException($"[{name}] is not a valid dashboard name.");
            }

            context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [state]");

            if (!context.Arguments.TryGetValue <string>("state", out var state))
            {
                state = "present";
            }

            state = state.ToLowerInvariant();

            if (context.HasErrors)
            {
                return;
            }

            // We have the required arguments, so perform the operation.

            switch (state)
            {
            case "absent":

                context.WriteLine(AnsibleVerbosity.Trace, $"Check if dashboard [{name}] exists.");

                if (hive.Dashboard.Get(name) != null)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Dashboard [{name}] already exists.");

                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Dashboard [{name}] will be deleted when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Deleting dashboard [{name}].");
                        hive.Dashboard.Remove(name);
                        context.WriteLine(AnsibleVerbosity.Trace, $"Dashboard [{name}] deleted.");
                        context.Changed = true;
                    }
                }
                else
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Dashboard [{name}] does not exist.");
                }
                break;

            case "present":

                // Parse the PRESENT arguments.

                context.WriteLine(AnsibleVerbosity.Trace, $"Parsing [url]");

                if (!context.Arguments.TryGetValue <string>("url", out var url) && state == "present")
                {
                    throw new ArgumentException($"[url] module argument is required when [state={state}].");
                }

                if (!Uri.TryCreate(url, UriKind.Absolute, out var urlParsed))
                {
                    throw new ArgumentException($"[url={url}] is not valid.");
                }

                url = urlParsed.ToString();

                context.Arguments.TryGetValue <string>("title", out var title);
                context.Arguments.TryGetValue <string>("folder", out var folder);
                context.Arguments.TryGetValue <string>("description", out var description);

                if (context.HasErrors)
                {
                    return;
                }

                // Build the dashboard definition from the arguments.

                var newDashboard = new HiveDashboard()
                {
                    Name        = name,
                    Title       = title,
                    Folder      = folder,
                    Url         = url,
                    Description = description
                };

                // Validate the dashboard.

                context.WriteLine(AnsibleVerbosity.Trace, "Validating dashboard.");

                var errors = newDashboard.Validate(hive.Definition);

                if (errors.Count > 0)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"[{errors.Count}] dashboard validation errors.");

                    foreach (var error in errors)
                    {
                        context.WriteLine(AnsibleVerbosity.Important, error);
                        context.WriteErrorLine(error);
                    }

                    context.Failed = true;
                    return;
                }

                context.WriteLine(AnsibleVerbosity.Trace, "Dashboard is valid.");

                // Try reading any existing dashboard with this name and then determine
                // whether the two versions are actually different.

                context.WriteLine(AnsibleVerbosity.Trace, $"Looking for existing dashboard [{name}]");

                var existingDashboard = hive.Dashboard.Get(name);
                var changed           = false;

                if (existingDashboard != null)
                {
                    context.WriteLine(AnsibleVerbosity.Trace, $"Dashboard exists: checking for differences.");

                    changed = !NeonHelper.JsonEquals(newDashboard, existingDashboard);

                    if (changed)
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Dashboards are different.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Dashboards are the same.  No need to update.");
                    }
                }
                else
                {
                    changed = true;
                    context.WriteLine(AnsibleVerbosity.Trace, $"Dashboard for [{name}] does not exist.");
                }

                if (changed)
                {
                    if (context.CheckMode)
                    {
                        context.WriteLine(AnsibleVerbosity.Info, $"Dashboard [{name}] will be updated when CHECK-MODE is disabled.");
                    }
                    else
                    {
                        context.WriteLine(AnsibleVerbosity.Trace, $"Updating dashboard.");
                        hive.Dashboard.Set(newDashboard);
                        context.WriteLine(AnsibleVerbosity.Info, $"Dashboard updated.");

                        context.Changed = true;
                    }

                    context.CheckMode = !context.CheckMode;
                }

                break;

            default:

                throw new ArgumentException($"[state={state}] is not one of the valid choices: [present] or [absent].");
            }
        }
Beispiel #6
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption)
            {
                Console.WriteLine(usage);
                Program.Exit(0);
            }

            // Process the command arguments.

            TlsCertificate certificate;
            string         certName;

            var command = commandLine.Arguments.FirstOrDefault();

            if (command == null)
            {
                Console.WriteLine(usage);
                Program.Exit(1);
            }

            commandLine = commandLine.Shift(1);

            switch (command)
            {
            case "get":

                Program.ConnectHive();

                certName = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(certName))
                {
                    Console.Error.WriteLine("*** ERROR: Expected arguments: NAME");
                    Program.Exit(1);
                }

                if (!HiveDefinition.IsValidName(certName))
                {
                    Console.Error.WriteLine($"*** ERROR: [{certName}] is not a valid certificate name.");
                    Program.Exit(1);
                }

                certificate = HiveHelper.Hive.Certificate.Get(certName);

                if (certificate == null)
                {
                    Console.Error.WriteLine($"*** ERROR: Certificate [{certName}] does not exist.");
                    Program.Exit(1);
                }

                Console.WriteLine(certificate.CombinedPem);
                break;

            case "join":

                if (commandLine.Arguments.Length != 3)
                {
                    Console.Error.WriteLine("*** ERROR: Expected arguments: PATH-CERT PATH-KEY PATH-OUTPUT");
                    Program.Exit(1);
                }

                certificate = TlsCertificate.Load(commandLine.Arguments[0], commandLine.Arguments[1]);

                File.WriteAllText(commandLine.Arguments[2], certificate.CombinedPem);
                break;

            case "list":
            case "ls":

                Program.ConnectHive();

                var certList = new List <CertInfo>();

                DateTime?checkDate = null;
                bool     expired   = false;

                if (commandLine.GetOption("--expired") != null)
                {
                    checkDate = DateTime.UtcNow;
                    expired   = true;
                }
                else if (commandLine.GetOption("--expiring") != null)
                {
                    checkDate = DateTime.UtcNow + TimeSpan.FromDays(30);
                }

                // List the certificate key/names and then fetch each one
                // to capture details like the expiration date and covered
                // hostnames.

                foreach (var name in HiveHelper.Hive.Certificate.List())
                {
                    certificate = HiveHelper.Hive.Certificate.Get(name);

                    if (checkDate.HasValue && certificate.IsValidDate(checkDate))
                    {
                        continue;
                    }

                    certList.Add(new CertInfo(name, certificate));
                }

                if (checkDate.HasValue && certList.Count == 0)
                {
                    Console.WriteLine(expired ? "* No certificates have expired." : "* No certificates are expiring within 30 days.");
                    Program.Exit(0);
                }

                if (certList.Count > 0)
                {
                    var nameHeader       = "Name";
                    var validUntilHeader = "Valid Until";
                    var hostsHeader      = "Hosts";
                    var nameColumnWidth  = Math.Max(nameHeader.Length, certList.Max(ci => ci.Name.Length));
                    var dateColumnWidth  = Math.Max(validUntilHeader.Length, certList.Max(ci => ci.ValidUntil.Length));
                    var hostColumnWidth  = Math.Max(hostsHeader.Length, certList.Max(ci => ci.Hosts.Length));

                    Console.WriteLine($"{nameHeader}{new string(' ', nameColumnWidth - "Name".Length)}   {validUntilHeader}{new string(' ', dateColumnWidth - validUntilHeader.Length)}   {hostsHeader}");
                    Console.WriteLine($"{new string('-', nameColumnWidth)}   {new string('-', dateColumnWidth)}   {new string('-', hostColumnWidth)}");

                    foreach (var certInfo in certList.OrderBy(ci => ci.Name.ToLowerInvariant()))
                    {
                        Console.WriteLine($"{certInfo.Name}{new string(' ', nameColumnWidth - certInfo.Name.Length)}   {certInfo.ValidUntil}{new string(' ', dateColumnWidth - certInfo.ValidUntil.Length)}   {certInfo.Hosts}");
                    }

                    if (checkDate.HasValue)
                    {
                        Program.Exit(1);
                    }
                }
                else
                {
                    Console.WriteLine("* No certificates");
                }
                break;

            case "remove":
            case "rm":

                Program.ConnectHive();

                certName = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(certName))
                {
                    Console.Error.WriteLine("*** ERROR: Expected arguments: NAME");
                    Program.Exit(1);
                }

                if (!HiveDefinition.IsValidName(certName))
                {
                    Console.Error.WriteLine($"*** ERROR: [{certName}] is not a valid certificate name.");
                    Program.Exit(1);
                }

                if (HiveHelper.Hive.Certificate.Get(certName) != null)
                {
                    HiveHelper.Hive.Certificate.Remove(certName);
                    Console.WriteLine($"Certificate [{certName}] was removed.");
                }
                else
                {
                    Console.Error.WriteLine($"*** ERROR: [{certName}] does not exist.");
                    Program.Exit(1);
                }
                break;

            case "set":

                Program.ConnectHive();

                using (var vault = HiveHelper.OpenVault(Program.HiveLogin.VaultCredentials.RootToken))
                {
                    if (commandLine.Arguments.Length != 2)
                    {
                        Console.Error.WriteLine("*** ERROR: Expected arguments: NAME PATH");
                        Program.Exit(1);
                    }

                    certName = commandLine.Arguments.FirstOrDefault();

                    if (string.IsNullOrEmpty(certName))
                    {
                        Console.Error.WriteLine("*** ERROR: Expected arguments: NAME");
                        Program.Exit(1);
                    }

                    if (!HiveDefinition.IsValidName(certName))
                    {
                        Console.Error.WriteLine($"*** ERROR: [{certName}] is not a valid certificate name.");
                        Program.Exit(1);
                    }

                    certificate = TlsCertificate.Load(commandLine.Arguments.ElementAtOrDefault(1));

                    certificate.Parse();

                    if (HiveHelper.Hive.Certificate.Get(certName) == null)
                    {
                        HiveHelper.Hive.Certificate.Set(certName, certificate);
                        Console.WriteLine($"Certificate [{certName}] was added.");
                    }
                    else
                    {
                        HiveHelper.Hive.Certificate.Set(certName, certificate);
                        Console.WriteLine($"Certificate [{certName}] was updated.");
                    }
                }
                break;

            case "split":

                if (commandLine.Arguments.Length != 3)
                {
                    Console.Error.WriteLine("*** ERROR: Expected arguments: PATH PATH-CERT PATH-KEY");
                    Program.Exit(1);
                }

                certificate = TlsCertificate.Load(commandLine.Arguments[0]);

                File.WriteAllText(commandLine.Arguments[1], certificate.CertPem);
                File.WriteAllText(commandLine.Arguments[2], certificate.KeyPem);
                break;

            case "verify":

                VerifyLocalCertificate(commandLine);
                break;

            default:

                Console.Error.WriteLine($"*** ERROR: Unknown command: [{command}]");
                Program.Exit(1);
                break;
            }
        }
Beispiel #7
0
        /// <inheritdoc/>
        public override void Run(CommandLine commandLine)
        {
            if (commandLine.HasHelpOption || commandLine.Arguments.Length == 0)
            {
                Console.WriteLine(usage);
                Program.Exit(0);
            }

            Program.ConnectHive();

            // Process the command arguments.

            var trafficManager = (TrafficManager)null;
            var yaml           = commandLine.HasOption("--yaml");
            var directorName   = commandLine.Arguments.FirstOrDefault();
            var isPublic       = false;

            switch (directorName)
            {
            case "help":

                // $hack: This isn't really a traffic manager name.

                Console.WriteLine(ruleHelp);
                Program.Exit(0);
                break;

            case "public":

                trafficManager = HiveHelper.Hive.PublicTraffic;
                isPublic       = true;
                break;

            case "private":

                trafficManager = HiveHelper.Hive.PrivateTraffic;
                isPublic       = false;
                break;

            default:

                Console.Error.WriteLine($"*** ERROR: Load balancer name must be one of [public] or [private] ([{directorName}] is not valid).");
                Program.Exit(1);
                break;
            }

            commandLine = commandLine.Shift(1);

            var command = commandLine.Arguments.FirstOrDefault();

            if (command == null)
            {
                Console.WriteLine(usage);
                Program.Exit(1);
            }

            commandLine = commandLine.Shift(1);

            string ruleName;

            switch (command)
            {
            case "get":

                ruleName = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(ruleName))
                {
                    Console.Error.WriteLine("*** ERROR: [RULE] argument expected.");
                    Program.Exit(1);
                }

                if (!HiveDefinition.IsValidName(ruleName))
                {
                    Console.Error.WriteLine($"*** ERROR: [{ruleName}] is not a valid rule name.");
                    Program.Exit(1);
                }

                // Fetch a specific traffic manager rule and output it.

                var rule = trafficManager.GetRule(ruleName);

                if (rule == null)
                {
                    Console.Error.WriteLine($"*** ERROR: Load balancer [{directorName}] rule [{ruleName}] does not exist.");
                    Program.Exit(1);
                }

                Console.WriteLine(yaml ? rule.ToYaml() : rule.ToJson());
                break;

            case "haproxy":
            case "haproxy-bridge":
            case "varnish":

                // We're going to download the traffic manager's ZIP archive containing the
                // [haproxy.cfg] or [varnish.vcl] file, extract and write it to the console.

                using (var consul = HiveHelper.OpenConsul())
                {
                    var proxy        = command.Equals("haproxy-bridge", StringComparison.InvariantCultureIgnoreCase) ? directorName + "-bridge" : directorName;
                    var confKey      = $"neon/service/neon-proxy-manager/proxies/{proxy}/proxy-conf";
                    var confZipBytes = consul.KV.GetBytesOrDefault(confKey).Result;

                    if (confZipBytes == null)
                    {
                        Console.Error.WriteLine($"*** ERROR: Proxy ZIP configuration was not found in Consul at [{confKey}].");
                        Program.Exit(1);
                    }

                    using (var msZipData = new MemoryStream(confZipBytes))
                    {
                        using (var zip = new ZipFile(msZipData))
                        {
                            var file  = command.Equals("varnish", StringComparison.InvariantCultureIgnoreCase) ? "varnish.vcl" : "haproxy.cfg";
                            var entry = zip.GetEntry(file);

                            if (entry == null || !entry.IsFile)
                            {
                                Console.Error.WriteLine($"*** ERROR: Proxy ZIP configuration in Consul at [{confKey}] appears to be corrupt.  Cannot locate the [{file}] entry.");
                                Program.Exit(1);
                            }

                            using (var entryStream = zip.GetInputStream(entry))
                            {
                                using (var reader = new StreamReader(entryStream))
                                {
                                    foreach (var line in reader.Lines())
                                    {
                                        Console.WriteLine(line);
                                    }
                                }
                            }
                        }
                    }
                }
                break;

            case "inspect":

                Console.WriteLine(NeonHelper.JsonSerialize(trafficManager.GetDefinition(), Formatting.Indented));
                break;

            case "list":
            case "ls":

                var showAll = commandLine.HasOption("--all");
                var showSys = commandLine.HasOption("--sys");
                var rules   = trafficManager.ListRules(
                    r =>
                {
                    if (showAll)
                    {
                        return(true);
                    }
                    else if (showSys)
                    {
                        return(r.System);
                    }
                    else
                    {
                        return(!r.System);
                    }
                });

                Console.WriteLine();
                Console.WriteLine($"[{rules.Count()}] {trafficManager.Name} rules");
                Console.WriteLine();

                foreach (var item in rules)
                {
                    Console.WriteLine(item.Name);
                }

                Console.WriteLine();
                break;

            case "purge":

                var purgeUri = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(purgeUri))
                {
                    Console.Error.WriteLine("*** ERROR: [URI-PATTERN] or [ALL] argument expected.");
                }

                if (purgeUri.Equals("all", StringComparison.InvariantCultureIgnoreCase))
                {
                    if (!commandLine.HasOption("--force") && !Program.PromptYesNo($"*** Are you sure you want to purge all cached items for [{directorName.ToUpperInvariant()}]?"))
                    {
                        return;
                    }

                    trafficManager.PurgeAll();
                }
                else
                {
                    trafficManager.Purge(new string[] { purgeUri });
                }

                Console.WriteLine();
                Console.WriteLine("Purge request submitted.");
                Console.WriteLine();
                break;

            case "update":

                trafficManager.Update();
                break;

            case "remove":
            case "rm":

                ruleName = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(ruleName))
                {
                    Console.Error.WriteLine("*** ERROR: [RULE] argument expected.");
                    Program.Exit(1);
                }

                if (!HiveDefinition.IsValidName(ruleName))
                {
                    Console.Error.WriteLine($"*** ERROR: [{ruleName}] is not a valid rule name.");
                    Program.Exit(1);
                }

                if (trafficManager.RemoveRule(ruleName))
                {
                    Console.Error.WriteLine($"Deleted load balancer [{directorName}] rule [{ruleName}].");
                }
                else
                {
                    Console.Error.WriteLine($"*** ERROR: Load balancer [{directorName}] rule [{ruleName}] does not exist.");
                    Program.Exit(1);
                }
                break;

            case "set":

                // $todo(jeff.lill):
                //
                // It would be really nice to download the existing rules and verify that
                // adding the new rule won't cause conflicts.  Currently errors will be
                // detected only by the [neon-proxy-manager] which will log them and cease
                // updating the hive until the errors are corrected.
                //
                // An alternative would be to have some kind of service available in the
                // hive to do this for us or perhaps having [neon-proxy-manager] generate
                // a summary of all of the certificates (names, covered hostnames, and
                // expiration dates) and save this to Consul so it would be easy to
                // download.  Perhaps do the same for the rules?

                if (commandLine.Arguments.Length != 2)
                {
                    Console.Error.WriteLine("*** ERROR: FILE or [-] argument expected.");
                    Program.Exit(1);
                }

                // Load the rule.  Note that we support reading rules as JSON or
                // YAML, automatcially detecting the format.  We always persist
                // rules as JSON though.

                var ruleFile = commandLine.Arguments[1];

                string ruleText;

                if (ruleFile == "-")
                {
                    using (var input = Console.OpenStandardInput())
                    {
                        using (var reader = new StreamReader(input, detectEncodingFromByteOrderMarks: true))
                        {
                            ruleText = reader.ReadToEnd();
                        }
                    }
                }
                else
                {
                    ruleText = File.ReadAllText(ruleFile);
                }

                var trafficManagerRule = TrafficRule.Parse(ruleText, strict: true);

                ruleName = trafficManagerRule.Name;

                if (!HiveDefinition.IsValidName(ruleName))
                {
                    Console.Error.WriteLine($"*** ERROR: [{ruleName}] is not a valid rule name.");
                    Program.Exit(1);
                }

                // Validate a clone of the rule with any implicit frontends.

                var clonedRule = NeonHelper.JsonClone(trafficManagerRule);
                var context    = new TrafficValidationContext(directorName, null)
                {
                    ValidateCertificates = false        // Disable this because we didn't download the certs (see note above)
                };

                clonedRule.Validate(context);
                clonedRule.Normalize(isPublic);

                if (context.HasErrors)
                {
                    Console.Error.WriteLine("*** ERROR: One or more rule errors:");
                    Console.Error.WriteLine();

                    foreach (var error in context.Errors)
                    {
                        Console.Error.WriteLine(error);
                    }

                    Program.Exit(1);
                }

                if (trafficManager.SetRule(trafficManagerRule))
                {
                    Console.WriteLine($"Load balancer [{directorName}] rule [{ruleName}] has been updated.");
                }
                else
                {
                    Console.WriteLine($"Load balancer [{directorName}] rule [{ruleName}] has been added.");
                }
                break;

            case "settings":

                var settingsFile = commandLine.Arguments.FirstOrDefault();

                if (string.IsNullOrEmpty(settingsFile))
                {
                    Console.Error.WriteLine("*** ERROR: [-] or FILE argument expected.");
                    Program.Exit(1);
                }

                string settingsText;

                if (settingsFile == "-")
                {
                    settingsText = NeonHelper.ReadStandardInputText();
                }
                else
                {
                    settingsText = File.ReadAllText(settingsFile);
                }

                var trafficManagerSettings = TrafficSettings.Parse(settingsText, strict: true);

                trafficManager.UpdateSettings(trafficManagerSettings);
                Console.WriteLine($"Traffic manager [{directorName}] settings have been updated.");
                break;

            case "status":

                using (var consul = HiveHelper.OpenConsul())
                {
                    var statusJson = consul.KV.GetStringOrDefault($"neon/service/neon-proxy-manager/status/{directorName}").Result;

                    if (statusJson == null)
                    {
                        Console.Error.WriteLine($"*** ERROR: Status for traffic manager [{directorName}] is not currently available.");
                        Program.Exit(1);
                    }

                    var trafficManagerStatus = NeonHelper.JsonDeserialize <TrafficStatus>(statusJson);

                    Console.WriteLine();
                    Console.WriteLine($"Snapshot Time: {trafficManagerStatus.TimestampUtc} (UTC)");
                    Console.WriteLine();

                    using (var reader = new StringReader(trafficManagerStatus.Status))
                    {
                        foreach (var line in reader.Lines())
                        {
                            Console.WriteLine(line);
                        }
                    }
                }
                break;

            default:

                Console.Error.WriteLine($"*** ERROR: Unknown command: [{command}]");
                Program.Exit(1);
                break;
            }
        }