/// <summary>Returns an enumerator that iterates through the resources in this response.</summary>
 public scg::IEnumerator <FirewallRule> GetEnumerator() => IngressRules.GetEnumerator();
Пример #2
0
        /// <summary>
        /// Validates the options and also ensures that all <c>null</c> properties are
        /// initialized to their default values.
        /// </summary>
        /// <param name="clusterDefinition">The cluster definition.</param>
        /// <exception cref="ClusterDefinitionException">Thrown if the definition is not valid.</exception>
        public void Validate(ClusterDefinition clusterDefinition)
        {
            Covenant.Requires <ArgumentNullException>(clusterDefinition != null, nameof(clusterDefinition));

            var networkOptionsPrefix = $"{nameof(ClusterDefinition.Network)}";
            var isCloud       = clusterDefinition.Hosting.IsCloudProvider;
            var subnets       = new List <SubnetDefinition>();
            var gateway       = (IPAddress)null;
            var premiseSubnet = (NetworkCidr)null;

            // Nameservers:
            //
            // For cloud environments, we'll going to leave the nameserver list alone and possibly
            // empty, letting the specific cloud hosting manager configure the default cloud nameserver
            // when none are specified.
            //
            // For non-cloud environments, we'll set the Google Public DNS nameservers when none
            // are specified.

            Nameservers ??= new List <string>();

            if (!isCloud && (Nameservers == null || Nameservers.Count == 0))
            {
                Nameservers = new List <string> {
                    "8.8.8.8", "8.8.4.4"
                };
            }

            foreach (var nameserver in Nameservers)
            {
                if (!NetHelper.TryParseIPv4Address(nameserver, out var address))
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(ClusterDefinition.Network.Nameservers)}={nameserver}] is not a valid IPv4 address.");
                }
            }

            // Note that we don't need to check the network settings for cloud environments
            // because we'll just use ambient settings in these cases.

            if (!isCloud)
            {
                // Verify [PremiseSubnet].

                if (!NetworkCidr.TryParse(PremiseSubnet, out premiseSubnet))
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(PremiseSubnet)}={PremiseSubnet}] is not a valid IPv4 subnet.");
                }

                // Verify [Gateway]

                if (string.IsNullOrEmpty(Gateway))
                {
                    // Default to the first valid address of the cluster nodes subnet
                    // if this isn't already set.

                    Gateway = premiseSubnet.FirstUsableAddress.ToString();
                }

                if (!NetHelper.TryParseIPv4Address(Gateway, out gateway) || gateway.AddressFamily != AddressFamily.InterNetwork)
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(Gateway)}={Gateway}] is not a valid IPv4 address.");
                }

                if (!premiseSubnet.Contains(gateway))
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(Gateway)}={Gateway}] address is not within the [{networkOptionsPrefix}.{nameof(NetworkOptions.PremiseSubnet)}={PremiseSubnet}] subnet.");
                }
            }

            // Verify [PodSubnet].

            if (!NetworkCidr.TryParse(PodSubnet, out var podSubnet))
            {
                throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(PodSubnet)}={PodSubnet}] is not a valid IPv4 subnet.");
            }

            subnets.Add(new SubnetDefinition(nameof(PodSubnet), podSubnet));

            // Verify [ServiceSubnet].

            if (!NetworkCidr.TryParse(ServiceSubnet, out var serviceSubnet))
            {
                throw new ClusterDefinitionException($"[{networkOptionsPrefix}.{nameof(ServiceSubnet)}={ServiceSubnet}] is not a valid IPv4 subnet.");
            }

            subnets.Add(new SubnetDefinition(nameof(ServiceSubnet), serviceSubnet));

            // Verify that none of the major subnets conflict.

            foreach (var subnet in subnets)
            {
                foreach (var next in subnets)
                {
                    if (subnet == next)
                    {
                        continue;   // Don't test against self.
                    }

                    if (subnet.Cidr.Overlaps(next.Cidr))
                    {
                        throw new ClusterDefinitionException($"[{networkOptionsPrefix}]: Subnet conflict: [{subnet.Name}={subnet.Cidr}] and [{next.Name}={next.Cidr}] overlap.");
                    }
                }
            }

            // Rules for HTTP/HTTPS are required.
            //
            // NOTE:
            // -----
            // We're not going to allow users to specify an ingress rule for the Kubernetes
            // API server here because that mapping is special and needs to be routed only
            // to the control-plane nodes.  We're just going to delete any rule using this port.

            IngressRules ??= new List <IngressRule>();

            if (!IngressRules.Any(rule => rule.Name == "http"))
            {
                IngressRules.Add(
                    new IngressRule()
                {
                    Name         = "http",
                    Protocol     = IngressProtocol.Tcp,
                    ExternalPort = NetworkPorts.HTTP,
                    NodePort     = KubeNodePort.IstioIngressHttp,
                    TargetPort   = 8080
                });
            }

            if (!IngressRules.Any(rule => rule.Name == "https"))
            {
                IngressRules.Add(
                    new IngressRule()
                {
                    Name         = "https",
                    Protocol     = IngressProtocol.Tcp,
                    ExternalPort = NetworkPorts.HTTPS,
                    NodePort     = KubeNodePort.IstioIngressHttps,
                    TargetPort   = 8443
                });
            }

            var apiServerRules = IngressRules
                                 .Where(rule => rule.ExternalPort == NetworkPorts.KubernetesApiServer)
                                 .ToList();

            foreach (var rule in apiServerRules)
            {
                IngressRules.Remove(rule);
            }

            // Ensure that ingress rules are valid and that their names are unique.

            var ingressRuleNames = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);

            foreach (var rule in IngressRules)
            {
                rule.Validate(clusterDefinition);

                if (ingressRuleNames.Contains(rule.Name))
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}]: Ingress Rule Conflict: Multiple rules have the same name: [{rule.Name}]");
                }

                ingressRuleNames.Add(rule.Name);
            }

            // Ensure that external ports are unique.

            var externalPorts = new HashSet <int>();

            foreach (var rule in IngressRules)
            {
                if (externalPorts.Contains(rule.ExternalPort))
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}]: Ingress Rule Conflict: Multiple rules use the same external port: [{rule.ExternalPort}]");
                }

                externalPorts.Add(rule.ExternalPort);
            }

            // Verify [EgressAddressRules].

            EgressAddressRules = EgressAddressRules ?? new List <AddressRule>();

            foreach (var rule in EgressAddressRules)
            {
                rule.Validate(clusterDefinition, nameof(EgressAddressRules));
            }

            // Verify [SshAddressRules].

            ManagementAddressRules = ManagementAddressRules ?? new List <AddressRule>();

            foreach (var rule in ManagementAddressRules)
            {
                rule.Validate(clusterDefinition, nameof(ManagementAddressRules));
            }

            // Verify that the [ReservedIngressStartPort...ReservedIngressEndPort] range doesn't
            // include common reserved ports.

            var reservedPorts = new int[]
            {
                NetworkPorts.HTTP,
                NetworkPorts.HTTPS,
                NetworkPorts.KubernetesApiServer
            };

            foreach (int reservedPort in reservedPorts)
            {
                if (ReservedIngressStartPort <= reservedPort && reservedPort <= ReservedIngressEndPort)
                {
                    throw new ClusterDefinitionException($"[{networkOptionsPrefix}]: The reserved ingress port range of [{ReservedIngressStartPort}...{ReservedIngressEndPort}] cannot include the port [{reservedPort}].");
                }
            }

            AcmeOptions = AcmeOptions ?? new AcmeOptions();
            AcmeOptions.Validate(clusterDefinition);
        }