public async Task Aquire(IAzureOptionsCommon options, IInputService _input)
        {
            var defaultEnvironment = (await Environment.GetValue()) ?? AzureEnvironments.AzureCloud;
            var environments       = new List <Choice <Func <Task> > >(
                AzureEnvironments.ResourceManagerUrls
                .OrderBy(kvp => kvp.Key)
                .Select(kvp =>
                        Choice.Create <Func <Task> >(() =>
            {
                options.AzureEnvironment = kvp.Key;
                return(Task.CompletedTask);
            },
                                                     description: kvp.Key,
                                                     @default: kvp.Key == defaultEnvironment)))
            {
                Choice.Create <Func <Task> >(async() => await InputUrl(_input, options), "Use a custom resource manager url")
            };
            var chosen = await _input.ChooseFromMenu("Which Azure environment are you using?", environments);

            await chosen.Invoke();

            options.UseMsi =
                await UseMsi.GetValue() == true ||
                await _input.PromptYesNo("Do you want to use a managed service identity?", false);

            if (!options.UseMsi)
            {
                // These options are only necessary for client id/secret authentication.
                options.TenantId = await TenantId.Interactive(_input, "Directory/tenant id").GetValue();

                options.ClientId = await ClientId.Interactive(_input, "Application client id").GetValue();

                options.Secret = await ClientSecret.Interactive(_input, "Application client secret").GetValue();
            }
        }
        public override async Task <CertificateStoreOptions?> Aquire(IInputService inputService, RunLevel runLevel)
        {
            var ret = await Default();

            if (ret != null &&
                string.IsNullOrEmpty(ret.StoreName) &&
                runLevel.HasFlag(RunLevel.Advanced))
            {
                var currentDefault = CertificateStore.DefaultStore(_settingsService, _iisClient);
                var choices        = new List <Choice <string?> >();
                if (_iisClient.Version.Major > 8)
                {
                    choices.Add(Choice.Create <string?>(
                                    "WebHosting",
                                    description: "[WebHosting] - Dedicated store for IIS"));
                }
                choices.Add(Choice.Create <string?>(
                                "My",
                                description: "[My] - General computer store (for Exchange/RDS)"));
                choices.Add(Choice.Create <string?>(
                                null,
                                description: $"[Default] - Use global default, currently {currentDefault}",
                                @default: true));
                var choice = await inputService.ChooseFromMenu(
                    "Choose store to use, or type the name of another unlisted store",
                    choices,
                    other => Choice.Create <string?>(other));

                // final save
                ret.StoreName = string.IsNullOrWhiteSpace(choice) ? null : choice;
            }
            return(ret);
        }
        public async Task Aquire(IAzureOptionsCommon options)
        {
            var az           = _arguments.GetArguments <T>();
            var environments = new List <Choice <Func <Task> > >(
                AzureEnvironments.ResourceManagerUrls
                .OrderBy(kvp => kvp.Key)
                .Select(kvp =>
                        Choice.Create <Func <Task> >(() =>
            {
                options.AzureEnvironment = kvp.Key;
                return(Task.CompletedTask);
            },
                                                     description: kvp.Key,
                                                     @default: kvp.Key == AzureEnvironments.AzureCloud)))
            {
                Choice.Create <Func <Task> >(async() => await InputUrl(_input, options), "Use a custom resource manager url")
            };

            var chosen = await _input.ChooseFromMenu("Which Azure environment are you using?", environments);

            await chosen.Invoke();

            options.UseMsi = az.AzureUseMsi || await _input.PromptYesNo("Do you want to use a managed service identity?", true);

            if (!options.UseMsi)
            {
                // These options are only necessary for client id/secret authentication.
                options.TenantId = await _arguments.TryGetArgument(az.AzureTenantId, _input, "Directory/tenant id");

                options.ClientId = await _arguments.TryGetArgument(az.AzureClientId, _input, "Application client id");

                options.Secret = new ProtectedString(await _arguments.TryGetArgument(az.AzureSecret, _input, "Application client secret", true));
            }
        }
        public override async Task <ScriptOptions?> Aquire(Target target, IInputService input, RunLevel runLevel)
        {
            var    args         = _arguments.GetArguments <ScriptArguments>();
            var    ret          = new ScriptOptions();
            string?createScript = null;

            do
            {
                createScript = args?.DnsCreateScript ?? await input.RequestString("Path to script that creates DNS records");
            }while (!createScript.ValidFile(_log));

            string?deleteScript = null;
            var    chosen       = await input.ChooseFromMenu(
                "How to delete records after validation",
                new List <Choice <Func <Task> > >()
            {
                Choice.Create <Func <Task> >(() => {
                    deleteScript = createScript;
                    return(Task.CompletedTask);
                }, "Using the same script"),
                Choice.Create <Func <Task> >(async() => {
                    do
                    {
                        deleteScript = args?.DnsDeleteScript ??
                                       await input.RequestString("Path to script that deletes DNS records");
                    }while (!deleteScript.ValidFile(_log));
                }, "Using a different script"),
                Choice.Create <Func <Task> >(() => Task.CompletedTask, "Do not delete")
            });

            await chosen.Invoke();

            ProcessScripts(ret, null, createScript, deleteScript);

            input.Show("{Identifier}", "Domain that's being validated");
            input.Show("{RecordName}", "Full TXT record name");
            input.Show("{Token}", "Expected value in the TXT record");
            var createArgs = args?.DnsCreateScriptArguments ??
                             await input.RequestString($"Input parameters for create script, or enter for default \"{Script.DefaultCreateArguments}\"");

            string?deleteArgs = null;

            if (!string.IsNullOrWhiteSpace(ret.DeleteScript) ||
                !string.IsNullOrWhiteSpace(ret.Script))
            {
                deleteArgs = args?.DnsDeleteScriptArguments ??
                             await input.RequestString($"Input parameters for delete script, or enter for default \"{Script.DefaultDeleteArguments}\"");
            }
            ProcessArgs(ret, createArgs, deleteArgs);
            return(ret);
        }
