/// <summary>Returns an enumerator that iterates through the resources in this response.</summary> public scg::IEnumerator <FirewallRule> GetEnumerator() => IngressRules.GetEnumerator();
/// <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); }