示例#1
0
 /// <summary>
 /// Create a new binding
 /// </summary>
 /// <param name="site"></param>
 /// <param name="host"></param>
 /// <param name="flags"></param>
 /// <param name="thumbprint"></param>
 /// <param name="store"></param>
 /// <param name="port"></param>
 /// <param name="IP"></param>
 private void AddBinding(TSite site, BindingOptions options)
 {
     options = options.WithFlags(CheckFlags(true, options.Host, options.Flags));
     _log.Information(true, "Adding new https binding {binding}", options.Binding);
     _client.AddBinding(site, options);
 }
示例#2
0
        /// <summary>
        /// Create or update a single binding in a single site
        /// </summary>
        /// <param name="site"></param>
        /// <param name="host"></param>
        /// <param name="flags"></param>
        /// <param name="thumbprint"></param>
        /// <param name="store"></param>
        /// <param name="port"></param>
        /// <param name="ipAddress"></param>
        /// <param name="fuzzy"></param>
        private string AddOrUpdateBindings(TBinding[] allBindings, TSite site, BindingOptions bindingOptions)
        {
            // Get all bindings which could map to the host
            var matchingBindings = site.Bindings.
                                   Select(x => new { binding = x, fit = Fits(x.Host, bindingOptions.Host, bindingOptions.Flags) }).
                                   Where(x => x.fit > 0).
                                   OrderByDescending(x => x.fit).
                                   ToList();

            var httpsMatches = matchingBindings.Where(x => x.binding.Protocol == "https");
            var httpMatches  = matchingBindings.Where(x => x.binding.Protocol == "http");

            // Existing https binding for exactly the domain we are looking for, will be
            // updated to use the new ACME certificate
            var perfectHttpsMatches = httpsMatches.Where(x => x.fit == 100);

            if (perfectHttpsMatches.Any())
            {
                foreach (var perfectMatch in perfectHttpsMatches)
                {
                    // The return value of UpdateFlags doesn't have to be checked here because
                    // we have a perfect match, e.g. there is always a host name and thus
                    // no risk when turning on the SNI flag
                    UpdateExistingBindingFlags(bindingOptions.Flags, perfectMatch.binding, allBindings, out SSLFlags updateFlags);
                    var updateOptions = bindingOptions.WithFlags(updateFlags);
                    UpdateBinding(site, perfectMatch.binding, updateOptions);
                }
                return(bindingOptions.Host);
            }

            // If we find a http-binding for the domain, a corresponding https binding
            // is set up to match incoming secure traffic
            var perfectHttpMatches = httpMatches.Where(x => x.fit == 100);

            if (perfectHttpMatches.Any())
            {
                if (AllowAdd(bindingOptions, allBindings))
                {
                    AddBinding(site, bindingOptions);
                    return(bindingOptions.Host);
                }
            }

            // Allow partial matching. Doesn't work for IIS CCS.
            if (!bindingOptions.Flags.HasFlag(SSLFlags.CentralSSL))
            {
                httpsMatches = httpsMatches.Except(perfectHttpsMatches);
                httpMatches  = httpMatches.Except(perfectHttpMatches);

                // There are no perfect matches for the domain, so at this point we start
                // to look at wildcard and/or default bindings binding. Since they are
                // order by 'best fit' we look at the first one.
                if (httpsMatches.Any())
                {
                    foreach (var match in httpsMatches)
                    {
                        if (UpdateExistingBindingFlags(bindingOptions.Flags, match.binding, allBindings, out SSLFlags updateFlags))
                        {
                            var updateOptions = bindingOptions.WithFlags(updateFlags);
                            UpdateBinding(site, match.binding, updateOptions);
                            return(match.binding.Host);
                        }
                    }
                }

                // Nothing on https, then start to look at http
                if (httpMatches.Any())
                {
                    var bestMatch  = httpMatches.First();
                    var addOptions = bindingOptions.WithHost(bestMatch.binding.Host);
                    if (AllowAdd(addOptions, allBindings))
                    {
                        AddBinding(site, addOptions);
                        return(bestMatch.binding.Host);
                    }
                }
            }


            // At this point we haven't even found a partial match for our hostname
            // so as the ultimate step we create new https binding
            if (AllowAdd(bindingOptions, allBindings))
            {
                AddBinding(site, bindingOptions);
                return(bindingOptions.Host);
            }
            return(null);
        }