示例#5
0
        /// <summary>
        /// Main user experience
        /// </summary>
        private async Task MainMenu()
        {
            var options = new List <Choice <Func <Task> > >
            {
                Choice.Create <Func <Task> >(
                    () => _renewalManager.SetupRenewal(RunLevel.Interactive | RunLevel.Simple),
                    "Create new certificate (simple for IIS)", "N",
                    @default: _userRoleService.AllowIIS,
                    disabled: !_userRoleService.AllowIIS),
                Choice.Create <Func <Task> >(
                    () => _renewalManager.SetupRenewal(RunLevel.Interactive | RunLevel.Advanced),
                    "Create new certificate (full options)", "M",
                    @default: !_userRoleService.AllowIIS),
                Choice.Create <Func <Task> >(
                    () => _renewalManager.ShowRenewals(),
                    "List scheduled renewals", "L"),
                Choice.Create <Func <Task> >(
                    () => _renewalManager.CheckRenewals(RunLevel.Interactive),
                    "Renew scheduled", "R"),
                Choice.Create <Func <Task> >(
                    () => _renewalManager.RenewSpecific(),
                    "Renew specific", "S"),
                Choice.Create <Func <Task> >(
                    () => _renewalManager.CheckRenewals(RunLevel.Interactive | RunLevel.ForceRenew),
                    "Renew *all*", "A"),
                Choice.Create <Func <Task> >(
                    () => ExtraMenu(),
                    "More options...", "O"),
                Choice.Create <Func <Task> >(
                    () => { _args.CloseOnFinish = true; _args.Test = false; return(Task.CompletedTask); },
                    "Quit", "Q")
            };
            var chosen = await _input.ChooseFromMenu("Please choose from the menu", options);

            await chosen.Invoke();
        }
示例#6
0
        public override async Task <bool> CreateRecord(DnsValidationRecord record)
        {
            _input.CreateSpace();
            _input.Show("Domain", record.Context.Identifier);
            _input.Show("Record", record.Authority.Domain);
            _input.Show("Type", "TXT");
            _input.Show("Content", $"\"{record.Value}\"");
            _input.Show("Note", "Some DNS managers add quotes automatically. A single set is needed.");
            if (!await _input.Wait("Please press <Enter> after you've created and verified the record"))
            {
                _log.Warning("User aborted");
                return(false);
            }

            if (!_settings.Validation.PreValidateDns)
            {
                return(true);
            }

            // Pre-pre-validate, allowing the manual user to correct mistakes
            while (true)
            {
                if (await PreValidate(record))
                {
                    return(true);
                }
                else
                {
                    _input.CreateSpace();
                    _input.Show(null, value: "The correct record has not yet been found by the local resolver. That means it's likely the validation attempt will fail, or your DNS provider needs a little more time to publish and synchronize the changes.");
                    var options = new List <Choice <bool?> >
                    {
                        Choice.Create <bool?>(null, "Retry check"),
                        Choice.Create <bool?>(true, "Ignore and continue"),
                        Choice.Create <bool?>(false, "Abort")
                    };
                    var chosen = await _input.ChooseFromMenu("How would you like to proceed?", options);

                    if (chosen != null)
                    {
                        return(chosen.Value);
                    }
                }
            }
        }
示例#7
0
        /// <summary>
        /// Single round of aquiring settings
        /// </summary>
        /// <param name="input"></param>
        /// <param name="allBindings"></param>
        /// <param name="visibleBindings"></param>
        /// <param name="allSites"></param>
        /// <param name="visibleSites"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        private async Task<IISOptions?> TryAquireSettings(
            IInputService input, 
            List<IISHelper.IISBindingOption> allBindings,
            List<IISHelper.IISBindingOption> visibleBindings,
            List<IISHelper.IISSiteOption> allSites,
            List<IISHelper.IISSiteOption> visibleSites,
            RunLevel runLevel)
        {
            input.CreateSpace();
            input.Show(null, "Please select which website(s) should be scanned for host names. " +
                "You may input one or more site identifiers (comma-separated) to filter by those sites, " +
                "or alternatively leave the input empty to scan *all* websites.");

            var options = new IISOptions();
            await input.WritePagedList(
                visibleSites.Select(x => Choice.Create(
                    item: x,
                    description: $"{x.Name} ({x.Hosts.Count()} binding{(x.Hosts.Count() == 1 ? "" : "s")})",
                    command: x.Id.ToString(),
                    color: x.Https ? ConsoleColor.DarkGray : (ConsoleColor?)null)));
            var raw = await input.RequestString("Site identifier(s) or <Enter> to choose all");
            if (!ParseSiteOptions(raw, allSites, options))
            {
                return null;
            }

            var filtered = _iisHelper.FilterBindings(visibleBindings, options);
            await ListBindings(input, filtered);
            input.CreateSpace();
            input.Show(null,
                "Listed above are the bindings found on the selected site(s). By default all of " +
                "them will be included, but you may either pick specific ones by typing the host names " +
                "or identifiers (comma-separated) or filter them using one of the options from the " +
                "menu.");
            var askExclude = true;
            var filters = new List<Choice<Func<Task>>>
            {
                Choice.Create<Func<Task>>(() => {
                    return InputPattern(input, options); 
                }, "Pick bindings based on a search pattern", command: "P"),
                Choice.Create<Func<Task>>(() => { 
                    askExclude = false; 
                    return Task.CompletedTask; 
                }, "Pick *all* bindings", @default: true, command: "A")
            };
            if (runLevel.HasFlag(RunLevel.Advanced))
            {
                filters.Insert(1, Choice.Create<Func<Task>>(() => { 
                    askExclude = true; 
                    return InputRegex(input, options);
                }, "Pick bindings based on a regular expression", command: "R"));
            }

            // Handle undefined input
            Choice<Func<Task>> processUnkown(string unknown) {
                return Choice.Create<Func<Task>>(() =>
                {
                    askExclude = false;
                    return ProcessInputHosts(
                        unknown, 
                        allBindings, 
                        filtered, 
                        options,
                        () => options.IncludeHosts, x => options.IncludeHosts = x);
                });
            }

            var chosen = await input.ChooseFromMenu(
                "Binding identifiers(s) or menu option",
                filters,
                processUnkown);
            await chosen.Invoke();

            filtered = _iisHelper.FilterBindings(allBindings, options);

            // Exclude specific bindings
            if (askExclude && filtered.Count > 1 && runLevel.HasFlag(RunLevel.Advanced))
            {
                await ListBindings(input, filtered);
                input.CreateSpace();
                input.Show(null, "The listed bindings match your current filter settings. " +
                    "If you wish to exclude one or more of them from the certificate, please " +
                    "input those bindings now. Press <Enter> to include all listed bindings.");
                await InputHosts("Exclude bindings", 
                    input, allBindings, filtered, options, 
                    () => options.ExcludeHosts, x => options.ExcludeHosts = x);
                if (options.ExcludeHosts != null)
                {
                    filtered = _iisHelper.FilterBindings(allBindings, options);
                }
            } 

            // Now the common name
            if (filtered.Select(x => x.HostUnicode).Distinct().Count() > 1)
            {
                await InputCommonName(input, filtered, options);
            }
            return options;
        }
示例#8
0
        /// <summary>
        /// Setup a new ACME account
        /// </summary>
        /// <param name="client"></param>
        /// <returns></returns>
        private async Task SetupAccount(AcmeProtocolClient client)
        {
            // Accept the terms of service, if defined by the server
            try
            {
                var(_, filename, content) = await client.GetTermsOfServiceAsync();

                _log.Verbose("Terms of service downloaded");
                if (!string.IsNullOrEmpty(filename))
                {
                    if (!await AcceptTos(filename, content))
                    {
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Error getting terms of service");
            }

            var contacts = default(string[]);

            var eabKid      = _accountArguments.EabKeyIdentifier;
            var eabKey      = _accountArguments.EabKey;
            var eabAlg      = _accountArguments.EabAlgorithm ?? "HS256";
            var eabFlow     = client.Directory?.Meta?.ExternalAccountRequired == "true";
            var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com");

            // Warn about unneeded EAB
            if (!eabFlow && !string.IsNullOrWhiteSpace(eabKid))
            {
                eabFlow = true;
                _input.CreateSpace();
                _input.Show(null, "You have provided an external account binding key, even though " +
                            "the server does not indicate that this is required. We will attempt to register " +
                            "using this key anyway.");
            }

            if (zeroSslFlow)
            {
                async Task emailRegistration()
                {
                    var registration = await GetContacts(allowMultiple : false, prefix : "");

                    var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? "");

                    if (eab != null)
                    {
                        eabKid = eab.Kid;
                        eabKey = eab.Hmac;
                    }
                    else
                    {
                        _log.Error("Unable to retrieve EAB credentials using the provided email address");
                    }
                }

                async Task apiKeyRegistration()
                {
                    var accessKey = await _input.ReadPassword("API access key");

                    var eab = await _zeroSsl.Obtain(accessKey ?? "");

                    if (eab != null)
                    {
                        eabKid = eab.Kid;
                        eabKey = eab.Hmac;
                    }
                    else
                    {
                        _log.Error("Unable to retrieve EAB credentials using the provided API access key");
                    }
                }

                if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress))
                {
                    await emailRegistration();
                }
                else
                {
                    var instruction = "ZeroSsl can be used either by setting up a new " +
                                      "account using your email address or by connecting it to your existing " +
                                      "account using the API access key or pre-generated EAB credentials, which can " +
                                      "be obtained from the Developer section of the dashboard.";
                    _input.CreateSpace();
                    _input.Show(null, instruction);
                    var chosen = await _input.ChooseFromMenu(
                        "How would you like to create the account?",
                        new List <Choice <Func <Task> > >()
                    {
                        Choice.Create(apiKeyRegistration, "API access key"),
                        Choice.Create(emailRegistration, "Email address"),
                        Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly")
                    });

                    await chosen.Invoke();
                }
            }

            if (eabFlow)
            {
                if (string.IsNullOrWhiteSpace(eabKid))
                {
                    var instruction = "This ACME endpoint requires an external account. " +
                                      "You will need to provide a key identifier and a key to proceed. " +
                                      "Please refer to the providers instructions on how to obtain these.";
                    _input.CreateSpace();
                    _input.Show(null, instruction);
                    eabKid = await _input.RequestString("Key identifier");
                }
                if (string.IsNullOrWhiteSpace(eabKey))
                {
                    eabKey = await _input.ReadPassword("Key (base64url encoded)");
                }
                contacts = await GetContacts(runLevel : RunLevel.Unattended);
            }
            else
            {
                contacts = await GetContacts();
            }

            var signer = _accountManager.DefaultSigner();

            try
            {
                await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey);
            }
            catch (AcmeProtocolException apex)
            {
                // Some non-ACME compliant server may not support ES256 or other
                // algorithms, so attempt fallback to RS256
                if (apex.ProblemType == acme.ProblemType.BadSignatureAlgorithm &&
                    signer.KeyType != "RS256")
                {
                    signer = _accountManager.NewSigner("RS256");
                    await CreateAccount(client, signer, contacts, eabAlg, eabKid, eabKey);
                }
                else
                {
                    throw;
                }
            }
            catch (Exception ex)
            {
                _log.Error(ex, "Error creating account");
                throw;
            }
        }