示例#3
0
        /// <summary>
        /// Create or update a single binding in a single site
        /// </summary>
        /// <param name="site"></param>
        /// <param name="host"></param>
        /// <param name="flags"></param>
        /// <param name="thumbprint"></param>
        /// <param name="store"></param>
        /// <param name="port"></param>
        /// <param name="ipAddress"></param>
        /// <param name="fuzzy"></param>
        private (string?, bool) AddOrUpdateBindings(TBinding[] allBindings, TSite site, BindingOptions bindingOptions)
        {
            if (bindingOptions.Host == null)
            {
                throw new InvalidOperationException("bindingOptions.Host is null");
            }

            // Require IIS manager to commit
            var commitRequired = false;

            // Get all bindings which could map to the host
            var matchingBindings = site.Bindings.
                                   Select(x => new { binding = x, fit = Fits(x.Host, bindingOptions.Host, bindingOptions.Flags) }).
                                   Where(x => x.fit > 0).
                                   OrderByDescending(x => x.fit).
                                   ToList();

            // If there are any bindings
            if (matchingBindings.Any())
            {
                var bestMatch   = matchingBindings.First();
                var bestMatches = matchingBindings.Where(x => x.binding.Host == bestMatch.binding.Host);
                if (bestMatch.fit == 100 || !bindingOptions.Flags.HasFlag(SSLFlags.CentralSsl))
                {
                    // All existing https bindings
                    var existing = bestMatches.
                                   Where(x => x.binding.Protocol == "https").
                                   Select(x => x.binding.BindingInformation.ToLower()).
                                   ToList();

                    foreach (var match in bestMatches)
                    {
                        var isHttps = match.binding.Protocol == "https";
                        if (isHttps)
                        {
                            if (UpdateExistingBindingFlags(bindingOptions.Flags, match.binding, allBindings, out var updateFlags))
                            {
                                var updateOptions = bindingOptions.WithFlags(updateFlags);
                                commitRequired = UpdateBinding(site, match.binding, updateOptions);
                            }
                        }
                        else
                        {
                            var addOptions = bindingOptions.WithHost(match.binding.Host);
                            // The existance of an HTTP binding with a specific IP overrules
                            // the default IP.
                            if (addOptions.IP == IISClient.DefaultBindingIp &&
                                match.binding.IP != IISClient.DefaultBindingIp &&
                                !string.IsNullOrEmpty(match.binding.IP))
                            {
                                addOptions = addOptions.WithIP(match.binding.IP);
                            }

                            var binding = addOptions.Binding;
                            if (!existing.Contains(binding.ToLower()) && AllowAdd(addOptions, allBindings))
                            {
                                AddBinding(site, addOptions);
                                existing.Add(binding);
                                commitRequired = true;
                            }
                        }
                    }
                    return(bestMatch.binding.Host, commitRequired);
                }
            }

            // At this point we haven't even found a partial match for our hostname
            // so as the ultimate step we create new https binding
            if (AllowAdd(bindingOptions, allBindings))
            {
                AddBinding(site, bindingOptions);
                commitRequired = true;
                return(bindingOptions.Host, commitRequired);
            }

            // We haven't been able to do anything
            return(null, commitRequired);
        }
示例#4
0
        /// <summary>
        /// Update existing bindng
        /// </summary>
        /// <param name="site"></param>
        /// <param name="existingBinding"></param>
        /// <param name="flags"></param>
        /// <param name="thumbprint"></param>
        /// <param name="store"></param>
        private void UpdateBinding(Site site, Binding existingBinding, BindingOptions options)
        {
            // Check flags
            options = options.WithFlags(CheckFlags(existingBinding.Host, options.Flags));

            // IIS 7.x is very picky about accessing the sslFlags attribute
            var currentFlags = existingBinding.SSLFlags();

            if ((currentFlags & ~SSLFlags.SNI) == (options.Flags & ~SSLFlags.SNI) && // Don't care about SNI status
                ((options.Store == null && existingBinding.CertificateStoreName == null) ||
                 StructuralComparisons.StructuralEqualityComparer.Equals(existingBinding.CertificateHash, options.Thumbprint) &&
                 string.Equals(existingBinding.CertificateStoreName, options.Store, StringComparison.InvariantCultureIgnoreCase)))
            {
                _log.Verbose("No binding update needed");
            }
            else
            {
                _log.Information(true, "Updating existing https binding {host}:{port}", existingBinding.Host, existingBinding.EndPoint.Port);

                // Replace instead of change binding because of #371
                var handled = new[] {
                    "protocol",
                    "bindingInformation",
                    "sslFlags",
                    "certificateStoreName",
                    "certificateHash"
                };
                var replacement = site.Bindings.CreateElement("binding");
                replacement.Protocol             = existingBinding.Protocol;
                replacement.BindingInformation   = existingBinding.BindingInformation;
                replacement.CertificateStoreName = options.Store;
                replacement.CertificateHash      = options.Thumbprint;
                foreach (var attr in existingBinding.Attributes)
                {
                    try
                    {
                        if (!handled.Contains(attr.Name) && attr.Value != null)
                        {
                            replacement.SetAttributeValue(attr.Name, attr.Value);
                        }
                    }
                    catch (Exception ex)
                    {
                        _log.Warning("Unable to set attribute {name} on new binding: {ex}", attr.Name, ex.Message);
                    }
                }

                // If current binding has SNI, the updated version
                // will also have that flag set, regardless
                // of whether or not it was requested by the caller.
                // Callers should not generally request SNI unless
                // required for the binding, e.g. for TLS-SNI validation.
                // Otherwise let the admin be in control.
                if (currentFlags.HasFlag(SSLFlags.SNI))
                {
                    options = options.WithFlags(options.Flags | SSLFlags.SNI);
                }
                if (options.Flags > 0)
                {
                    replacement.SetAttributeValue("sslFlags", options.Flags);
                }
                site.Bindings.Remove(existingBinding);
                site.Bindings.Add(replacement);
            }
        }