示例#9
0
        /// <summary>
        /// Renewal management mode
        /// </summary>
        /// <returns></returns>
        internal async Task ManageRenewals()
        {
            IEnumerable <Renewal> originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
            var selectedRenewals = originalSelection;
            var quit             = false;
            var displayAll       = false;

            do
            {
                var all                  = selectedRenewals.Count() == originalSelection.Count();
                var none                 = !selectedRenewals.Any();
                var totalLabel           = originalSelection.Count() != 1 ? "renewals" : "renewal";
                var renewalSelectedLabel = selectedRenewals.Count() != 1 ? "renewals" : "renewal";
                var selectionLabel       =
                    all ? selectedRenewals.Count() == 1 ? "the renewal" : "*all* renewals" :
                    none ? "no renewals" :
                    $"{selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}";

                _input.CreateSpace();
                _input.Show(null,
                            "Welcome to the renewal manager. Actions selected in the menu below will " +
                            "be applied to the following list of renewals. You may filter the list to target " +
                            "your action at a more specific set of renewals, or sort it to make it easier to " +
                            "find what you're looking for.");

                var displayRenewals    = selectedRenewals;
                var displayLimited     = !displayAll && selectedRenewals.Count() >= _settings.UI.PageSize;
                var displayHidden      = 0;
                var displayHiddenLabel = "";
                if (displayLimited)
                {
                    displayRenewals    = displayRenewals.Take(_settings.UI.PageSize - 1);
                    displayHidden      = selectedRenewals.Count() - displayRenewals.Count();
                    displayHiddenLabel = displayHidden != 1 ? "renewals" : "renewal";
                }
                var choices = displayRenewals.Select(x => Choice.Create <Renewal?>(x,
                                                                                   description: x.ToString(_input),
                                                                                   color: x.History.LastOrDefault()?.Success ?? false ?
                                                                                   x.IsDue() ?
                                                                                   ConsoleColor.DarkYellow :
                                                                                   ConsoleColor.Green :
                                                                                   ConsoleColor.Red)).ToList();
                if (displayLimited)
                {
                    choices.Add(Choice.Create <Renewal?>(null,
                                                         command: "More",
                                                         description: $"{displayHidden} additional {displayHiddenLabel} selected but currently not displayed"));
                }
                await _input.WritePagedList(choices);

                displayAll = false;

                var options = new List <Choice <Func <Task> > >();
                if (displayLimited)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            () => { displayAll = true; return(Task.CompletedTask); },
                            "List all selected renewals", "A"));
                }
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => { quit = true; await EditRenewal(selectedRenewals.First()); },
                        "Edit renewal", "E",
                        @disabled: (selectedRenewals.Count() != 1, none ? "No renewals selected." : "Multiple renewals selected.")));
                if (selectedRenewals.Count() > 1)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            async() => selectedRenewals = await FilterRenewalsMenu(selectedRenewals),
                            all ? "Apply filter" : "Apply additional filter", "F",
                            @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to filter.")));
                    options.Add(
                        Choice.Create <Func <Task> >(
                            async() => selectedRenewals = await SortRenewalsMenu(selectedRenewals),
                            "Sort renewals", "S",
                            @disabled: (selectedRenewals.Count() < 2, "Not enough renewals to sort.")));
                }
                if (!all)
                {
                    options.Add(
                        Choice.Create <Func <Task> >(
                            () => { selectedRenewals = originalSelection; return(Task.CompletedTask); },
                            "Reset sorting and filtering", "X",
                            @disabled: (all, "No filters have been applied yet.")));
                }
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    foreach (var renewal in selectedRenewals)
                    {
                        var index = selectedRenewals.ToList().IndexOf(renewal) + 1;
                        _log.Information("Details for renewal {n}/{m}", index, selectedRenewals.Count());
                        await ShowRenewal(renewal);
                        var cont = false;
                        if (index != selectedRenewals.Count())
                        {
                            cont = await _input.Wait("Press <Enter> to continue or <Esc> to abort");
                            if (!cont)
                            {
                                break;
                            }
                        }
                        else
                        {
                            await _input.Wait();
                        }
                    }
                },
                        $"Show details for {selectionLabel}", "D",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    WarnAboutRenewalArguments();
                    foreach (var renewal in selectedRenewals)
                    {
                        var runLevel = RunLevel.Interactive | RunLevel.ForceRenew;
                        if (_args.Force)
                        {
                            runLevel |= RunLevel.IgnoreCache;
                        }
                        await ProcessRenewal(renewal, runLevel);
                    }
                },
                        $"Run {selectionLabel}", "R",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await Analyze(selectedRenewals),
                        $"Analyze duplicates for {selectionLabel}", "U",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to cancel {selectedRenewals.Count()} currently selected {renewalSelectedLabel}?", false);
                    if (confirm)
                    {
                        foreach (var renewal in selectedRenewals)
                        {
                            _renewalStore.Cancel(renewal);
                        }
                        ;
                        originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
                        selectedRenewals  = originalSelection;
                    }
                },
                        $"Cancel {selectionLabel}", "C",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to revoke the most recently issued certificate for {selectedRenewals.Count()} currently selected {renewalSelectedLabel}? This should only be done in case of a (suspected) security breach. Cancel the {renewalSelectedLabel} if you simply don't need the certificates anymore.", false);
                    if (confirm)
                    {
                        await RevokeCertificates(selectedRenewals);
                    }
                },
                        $"Revoke certificate(s) for {selectionLabel}", "V",
                        @disabled: (none, "No renewals selected.")));
                options.Add(
                    Choice.Create <Func <Task> >(
                        () => { quit = true; return(Task.CompletedTask); },
                        "Back", "Q",
                        @default: !originalSelection.Any()));

                if (selectedRenewals.Count() > 1)
                {
                    _input.CreateSpace();
                    _input.Show(null, $"Currently selected {selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}");
                }
                var chosen = await _input.ChooseFromMenu(
                    "Choose an action or type numbers to select renewals",
                    options,
                    (string unexpected) =>
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await FilterRenewalsById(selectedRenewals, unexpected)));

                await chosen.Invoke();
            }while (!quit);
        }
示例#10
0
        /// <summary>
        /// Single round of aquiring settings
        /// </summary>
        /// <param name="input"></param>
        /// <param name="allBindings"></param>
        /// <param name="visibleBindings"></param>
        /// <param name="allSites"></param>
        /// <param name="visibleSites"></param>
        /// <param name="runLevel"></param>
        /// <returns></returns>
        private async Task <IISOptions?> TryAquireSettings(
            IInputService input,
            List <IISHelper.IISBindingOption> allBindings,
            List <IISHelper.IISBindingOption> visibleBindings,
            List <IISHelper.IISSiteOption> allSites,
            List <IISHelper.IISSiteOption> visibleSites,
            RunLevel runLevel)
        {
            input.Show(null, "Please select which website(s) should be scanned for host names. " +
                       "You may input one or more site identifiers (comma separated) to filter by those sites, " +
                       "or alternatively leave the input empty to scan *all* websites.", true);

            var options = new IISOptions();
            await input.WritePagedList(
                visibleSites.Select(x => Choice.Create(
                                        item: x,
                                        description: $"{x.Name} ({x.Hosts.Count()} binding{(x.Hosts.Count() == 1 ? "" : "s")})",
                                        command: x.Id.ToString(),
                                        color: x.Https ? ConsoleColor.DarkGray : (ConsoleColor?)null)));

            var raw = await input.RequestString("Site identifier(s) or <ENTER> to choose all");

            if (!ParseSiteOptions(raw, allSites, options))
            {
                return(null);
            }

            var filtered = _iisHelper.FilterBindings(visibleBindings, options);

            await ListBindings(input, runLevel, filtered);

            input.Show(null,
                       "You may either choose to include all listed bindings as host names in your certificate, " +
                       "or apply an additional filter. Different types of filters are available.", true);
            var askExclude = true;
            var filters    = new List <Choice <Func <Task> > >
            {
                Choice.Create <Func <Task> >(() => {
                    askExclude = false;
                    return(InputHosts(
                               "Include bindings", input, allBindings, filtered, options,
                               () => options.IncludeHosts, x => options.IncludeHosts = x));
                }, "Pick specific bindings from the list"),
                Choice.Create <Func <Task> >(() => {
                    return(InputPattern(input, options));
                }, "Pick bindings based on a search pattern"),
                Choice.Create <Func <Task> >(() => {
                    askExclude = false;
                    return(Task.CompletedTask);
                }, "Pick *all* bindings", @default: true)
            };

            if (runLevel.HasFlag(RunLevel.Advanced))
            {
                filters.Insert(2, Choice.Create <Func <Task> >(() => {
                    askExclude = true;
                    return(InputRegex(input, options));
                }, "Pick bindings based on a regular expression"));
            }
            var chosen = await input.ChooseFromMenu("How do you want to pick the bindings?", filters);

            await chosen.Invoke();

            filtered = _iisHelper.FilterBindings(allBindings, options);

            // Check for wildcards in simple mode
            if (!runLevel.HasFlag(RunLevel.Advanced) && filtered.Any(x => x.Wildcard))
            {
                await ListBindings(input, runLevel, filtered);

                input.Show(null, "The pattern that you've chosen matches a wildcard binding, which " +
                           "is not supported by the 'simple' mode of this program because it requires DNS " +
                           "validation. Please try again with a different pattern or use the 'full options' " +
                           "mode instead.", true);
                return(null);
            }

            // Exclude specific bindings
            var listForCommon = false;

            if (askExclude && filtered.Count > 1 && runLevel.HasFlag(RunLevel.Advanced))
            {
                await ListBindings(input, runLevel, filtered);

                input.Show(null, "The listed bindings match your current filter settings. " +
                           "If you wish to exclude one or more of them from the certificate, please " +
                           "input those bindings now. Press <Enter> to include all listed bindings.", true);
                await InputHosts("Exclude bindings",
                                 input, allBindings, filtered, options,
                                 () => options.ExcludeHosts, x => options.ExcludeHosts = x);

                if (options.ExcludeHosts != null)
                {
                    filtered      = _iisHelper.FilterBindings(allBindings, options);
                    listForCommon = true;
                }
            }
            else
            {
                listForCommon = true;
            }

            // Now the common name
            if (filtered.Select(x => x.HostUnicode).Distinct().Count() > 1)
            {
                // If no bindings have been excluded, we can re-use
                // the previously printed list
                if (listForCommon)
                {
                    await ListBindings(input, runLevel, filtered);
                }
                await InputCommonName(input, filtered, options);
            }
            return(options);
        }