示例#5
0
        /// <summary>
        /// Create or update a single binding in a single site
        /// </summary>
        /// <param name="site"></param>
        /// <param name="host"></param>
        /// <param name="flags"></param>
        /// <param name="thumbprint"></param>
        /// <param name="store"></param>
        /// <param name="port"></param>
        /// <param name="ipAddress"></param>
        /// <param name="fuzzy"></param>
        private string AddOrUpdateBindings(Binding[] allBindings, IISSiteWrapper site, BindingOptions bindingOptions, bool fuzzy)
        {
            // Get all bindings which could map to the host
            var matchingBindings = site.Site.Bindings.
                                   Select(x => new { binding = x, fit = Fits(x.Host, bindingOptions.Host, bindingOptions.Flags) }).
                                   Where(x => x.fit > 0).
                                   OrderByDescending(x => x.fit).
                                   ToList();

            var httpsMatches = matchingBindings.Where(x => x.binding.Protocol == "https");
            var httpMatches  = matchingBindings.Where(x => x.binding.Protocol == "http");

            // Existing https binding for exactly the domain we are looking for, will be
            // updated to use the new ACME certificate
            var perfectHttpsMatches = httpsMatches.Where(x => x.fit == 100);

            if (perfectHttpsMatches.Any())
            {
                foreach (var perfectMatch in perfectHttpsMatches)
                {
                    var updateOptions = bindingOptions.WithFlags(UpdateFlags(bindingOptions.Flags, perfectMatch.binding, allBindings));
                    UpdateBinding(site.Site, perfectMatch.binding, updateOptions);
                }
                return(bindingOptions.Host);
            }

            // If we find a http-binding for the domain, a corresponding https binding
            // is set up to match incoming secure traffic
            var perfectHttpMatches = httpMatches.Where(x => x.fit == 100);

            if (perfectHttpMatches.Any())
            {
                if (AllowAdd(bindingOptions, allBindings))
                {
                    AddBinding(site, bindingOptions);
                    return(bindingOptions.Host);
                }
            }

            // Allow partial matching. Doesn't work for IIS CCS. Also
            // should not be used for TLS-SNI validation.
            if (bindingOptions.Host.StartsWith("*.") || fuzzy)
            {
                httpsMatches = httpsMatches.Except(perfectHttpsMatches);
                httpMatches  = httpMatches.Except(perfectHttpMatches);

                // There are no perfect matches for the domain, so at this point we start
                // to look at wildcard and/or default bindings binding. Since they are
                // order by 'best fit' we look at the first one.
                if (httpsMatches.Any())
                {
                    var bestMatch     = httpsMatches.First();
                    var updateFlags   = UpdateFlags(bindingOptions.Flags, bestMatch.binding, allBindings);
                    var updateOptions = bindingOptions.WithFlags(updateFlags);
                    UpdateBinding(site.Site, bestMatch.binding, updateOptions);
                    return(bestMatch.binding.Host);
                }

                // Nothing on https, then start to look at http
                if (httpMatches.Any())
                {
                    var bestMatch  = httpMatches.First();
                    var addOptions = bindingOptions.WithHost(bestMatch.binding.Host);
                    if (AllowAdd(addOptions, allBindings))
                    {
                        AddBinding(site, addOptions);
                        return(bestMatch.binding.Host);
                    }
                }
            }


            // At this point we haven't even found a partial match for our hostname
            // so as the ultimate step we create new https binding
            if (AllowAdd(bindingOptions, allBindings))
            {
                AddBinding(site, bindingOptions);
                return(bindingOptions.Host);
            }
            return(null);
        }
 /// <summary>
 /// Create a new binding
 /// </summary>
 /// <param name="site"></param>
 /// <param name="host"></param>
 /// <param name="flags"></param>
 /// <param name="thumbprint"></param>
 /// <param name="store"></param>
 /// <param name="port"></param>
 /// <param name="IP"></param>
 private IIISBinding AddBinding(TSite site, BindingOptions options)
 {
     options = options.WithFlags(CheckFlags(true, options.Host, options.Flags));
     _log.Information(LogType.All, "Adding new https binding {binding}", options.Binding);
     return(_client.AddBinding(site, options));
 }