示例#11
0
        /// <summary>
        /// Get a secret from interactive mode setup
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        public async Task <string?> GetSecret(string purpose, string? @default = null, string?none = null, bool required = false, bool multiline = false)
        {
            var    stop = false;
            string?ret  = null;

            // While loop allows the "Find in vault" option
            // to be cancelled so that the user can still
            // input a new password if it's not found yet
            // without having to restart the process.
            while (!stop && string.IsNullOrEmpty(ret))
            {
                var options = new List <Choice <Func <Task <string?> > > >();
                if (!required)
                {
                    options.Add(Choice.Create <Func <Task <string?> > >(
                                    () => {
                        stop = true;
                        return(Task.FromResult(none));
                    },
                                    description: "None"));
                }
                options.Add(Choice.Create <Func <Task <string?> > >(
                                async() => {
                    stop = true;
                    if (multiline)
                    {
                        return(await _inputService.RequestString(purpose, true));
                    }
                    else
                    {
                        return(await _inputService.ReadPassword(purpose));
                    }
                },
                                description: "Type/paste in console"));
                options.Add(Choice.Create <Func <Task <string?> > >(
                                () => FindSecret(),
                                description: "Search in vault"));
                if (!string.IsNullOrWhiteSpace(@default))
                {
                    options.Add(Choice.Create <Func <Task <string?> > >(
                                    () => {
                        stop = true;
                        return(Task.FromResult <string?>(@default));
                    },
                                    description: "Default"));
                }

                // Handle undefined input as direct password
                Choice <Func <Task <string?> > > processUnkown(string?unknown) => Choice.Create <Func <Task <string?> > >(() => Task.FromResult(unknown));

                var chosen = await _inputService.ChooseFromMenu("Choose from the menu", options, (x) => processUnkown(x));

                ret = await chosen.Invoke();
            }

            if (ret == none)
            {
                return(none);
            }
            if (ret == @default || ret == null)
            {
                return(@default);
            }

            // Offer to save in list
            if (!ret.StartsWith(VaultPrefix))
            {
                var save = await _inputService.PromptYesNo($"Save to vault for future reuse?", false);

                if (save)
                {
                    return(await ChooseKeyAndStoreSecret(ret));
                }
            }
            return(ret);
        }
示例#12
0
        private async Task <AccountDetails?> LoadAccount(AcmeProtocolClient client, IJwsTool?signer)
        {
            _log.Verbose("Loading ACME account");
            AccountDetails?account = null;

            if (File.Exists(AccountPath))
            {
                if (signer != null)
                {
                    _log.Debug("Loading account information from {registrationPath}", AccountPath);
                    account        = JsonConvert.DeserializeObject <AccountDetails>(File.ReadAllText(AccountPath));
                    client.Account = account;
                    // Maybe we should update the account details
                    // on every start of the program to figure out
                    // if it hasn't been suspended or cancelled?
                    // UpdateAccount();
                }
                else
                {
                    _log.Error("Account found but no valid signer could be loaded");
                }
            }
            else
            {
                _log.Verbose("No account found at {path}, creating new one", AccountPath);
                try
                {
                    var(_, filename, content) = await client.GetTermsOfServiceAsync();

                    _log.Verbose("Terms of service downloaded");
                    if (!string.IsNullOrEmpty(filename))
                    {
                        if (!await AcceptTos(filename, content))
                        {
                            return(null);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error getting terms of service");
                }
                var contacts        = default(string[]);
                var externalAccount = default(ExternalAccountBinding);

                var kid         = _accountArguments.EabKeyIdentifier;
                var key         = _accountArguments.EabKey;
                var alg         = _accountArguments.EabAlgorithm ?? "HS256";
                var eabFlow     = client.Directory?.Meta?.ExternalAccountRequired == "true";
                var zeroSslFlow = _settings.BaseUri.Host.Contains("zerossl.com");

                // Warn about unneeded EAB
                if (!eabFlow && !string.IsNullOrWhiteSpace(kid))
                {
                    eabFlow = true;
                    _input.CreateSpace();
                    _input.Show(null, "You have provided an external account binding key, even though " +
                                "the server does not indicate that this is required. We will attempt to register " +
                                "using this key anyway.");
                }

                if (zeroSslFlow)
                {
                    async Task emailRegistration()
                    {
                        var registration = await GetContacts(allowMultiple : false, prefix : "");

                        var eab = await _zeroSsl.Register(registration.FirstOrDefault() ?? "");

                        if (eab != null)
                        {
                            kid = eab.Kid;
                            key = eab.Hmac;
                        }
                        else
                        {
                            _log.Error("Unable to retrieve EAB credentials using the provided email address");
                        }
                    }

                    async Task apiKeyRegistration()
                    {
                        var accessKey = await _input.ReadPassword("API access key");

                        var eab = await _zeroSsl.Obtain(accessKey ?? "");

                        if (eab != null)
                        {
                            kid = eab.Kid;
                            key = eab.Hmac;
                        }
                        else
                        {
                            _log.Error("Unable to retrieve EAB credentials using the provided API access key");
                        }
                    }

                    if (!string.IsNullOrWhiteSpace(_accountArguments.EmailAddress))
                    {
                        await emailRegistration();
                    }
                    else
                    {
                        var instruction = "ZeroSsl can be used either by setting up a new " +
                                          "account using your email address or by connecting it to your existing " +
                                          "account using the API access key or pre-generated EAB credentials, which can " +
                                          "be obtained from the Developer section of the dashboard.";
                        _input.CreateSpace();
                        _input.Show(null, instruction);
                        var chosen = await _input.ChooseFromMenu(
                            "How would you like to create the account?",
                            new List <Choice <Func <Task> > >()
                        {
                            Choice.Create((Func <Task>)apiKeyRegistration, "API access key"),
                            Choice.Create((Func <Task>)emailRegistration, "Email address"),
                            Choice.Create <Func <Task> >(() => Task.CompletedTask, "Input EAB credentials directly")
                        });

                        await chosen.Invoke();
                    }
                }

                if (eabFlow)
                {
                    if (string.IsNullOrWhiteSpace(kid))
                    {
                        var instruction = "This ACME endpoint requires an external account. " +
                                          "You will need to provide a key identifier and a key to proceed. " +
                                          "Please refer to the providers instructions on how to obtain these.";
                        _input.CreateSpace();
                        _input.Show(null, instruction);
                        kid = await _input.RequestString("Key identifier");
                    }
                    if (string.IsNullOrWhiteSpace(key))
                    {
                        key = await _input.ReadPassword("Key (base64url encoded)");
                    }
                    externalAccount = new ExternalAccountBinding(
                        alg,
                        JsonConvert.SerializeObject(
                            client.Signer.ExportJwk(),
                            Formatting.None),
                        kid,
                        key ?? "",
                        client.Directory?.NewAccount ?? "");
                }
                else
                {
                    contacts = await GetContacts();
                }

                try
                {
                    account = await client.CreateAccountAsync(
                        contacts,
                        termsOfServiceAgreed : true,
                        externalAccountBinding : externalAccount?.Payload() ?? null);
                }
                catch (Exception ex)
                {
                    _log.Error(ex, "Error creating account");
                }

                if (account != null)
                {
                    try
                    {
                        _log.Debug("Saving account");
                        var accountKey = new AccountSigner
                        {
                            KeyType   = client.Signer.JwsAlg,
                            KeyExport = client.Signer.Export(),
                        };
                        AccountSigner = accountKey;
                        await File.WriteAllTextAsync(AccountPath, JsonConvert.SerializeObject(account));
                    }
                    catch (Exception ex)
                    {
                        _log.Error(ex, "Error saving account");
                        account = null;
                    }
                }
            }
            return(account);
        }
        /// <summary>
        /// Get a secret from interactive mode setup
        /// </summary>
        /// <param name="purpose"></param>
        /// <returns></returns>
        public async Task <string?> GetSecret(string purpose, string? @default = null, bool?multiline = null)
        {
            var    stop = false;
            string?ret  = null;

            while (!stop && string.IsNullOrEmpty(ret))
            {
                var options = new List <Choice <Func <Task <string?> > > >
                {
                    Choice.Create <Func <Task <string?> > >(
                        () => {
                        stop = true;
                        return(Task.FromResult <string?>(null));
                    },
                        description: "None"),
                    Choice.Create <Func <Task <string?> > >(
                        async() => {
                        if (multiline == true)
                        {
                            return(await _inputService.RequestString("New secret", true));
                        }
                        else
                        {
                            return(await _inputService.ReadPassword("New secret"));
                        }
                    },
                        description: "Type/paste in console"),
                    Choice.Create <Func <Task <string?> > >(
                        () => FindSecret(),
                        description: "Search in vault"),
                };
                if (!string.IsNullOrWhiteSpace(@default))
                {
                    options.Add(Choice.Create <Func <Task <string?> > >(
                                    () => {
                        stop = true;
                        return(Task.FromResult <string?>(@default));
                    },
                                    description: "Default"));
                }
                var chosen = await _inputService.ChooseFromMenu(purpose, options);

                ret = await chosen.Invoke();
            }

            if (stop || string.IsNullOrWhiteSpace(ret))
            {
                return(null);
            }

            // Offer to save in list
            if (ret != @default && !ret.StartsWith(VaultPrefix))
            {
                var save = await _inputService.PromptYesNo($"Save to vault for future reuse?", false);

                if (save)
                {
                    return(await ChooseKeyAndStoreSecret(ret));
                }
            }
            return(ret);
        }
示例#14
0
        /// <summary>
        /// Renewal management mode
        /// </summary>
        /// <returns></returns>
        internal async Task ManageRenewals()
        {
            IEnumerable <Renewal> originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
            var selectedRenewals = originalSelection;
            var quit             = false;

            do
            {
                var all            = selectedRenewals.Count() == originalSelection.Count();
                var none           = selectedRenewals.Count() == 0;
                var totalLabel     = originalSelection.Count() != 1 ? "renewals" : "renewal";
                var selectionLabel =
                    all ? $"*all* renewals" :
                    none ? "no renewals" :
                    $"{selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}";
                var renewalSelectedLabel = selectedRenewals.Count() != 1 ? "renewals" : "renewal";

                _input.Show(null,
                            "Welcome to the renewal manager. Actions selected in the menu below will " +
                            "be applied to the following list of renewals. You may filter the list to target " +
                            "your action at a more specific set of renewals, or sort it to make it easier to " +
                            "find what you're looking for.",
                            true);

                await _input.WritePagedList(
                    selectedRenewals.Select(x => Choice.Create <Renewal?>(x,
                                                                          description: x.ToString(_input),
                                                                          color: x.History.Last().Success ?
                                                                          x.IsDue() ?
                                                                          ConsoleColor.DarkYellow :
                                                                          ConsoleColor.Green :
                                                                          ConsoleColor.Red)));

                var options = new List <Choice <Func <Task> > >();
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await FilterRenewalsMenu(selectedRenewals),
                        all ? "Apply filter" : "Apply additional filter", "F",
                        @disabled: selectedRenewals.Count() < 2,
                        disabledReason: "Not enough renewals to filter.",
                        @default: !(selectedRenewals.Count() < 2)));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => selectedRenewals = await SortRenewalsMenu(selectedRenewals),
                        "Sort renewals", "S",
                        @disabled: selectedRenewals.Count() < 2,
                        disabledReason: "Not enough renewals to sort."));
                options.Add(
                    Choice.Create <Func <Task> >(
                        () => { selectedRenewals = originalSelection; return(Task.CompletedTask); },
                        "Reset sorting and filtering", "X",
                        @disabled: all,
                        disabledReason: "No filters have been applied yet.",
                        @default: originalSelection.Count() > 0 && none));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    foreach (var renewal in selectedRenewals)
                    {
                        var index = selectedRenewals.ToList().IndexOf(renewal) + 1;
                        _log.Information("Details for renewal {n}/{m}", index, selectedRenewals.Count());
                        await ShowRenewal(renewal);
                        var cont = false;
                        if (index != selectedRenewals.Count())
                        {
                            cont = await _input.Wait("Press <Enter> to continue or <Esc> to abort");
                            if (!cont)
                            {
                                break;
                            }
                        }
                        else
                        {
                            await _input.Wait();
                        }
                    }
                },
                        $"Show details for {selectionLabel}", "D",
                        @disabled: none,
                        disabledReason: "No renewals selected."));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    WarnAboutRenewalArguments();
                    foreach (var renewal in selectedRenewals)
                    {
                        var runLevel = RunLevel.Interactive | RunLevel.ForceRenew;
                        if (_args.Force)
                        {
                            runLevel |= RunLevel.IgnoreCache;
                        }
                        await ProcessRenewal(renewal, runLevel);
                    }
                },
                        $"Run {selectionLabel}", "R",
                        @disabled: none,
                        disabledReason: "No renewals selected."));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to cancel {selectedRenewals.Count()} currently selected {renewalSelectedLabel}?", false);
                    if (confirm)
                    {
                        foreach (var renewal in selectedRenewals)
                        {
                            _renewalStore.Cancel(renewal);
                        }
                        ;
                        originalSelection = _renewalStore.Renewals.OrderBy(x => x.LastFriendlyName);
                        selectedRenewals  = originalSelection;
                    }
                },
                        $"Cancel {selectionLabel}", "C",
                        @disabled: none,
                        disabledReason: "No renewals selected."));
                options.Add(
                    Choice.Create <Func <Task> >(
                        async() => {
                    var confirm = await _input.PromptYesNo($"Are you sure you want to revoke the most recently issued certificate for {selectedRenewals.Count()} currently selected {renewalSelectedLabel}? This should only be done in case of a (suspected) security breach. Cancel the {renewalSelectedLabel} if you simply don't need the certificates anymore.", false);
                    if (confirm)
                    {
                        foreach (var renewal in selectedRenewals)
                        {
                            using var scope = _scopeBuilder.Execution(_container, renewal, RunLevel.Interactive);
                            var cs          = scope.Resolve <ICertificateService>();
                            try
                            {
                                await cs.RevokeCertificate(renewal);
                                renewal.History.Add(new RenewResult("Certificate revoked"));
                            }
                            catch (Exception ex)
                            {
                                _exceptionHandler.HandleException(ex);
                            }
                        }
                        ;
                    }
                },
                        $"Revoke {selectionLabel}", "V",
                        @disabled: none,
                        disabledReason: "No renewals selected."));
                options.Add(
                    Choice.Create <Func <Task> >(
                        () => { quit = true; return(Task.CompletedTask); },
                        "Back", "Q",
                        @default: originalSelection.Count() == 0));


                _input.Show(null, $"Currently selected {selectedRenewals.Count()} of {originalSelection.Count()} {totalLabel}", true);
                var chosen = await _input.ChooseFromMenu("Please choose from the menu", options);

                await chosen.Invoke();
            }while (!quit);
        }