Repository: kubernetes/kops (go)

Issue:
Add Cilium Ingress suport
/kind feature

**1. Describe IN DETAIL the feature/behavior/change you would like to see.**

We have now the ability to add cilium ingress support: https://docs.cilium.io/en/v1.13/network/servicemesh/ingress/
It allows users to use the cilium ingress controller instead of using nginx, haproxy etc.

As far as I know, this will also allow to use L7 proxy directly with Cilium.
Especially with Cilium 1.14, which can handle Wireguard + L7 Proxy, this becomes more interesting.

**2. Feel free to provide a design supporting your feature request.**

I'll try to provide a PR in the next few days. It should be implemented as we have done with Hubble (via CiliumIngressSpec in CiliumNetworkingSpec).

Pull request description:
Implement Cilium Ingress
Fixes #15784 

Patch:
diff --git a/k8s/crds/kops.k8s.io_clusters.yaml b/k8s/crds/kops.k8s.io_clusters.yaml
index b90efcedeb..81ab6b9524 100644
--- a/k8s/crds/kops.k8s.io_clusters.yaml
+++ b/k8s/crds/kops.k8s.io_clusters.yaml
@@ -5033,6 +5033,39 @@ spec:
                         description: 'IdentityChangeGracePeriod specifies the duration
                           to wait before using a changed identity. Default: 5s'
                         type: string
+                      ingress:
+                        description: Ingress specifies the configuration for Cilium
+                          Ingress settings.
+                        properties:
+                          defaultLoadBalancerMode:
+                            description: 'DefaultLoadBalancerMode specifies the default
+                              load balancer mode. Possible values: ''shared'' or ''dedicated''
+                              Default: dedicated'
+                            type: string
+                          enableSecretsSync:
+                            description: 'EnableSecretsSync specifies whether synchronization
+                              of secrets is enabled. Default: true'
+                            type: boolean
+                          enabled:
+                            description: Enabled specifies whether Cilium Ingress
+                              is enabled.
+                            type: boolean
+                          enforceHttps:
+                            description: 'EnforceHttps specifies whether HTTPS enforcement
+                              is enabled for Ingress traffic. Default: true'
+                            type: boolean
+                          loadBalancerAnnotationPrefixes:
+                            description: 'LoadBalancerAnnotationPrefixes specifies
+                              annotation prefixes for Load Balancer configuration.
+                              Default: "service.beta.kubernetes.io service.kubernetes.io
+                              cloud.google.com"'
+                            type: string
+                          sharedLoadBalancerServiceName:
+                            description: 'SharedLoadBalancerServiceName specifies
+                              the name of the shared load balancer service. Default:
+                              cilium-ingress'
+                            type: string
+                        type: object
                       ipam:
                         description: 'IPAM specifies the IP address allocation mode
                           to use. Possible values are "crd" and "eni". "eni" will
diff --git a/pkg/apis/kops/networking.go b/pkg/apis/kops/networking.go
index ddacc4077f..563aa339d8 100644
--- a/pkg/apis/kops/networking.go
+++ b/pkg/apis/kops/networking.go
@@ -493,6 +493,36 @@ type CiliumNetworkingSpec struct {
 
 	// EnableServiceTopology determine if cilium should use topology aware hints.
 	EnableServiceTopology bool `json:"enableServiceTopology,omitempty"`
+
+	// Ingress specifies the configuration for Cilium Ingress settings.
+	Ingress *CiliumIngressSpec `json:"ingress,omitempty"`
+}
+
+// CiliumIngressSpec configures Cilium Ingress settings.
+type CiliumIngressSpec struct {
+	// Enabled specifies whether Cilium Ingress is enabled.
+	Enabled *bool `json:"enabled,omitempty"`
+
+	// EnforceHttps specifies whether HTTPS enforcement is enabled for Ingress traffic.
+	// Default: true
+	EnforceHttps *bool `json:"enforceHttps,omitempty"`
+
+	// EnableSecretsSync specifies whether synchronization of secrets is enabled.
+	// Default: true
+	EnableSecretsSync *bool `json:"enableSecretsSync,omitempty"`
+
+	// LoadBalancerAnnotationPrefixes specifies annotation prefixes for Load Balancer configuration.
+	// Default: "service.beta.kubernetes.io service.kubernetes.io cloud.google.com"
+	LoadBalancerAnnotationPrefixes string `json:"loadBalancerAnnotationPrefixes,omitempty"`
+
+	// DefaultLoadBalancerMode specifies the default load balancer mode.
+	// Possible values: 'shared' or 'dedicated'
+	// Default: dedicated
+	DefaultLoadBalancerMode string `json:"defaultLoadBalancerMode,omitempty"`
+
+	// SharedLoadBalancerServiceName specifies the name of the shared load balancer service.
+	// Default: cilium-ingress
+	SharedLoadBalancerServiceName string `json:"sharedLoadBalancerServiceName,omitempty"`
 }
 
 // HubbleSpec configures the Hubble service on the Cilium agent.
diff --git a/pkg/apis/kops/v1alpha2/networking.go b/pkg/apis/kops/v1alpha2/networking.go
index 48e482b814..a781bf6d16 100644
--- a/pkg/apis/kops/v1alpha2/networking.go
+++ b/pkg/apis/kops/v1alpha2/networking.go
@@ -614,6 +614,36 @@ type CiliumNetworkingSpec struct {
 
 	// EnableServiceTopology determine if cilium should use topology aware hints.
 	EnableServiceTopology bool `json:"enableServiceTopology,omitempty"`
+
+	// Ingress specifies the configuration for Cilium Ingress settings.
+	Ingress *CiliumIngressSpec `json:"ingress,omitempty"`
+}
+
+// CiliumIngressSpec configures Cilium Ingress settings.
+type CiliumIngressSpec struct {
+	// Enabled specifies whether Cilium Ingress is enabled.
+	Enabled *bool `json:"enabled,omitempty"`
+
+	// EnforceHttps specifies whether HTTPS enforcement is enabled for Ingress traffic.
+	// Default: true
+	EnforceHttps *bool `json:"enforceHttps,omitempty"`
+
+	// EnableSecretsSync specifies whether synchronization of secrets is enabled.
+	// Default: true
+	EnableSecretsSync *bool `json:"enableSecretsSync,omitempty"`
+
+	// LoadBalancerAnnotationPrefixes specifies annotation prefixes for Load Balancer configuration.
+	// Default: "service.beta.kubernetes.io service.kubernetes.io cloud.google.com"
+	LoadBalancerAnnotationPrefixes string `json:"loadBalancerAnnotationPrefixes,omitempty"`
+
+	// DefaultLoadBalancerMode specifies the default load balancer mode.
+	// Possible values: 'shared' or 'dedicated'
+	// Default: dedicated
+	DefaultLoadBalancerMode string `json:"defaultLoadBalancerMode,omitempty"`
+
+	// SharedLoadBalancerServiceName specifies the name of the shared load balancer service.
+	// Default: cilium-ingress
+	SharedLoadBalancerServiceName string `json:"sharedLoadBalancerServiceName,omitempty"`
 }
 
 // HubbleSpec configures the Hubble service on the Cilium agent.
diff --git a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go
index be120b7570..8e2bf472b4 100644
--- a/pkg/apis/kops/v1alpha2/zz_generated.conversion.go
+++ b/pkg/apis/kops/v1alpha2/zz_generated.conversion.go
@@ -214,6 +214,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*CiliumIngressSpec)(nil), (*kops.CiliumIngressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec(a.(*CiliumIngressSpec), b.(*kops.CiliumIngressSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*kops.CiliumIngressSpec)(nil), (*CiliumIngressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec(a.(*kops.CiliumIngressSpec), b.(*CiliumIngressSpec), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddGeneratedConversionFunc((*ClassicNetworkingSpec)(nil), (*kops.ClassicNetworkingSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha2_ClassicNetworkingSpec_To_kops_ClassicNetworkingSpec(a.(*ClassicNetworkingSpec), b.(*kops.ClassicNetworkingSpec), scope)
 	}); err != nil {
@@ -1907,6 +1917,36 @@ func Convert_kops_CertManagerConfig_To_v1alpha2_CertManagerConfig(in *kops.CertM
 	return autoConvert_kops_CertManagerConfig_To_v1alpha2_CertManagerConfig(in, out, s)
 }
 
+func autoConvert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec(in *CiliumIngressSpec, out *kops.CiliumIngressSpec, s conversion.Scope) error {
+	out.Enabled = in.Enabled
+	out.EnforceHttps = in.EnforceHttps
+	out.EnableSecretsSync = in.EnableSecretsSync
+	out.LoadBalancerAnnotationPrefixes = in.LoadBalancerAnnotationPrefixes
+	out.DefaultLoadBalancerMode = in.DefaultLoadBalancerMode
+	out.SharedLoadBalancerServiceName = in.SharedLoadBalancerServiceName
+	return nil
+}
+
+// Convert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec is an autogenerated conversion function.
+func Convert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec(in *CiliumIngressSpec, out *kops.CiliumIngressSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec(in, out, s)
+}
+
+func autoConvert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec(in *kops.CiliumIngressSpec, out *CiliumIngressSpec, s conversion.Scope) error {
+	out.Enabled = in.Enabled
+	out.EnforceHttps = in.EnforceHttps
+	out.EnableSecretsSync = in.EnableSecretsSync
+	out.LoadBalancerAnnotationPrefixes = in.LoadBalancerAnnotationPrefixes
+	out.DefaultLoadBalancerMode = in.DefaultLoadBalancerMode
+	out.SharedLoadBalancerServiceName = in.SharedLoadBalancerServiceName
+	return nil
+}
+
+// Convert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec is an autogenerated conversion function.
+func Convert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec(in *kops.CiliumIngressSpec, out *CiliumIngressSpec, s conversion.Scope) error {
+	return autoConvert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec(in, out, s)
+}
+
 func autoConvert_v1alpha2_CiliumNetworkingSpec_To_kops_CiliumNetworkingSpec(in *CiliumNetworkingSpec, out *kops.CiliumNetworkingSpec, s conversion.Scope) error {
 	out.Registry = in.Registry
 	out.Version = in.Version
@@ -2017,6 +2057,15 @@ func autoConvert_v1alpha2_CiliumNetworkingSpec_To_kops_CiliumNetworkingSpec(in *
 	// INFO: in.CniBinPath opted out of conversion generation
 	out.DisableCNPStatusUpdates = in.DisableCNPStatusUpdates
 	out.EnableServiceTopology = in.EnableServiceTopology
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(kops.CiliumIngressSpec)
+		if err := Convert_v1alpha2_CiliumIngressSpec_To_kops_CiliumIngressSpec(*in, *out, s); err != nil {
+			return err
+		}
+	} else {
+		out.Ingress = nil
+	}
 	return nil
 }
 
@@ -2078,6 +2127,15 @@ func autoConvert_kops_CiliumNetworkingSpec_To_v1alpha2_CiliumNetworkingSpec(in *
 	}
 	out.DisableCNPStatusUpdates = in.DisableCNPStatusUpdates
 	out.EnableServiceTopology = in.EnableServiceTopology
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(CiliumIngressSpec)
+		if err := Convert_kops_CiliumIngressSpec_To_v1alpha2_CiliumIngressSpec(*in, *out, s); err != nil {
+			return err
+		}
+	} else {
+		out.Ingress = nil
+	}
 	return nil
 }
 
diff --git a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go
index b7c19dbfc9..47caff87ae 100644
--- a/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go
+++ b/pkg/apis/kops/v1alpha2/zz_generated.deepcopy.go
@@ -520,6 +520,37 @@ func (in *CertManagerConfig) DeepCopy() *CertManagerConfig {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CiliumIngressSpec) DeepCopyInto(out *CiliumIngressSpec) {
+	*out = *in
+	if in.Enabled != nil {
+		in, out := &in.Enabled, &out.Enabled
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnforceHttps != nil {
+		in, out := &in.EnforceHttps, &out.EnforceHttps
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnableSecretsSync != nil {
+		in, out := &in.EnableSecretsSync, &out.EnableSecretsSync
+		*out = new(bool)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CiliumIngressSpec.
+func (in *CiliumIngressSpec) DeepCopy() *CiliumIngressSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(CiliumIngressSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 	*out = *in
@@ -636,6 +667,11 @@ func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 		*out = new(bool)
 		**out = **in
 	}
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(CiliumIngressSpec)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/apis/kops/v1alpha3/networking.go b/pkg/apis/kops/v1alpha3/networking.go
index d240f62054..0bc057f665 100644
--- a/pkg/apis/kops/v1alpha3/networking.go
+++ b/pkg/apis/kops/v1alpha3/networking.go
@@ -456,6 +456,36 @@ type CiliumNetworkingSpec struct {
 
 	// EnableServiceTopology determine if cilium should use topology aware hints.
 	EnableServiceTopology bool `json:"enableServiceTopology,omitempty"`
+
+	// Ingress specifies the configuration for Cilium Ingress settings.
+	Ingress *CiliumIngressSpec `json:"ingress,omitempty"`
+}
+
+// CiliumIngressSpec configures Cilium Ingress settings.
+type CiliumIngressSpec struct {
+	// Enabled specifies whether Cilium Ingress is enabled.
+	Enabled *bool `json:"enabled,omitempty"`
+
+	// EnforceHttps specifies whether HTTPS enforcement is enabled for Ingress traffic.
+	// Default: true
+	EnforceHttps *bool `json:"enforceHttps,omitempty"`
+
+	// EnableSecretsSync specifies whether synchronization of secrets is enabled.
+	// Default: true
+	EnableSecretsSync *bool `json:"enableSecretsSync,omitempty"`
+
+	// LoadBalancerAnnotationPrefixes specifies annotation prefixes for Load Balancer configuration.
+	// Default: "service.beta.kubernetes.io service.kubernetes.io cloud.google.com"
+	LoadBalancerAnnotationPrefixes string `json:"loadBalancerAnnotationPrefixes,omitempty"`
+
+	// DefaultLoadBalancerMode specifies the default load balancer mode.
+	// Possible values: 'shared' or 'dedicated'
+	// Default: dedicated
+	DefaultLoadBalancerMode string `json:"defaultLoadBalancerMode,omitempty"`
+
+	// SharedLoadBalancerServiceName specifies the name of the shared load balancer service.
+	// Default: cilium-ingress
+	SharedLoadBalancerServiceName string `json:"sharedLoadBalancerServiceName,omitempty"`
 }
 
 // HubbleSpec configures the Hubble service on the Cilium agent.
diff --git a/pkg/apis/kops/v1alpha3/zz_generated.conversion.go b/pkg/apis/kops/v1alpha3/zz_generated.conversion.go
index ba769dbbc9..1189b3f96d 100644
--- a/pkg/apis/kops/v1alpha3/zz_generated.conversion.go
+++ b/pkg/apis/kops/v1alpha3/zz_generated.conversion.go
@@ -234,6 +234,16 @@ func RegisterConversions(s *runtime.Scheme) error {
 	}); err != nil {
 		return err
 	}
+	if err := s.AddGeneratedConversionFunc((*CiliumIngressSpec)(nil), (*kops.CiliumIngressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec(a.(*CiliumIngressSpec), b.(*kops.CiliumIngressSpec), scope)
+	}); err != nil {
+		return err
+	}
+	if err := s.AddGeneratedConversionFunc((*kops.CiliumIngressSpec)(nil), (*CiliumIngressSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
+		return Convert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec(a.(*kops.CiliumIngressSpec), b.(*CiliumIngressSpec), scope)
+	}); err != nil {
+		return err
+	}
 	if err := s.AddGeneratedConversionFunc((*CiliumNetworkingSpec)(nil), (*kops.CiliumNetworkingSpec)(nil), func(a, b interface{}, scope conversion.Scope) error {
 		return Convert_v1alpha3_CiliumNetworkingSpec_To_kops_CiliumNetworkingSpec(a.(*CiliumNetworkingSpec), b.(*kops.CiliumNetworkingSpec), scope)
 	}); err != nil {
@@ -2089,6 +2099,36 @@ func Convert_kops_CertManagerConfig_To_v1alpha3_CertManagerConfig(in *kops.CertM
 	return autoConvert_kops_CertManagerConfig_To_v1alpha3_CertManagerConfig(in, out, s)
 }
 
+func autoConvert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec(in *CiliumIngressSpec, out *kops.CiliumIngressSpec, s conversion.Scope) error {
+	out.Enabled = in.Enabled
+	out.EnforceHttps = in.EnforceHttps
+	out.EnableSecretsSync = in.EnableSecretsSync
+	out.LoadBalancerAnnotationPrefixes = in.LoadBalancerAnnotationPrefixes
+	out.DefaultLoadBalancerMode = in.DefaultLoadBalancerMode
+	out.SharedLoadBalancerServiceName = in.SharedLoadBalancerServiceName
+	return nil
+}
+
+// Convert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec is an autogenerated conversion function.
+func Convert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec(in *CiliumIngressSpec, out *kops.CiliumIngressSpec, s conversion.Scope) error {
+	return autoConvert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec(in, out, s)
+}
+
+func autoConvert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec(in *kops.CiliumIngressSpec, out *CiliumIngressSpec, s conversion.Scope) error {
+	out.Enabled = in.Enabled
+	out.EnforceHttps = in.EnforceHttps
+	out.EnableSecretsSync = in.EnableSecretsSync
+	out.LoadBalancerAnnotationPrefixes = in.LoadBalancerAnnotationPrefixes
+	out.DefaultLoadBalancerMode = in.DefaultLoadBalancerMode
+	out.SharedLoadBalancerServiceName = in.SharedLoadBalancerServiceName
+	return nil
+}
+
+// Convert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec is an autogenerated conversion function.
+func Convert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec(in *kops.CiliumIngressSpec, out *CiliumIngressSpec, s conversion.Scope) error {
+	return autoConvert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec(in, out, s)
+}
+
 func autoConvert_v1alpha3_CiliumNetworkingSpec_To_kops_CiliumNetworkingSpec(in *CiliumNetworkingSpec, out *kops.CiliumNetworkingSpec, s conversion.Scope) error {
 	out.Registry = in.Registry
 	out.Version = in.Version
@@ -2147,6 +2187,15 @@ func autoConvert_v1alpha3_CiliumNetworkingSpec_To_kops_CiliumNetworkingSpec(in *
 	}
 	out.DisableCNPStatusUpdates = in.DisableCNPStatusUpdates
 	out.EnableServiceTopology = in.EnableServiceTopology
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(kops.CiliumIngressSpec)
+		if err := Convert_v1alpha3_CiliumIngressSpec_To_kops_CiliumIngressSpec(*in, *out, s); err != nil {
+			return err
+		}
+	} else {
+		out.Ingress = nil
+	}
 	return nil
 }
 
@@ -2213,6 +2262,15 @@ func autoConvert_kops_CiliumNetworkingSpec_To_v1alpha3_CiliumNetworkingSpec(in *
 	}
 	out.DisableCNPStatusUpdates = in.DisableCNPStatusUpdates
 	out.EnableServiceTopology = in.EnableServiceTopology
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(CiliumIngressSpec)
+		if err := Convert_kops_CiliumIngressSpec_To_v1alpha3_CiliumIngressSpec(*in, *out, s); err != nil {
+			return err
+		}
+	} else {
+		out.Ingress = nil
+	}
 	return nil
 }
 
diff --git a/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go
index 45b2f6bcd2..5ec82fdf50 100644
--- a/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go
+++ b/pkg/apis/kops/v1alpha3/zz_generated.deepcopy.go
@@ -581,6 +581,37 @@ func (in *CertManagerConfig) DeepCopy() *CertManagerConfig {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CiliumIngressSpec) DeepCopyInto(out *CiliumIngressSpec) {
+	*out = *in
+	if in.Enabled != nil {
+		in, out := &in.Enabled, &out.Enabled
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnforceHttps != nil {
+		in, out := &in.EnforceHttps, &out.EnforceHttps
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnableSecretsSync != nil {
+		in, out := &in.EnableSecretsSync, &out.EnableSecretsSync
+		*out = new(bool)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CiliumIngressSpec.
+func (in *CiliumIngressSpec) DeepCopy() *CiliumIngressSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(CiliumIngressSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 	*out = *in
@@ -658,6 +689,11 @@ func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 		*out = new(bool)
 		**out = **in
 	}
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(CiliumIngressSpec)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/apis/kops/validation/validation.go b/pkg/apis/kops/validation/validation.go
index d2c0ddad09..21b91e0e8d 100644
--- a/pkg/apis/kops/validation/validation.go
+++ b/pkg/apis/kops/validation/validation.go
@@ -1338,6 +1338,12 @@ func validateNetworkingCilium(cluster *kops.Cluster, v *kops.CiliumNetworkingSpe
 		}
 	}
 
+	if v.Ingress != nil && fi.ValueOf(v.Ingress.Enabled) {
+		if v.Ingress.DefaultLoadBalancerMode != "" {
+			allErrs = append(allErrs, IsValidValue(fldPath.Child("ingress", "defaultLoadBalancerMode"), &v.Ingress.DefaultLoadBalancerMode, []string{"shared", "dedicated"})...)
+		}
+	}
+
 	return allErrs
 }
 
diff --git a/pkg/apis/kops/zz_generated.deepcopy.go b/pkg/apis/kops/zz_generated.deepcopy.go
index 9dbdea4968..fc8950d970 100644
--- a/pkg/apis/kops/zz_generated.deepcopy.go
+++ b/pkg/apis/kops/zz_generated.deepcopy.go
@@ -662,6 +662,37 @@ func (in *ChannelSpec) DeepCopy() *ChannelSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *CiliumIngressSpec) DeepCopyInto(out *CiliumIngressSpec) {
+	*out = *in
+	if in.Enabled != nil {
+		in, out := &in.Enabled, &out.Enabled
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnforceHttps != nil {
+		in, out := &in.EnforceHttps, &out.EnforceHttps
+		*out = new(bool)
+		**out = **in
+	}
+	if in.EnableSecretsSync != nil {
+		in, out := &in.EnableSecretsSync, &out.EnableSecretsSync
+		*out = new(bool)
+		**out = **in
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CiliumIngressSpec.
+func (in *CiliumIngressSpec) DeepCopy() *CiliumIngressSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(CiliumIngressSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 	*out = *in
@@ -739,6 +770,11 @@ func (in *CiliumNetworkingSpec) DeepCopyInto(out *CiliumNetworkingSpec) {
 		*out = new(bool)
 		**out = **in
 	}
+	if in.Ingress != nil {
+		in, out := &in.Ingress, &out.Ingress
+		*out = new(CiliumIngressSpec)
+		(*in).DeepCopyInto(*out)
+	}
 	return
 }
 
diff --git a/pkg/model/components/cilium.go b/pkg/model/components/cilium.go
index 6af6332c11..f7b8274e56 100644
--- a/pkg/model/components/cilium.go
+++ b/pkg/model/components/cilium.go
@@ -168,5 +168,16 @@ func (b *CiliumOptionsBuilder) BuildOptions(o interface{}) error {
 		}
 	}
 
+	ingress := c.Ingress
+	if ingress != nil {
+		if ingress.Enabled == nil {
+			ingress.Enabled = fi.PtrTo(true)
+		}
+	} else {
+		c.Ingress = &kops.CiliumIngressSpec{
+			Enabled: fi.PtrTo(false),
+		}
+	}
+
 	return nil
 }
diff --git a/upup/models/cloudup/resources/addons/networking.cilium.io/k8s-1.16-v1.13.yaml.template b/upup/models/cloudup/resources/addons/networking.cilium.io/k8s-1.16-v1.13.yaml.template
index ee747b1958..9fdf97a4e0 100644
--- a/upup/models/cloudup/resources/addons/networking.cilium.io/k8s-1.16-v1.13.yaml.template
+++ b/upup/models/cloudup/resources/addons/networking.cilium.io/k8s-1.16-v1.13.yaml.template
@@ -278,6 +278,33 @@ data:
 
   enable-service-topology: "{{ .EnableServiceTopology }}"
 
+  {{ if WithDefaultBool .Ingress.Enabled false }}
+  enable-envoy-config: "true"
+  external-envoy-proxy: "false"
+  enable-ingress-controller: "true"
+  ingress-secrets-namespace: kube-system
+
+  {{ if .Ingress.EnforceHttps }}
+  enforce-ingress-https: "{{ .Ingress.EnforceHttps }}"
+  {{ end }}
+
+  {{ if .Ingress.EnableSecretsSync }}
+  enable-ingress-secrets-sync: "{{ .Ingress.EnableSecretsSync }}"
+  {{ end }}
+
+  {{ if .Ingress.SharedLoadBalancerServiceName }}
+  ingress-shared-lb-service-name: {{ .Ingress.SharedLoadBalancerServiceName }}
+  {{ end }}
+
+  {{ if .Ingress.DefaultLoadBalancerMode }}
+  ingress-default-lb-mode: {{ .Ingress.DefaultLoadBalancerMode }}
+  {{ end }}
+
+  {{ if .Ingress.LoadBalancerAnnotationPrefixes }}
+  ingress-lb-annotation-prefixes: "{{ .Ingress.LoadBalancerAnnotationPrefixes }}"
+  {{ end }}
+  {{ end }}
+
   {{ if WithDefaultBool .Hubble.Enabled false }}
   # Enable Hubble gRPC service.
   enable-hubble: "true"
@@ -583,6 +610,23 @@ rules:
   - create
   - get
   - update
+{{ if WithDefaultBool .Ingress.Enabled false }}
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  - ingressclasses
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses/status # To update ingress status with load balancer IP.
+  verbs:
+  - update
+{{ end }}
 ---
 apiVersion: rbac.authorization.k8s.io/v1
 kind: ClusterRoleBinding
@@ -609,6 +653,121 @@ subjects:
 - kind: ServiceAccount
   name: cilium-operator
   namespace: kube-system
+{{ if WithDefaultBool .Ingress.Enabled false }}
+---
+# Source: cilium/templates/cilium-agent/role.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: cilium-ingress-secrets
+  namespace: kube-system
+  labels:
+    app.kubernetes.io/part-of: cilium
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+---
+# Source: cilium/templates/cilium-agent/rolebinding.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: cilium-secrets
+  namespace: kube-system
+  labels:
+    app.kubernetes.io/part-of: cilium
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: cilium-ingress-secrets
+subjects:
+  - kind: ServiceAccount
+    name: "cilium"
+    namespace: kube-system
+---
+# Source: cilium/templates/cilium-operator/role.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: cilium-operator-ingress-secrets
+  namespace: kube-system
+  labels:
+    app.kubernetes.io/part-of: cilium
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - create
+  - delete
+  - update
+  - patch
+---
+# Source: cilium/templates/cilium-operator/rolebinding.yaml
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: cilium-operator-ingress-secrets
+  namespace: kube-system
+  labels:
+    app.kubernetes.io/part-of: cilium
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: cilium-operator-ingress-secrets
+subjects:
+  - kind: ServiceAccount
+    name: "cilium-operator"
+    namespace: kube-system
+---
+# Source: cilium/templates/cilium-ingress-class.yaml
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  name: cilium
+spec:
+  controller: cilium.io/ingress-controller
+{{ if or (eq .Ingress.DefaultLoadBalancerMode "shared") (not .Ingress.DefaultLoadBalancerMode) }}
+---
+# Source: cilium/templates/cilium-ingress-service.yaml
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ .Ingress.SharedLoadBalancerServiceName }}
+  namespace: kube-system
+  labels:
+    cilium.io/ingress: "true"
+spec:
+  ports:
+  - name: http
+    port: 80
+    protocol: TCP
+    nodePort:
+  - name: https
+    port: 443
+    protocol: TCP
+    nodePort:
+  type: LoadBalancer
+---
+# Source: cilium/templates/cilium-ingress-service.yaml
+apiVersion: v1
+kind: Endpoints
+metadata:
+  name: {{ .Ingress.SharedLoadBalancerServiceName }}
+  namespace: kube-system
+subsets:
+- addresses:
+  - ip: "192.192.192.192"
+  ports:
+  - port: 9999
+{{ end }}
+{{ end }}
 {{ if WithDefaultBool .Hubble.Enabled false }}
 ---
 # Source: cilium/templates/hubble-relay-service.yaml


Test patch:
diff --git a/pkg/apis/kops/validation/validation_test.go b/pkg/apis/kops/validation/validation_test.go
index af9f6a8a5d..4acb77373e 100644
--- a/pkg/apis/kops/validation/validation_test.go
+++ b/pkg/apis/kops/validation/validation_test.go
@@ -961,6 +961,25 @@ func Test_Validate_Cilium(t *testing.T) {
 			},
 			ExpectedErrors: []string{"Forbidden::cilium.hubble.enabled"},
 		},
+		{
+			Cilium: kops.CiliumNetworkingSpec{
+				Version: "v1.13.5",
+				Ingress: &kops.CiliumIngressSpec{
+					Enabled:                 fi.PtrTo(true),
+					DefaultLoadBalancerMode: "bad-value",
+				},
+			},
+			ExpectedErrors: []string{"Unsupported value::cilium.ingress.defaultLoadBalancerMode"},
+		},
+		{
+			Cilium: kops.CiliumNetworkingSpec{
+				Version: "v1.13.5",
+				Ingress: &kops.CiliumIngressSpec{
+					Enabled:                 fi.PtrTo(true),
+					DefaultLoadBalancerMode: "dedicated",
+				},
+			},
+		},
 		{
 			Cilium: kops.CiliumNetworkingSpec{
 				Version: "v1.13.5",
diff --git a/tests/integration/update_cluster/minimal-ipv6-cilium/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/minimal-ipv6-cilium/data/aws_s3_object_cluster-completed.spec_content
index 3b29adebbc..92406d3cbc 100644
--- a/tests/integration/update_cluster/minimal-ipv6-cilium/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/minimal-ipv6-cilium/data/aws_s3_object_cluster-completed.spec_content
@@ -218,6 +218,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: kubernetes
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/minimal-warmpool/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/minimal-warmpool/data/aws_s3_object_cluster-completed.spec_content
index 66de8bca22..599d2ab743 100644
--- a/tests/integration/update_cluster/minimal-warmpool/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/minimal-warmpool/data/aws_s3_object_cluster-completed.spec_content
@@ -210,6 +210,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: kubernetes
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content
index 96be8120ae..33772d3893 100644
--- a/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/minimal_scaleway/data/aws_s3_object_cluster-completed.spec_content
@@ -191,6 +191,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: kubernetes
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/privatecilium-eni/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/privatecilium-eni/data/aws_s3_object_cluster-completed.spec_content
index f2eac74e19..dec90d094e 100644
--- a/tests/integration/update_cluster/privatecilium-eni/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/privatecilium-eni/data/aws_s3_object_cluster-completed.spec_content
@@ -212,6 +212,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: eni
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/privatecilium/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/privatecilium/data/aws_s3_object_cluster-completed.spec_content
index 6dd8aba8af..bf5024fe76 100644
--- a/tests/integration/update_cluster/privatecilium/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/privatecilium/data/aws_s3_object_cluster-completed.spec_content
@@ -216,6 +216,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: kubernetes
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_cluster-completed.spec_content
index a8c6786757..3bcf34deea 100644
--- a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_cluster-completed.spec_content
@@ -216,6 +216,13 @@ spec:
         enabled: true
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        defaultLoadBalancerMode: dedicated
+        enableSecretsSync: false
+        enabled: true
+        enforceHttps: false
+        loadBalancerAnnotationPrefixes: service.alpha.kubernetes.io
+        sharedLoadBalancerServiceName: private-ingress
       ipam: kubernetes
       memoryRequest: 128Mi
       monitorAggregation: medium
diff --git a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-bootstrap_content b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-bootstrap_content
index 29746c1dd3..8270f07a17 100644
--- a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-bootstrap_content
+++ b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-bootstrap_content
@@ -162,7 +162,7 @@ spec:
     version: 9.99.0
   - id: k8s-1.16
     manifest: networking.cilium.io/k8s-1.16-v1.13.yaml
-    manifestHash: 41a6598e0f382e210ad03cb13b8f247161399691248fb677f43f78ad25487a3a
+    manifestHash: ba5c764f4ddeb058c0dc7fd9287d445a6a3e8f186dbac9d63daf56770d81c24c
     name: networking.cilium.io
     needsPKI: true
     needsRollingUpdate: all
diff --git a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-networking.cilium.io-k8s-1.16_content b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-networking.cilium.io-k8s-1.16_content
index 966a97901b..15d23aab41 100644
--- a/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-networking.cilium.io-k8s-1.16_content
+++ b/tests/integration/update_cluster/privatecilium2/data/aws_s3_object_privatecilium.example.com-addons-networking.cilium.io-k8s-1.16_content
@@ -58,7 +58,10 @@ data:
   disable-endpoint-crd: "false"
   enable-bpf-masquerade: "false"
   enable-endpoint-health-checking: "true"
+  enable-envoy-config: "true"
   enable-hubble: "true"
+  enable-ingress-controller: "true"
+  enable-ingress-secrets-sync: "false"
   enable-ipv4: "true"
   enable-ipv4-masquerade: "true"
   enable-ipv6: "false"
@@ -68,6 +71,8 @@ data:
   enable-remote-node-identity: "true"
   enable-service-topology: "false"
   enable-unreachable-routes: "false"
+  enforce-ingress-https: "false"
+  external-envoy-proxy: "false"
   hubble-disable-tls: "false"
   hubble-listen-address: :4244
   hubble-socket-path: /var/run/cilium/hubble.sock
@@ -76,6 +81,10 @@ data:
   hubble-tls-key-file: /var/lib/cilium/tls/hubble/tls.key
   identity-allocation-mode: crd
   identity-change-grace-period: 5s
+  ingress-default-lb-mode: dedicated
+  ingress-lb-annotation-prefixes: service.alpha.kubernetes.io
+  ingress-secrets-namespace: kube-system
+  ingress-shared-lb-service-name: private-ingress
   install-iptables-rules: "true"
   ipam: kubernetes
   kube-proxy-replacement: partial
@@ -388,6 +397,21 @@ rules:
   - create
   - get
   - update
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses
+  - ingressclasses
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - networking.k8s.io
+  resources:
+  - ingresses/status
+  verbs:
+  - update
 
 ---
 
@@ -431,6 +455,111 @@ subjects:
 
 ---
 
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  labels:
+    addon.kops.k8s.io/name: networking.cilium.io
+    app.kubernetes.io/managed-by: kops
+    app.kubernetes.io/part-of: cilium
+    role.kubernetes.io/networking: "1"
+  name: cilium-ingress-secrets
+  namespace: kube-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - get
+  - list
+  - watch
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  creationTimestamp: null
+  labels:
+    addon.kops.k8s.io/name: networking.cilium.io
+    app.kubernetes.io/managed-by: kops
+    app.kubernetes.io/part-of: cilium
+    role.kubernetes.io/networking: "1"
+  name: cilium-secrets
+  namespace: kube-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: cilium-ingress-secrets
+subjects:
+- kind: ServiceAccount
+  name: cilium
+  namespace: kube-system
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  creationTimestamp: null
+  labels:
+    addon.kops.k8s.io/name: networking.cilium.io
+    app.kubernetes.io/managed-by: kops
+    app.kubernetes.io/part-of: cilium
+    role.kubernetes.io/networking: "1"
+  name: cilium-operator-ingress-secrets
+  namespace: kube-system
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - secrets
+  verbs:
+  - create
+  - delete
+  - update
+  - patch
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  creationTimestamp: null
+  labels:
+    addon.kops.k8s.io/name: networking.cilium.io
+    app.kubernetes.io/managed-by: kops
+    app.kubernetes.io/part-of: cilium
+    role.kubernetes.io/networking: "1"
+  name: cilium-operator-ingress-secrets
+  namespace: kube-system
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: cilium-operator-ingress-secrets
+subjects:
+- kind: ServiceAccount
+  name: cilium-operator
+  namespace: kube-system
+
+---
+
+apiVersion: networking.k8s.io/v1
+kind: IngressClass
+metadata:
+  creationTimestamp: null
+  labels:
+    addon.kops.k8s.io/name: networking.cilium.io
+    app.kubernetes.io/managed-by: kops
+    role.kubernetes.io/networking: "1"
+  name: cilium
+spec:
+  controller: cilium.io/ingress-controller
+
+---
+
 apiVersion: v1
 kind: Service
 metadata:
diff --git a/tests/integration/update_cluster/privatecilium2/in-v1alpha2.yaml b/tests/integration/update_cluster/privatecilium2/in-v1alpha2.yaml
index 1646cfc767..925101fdf2 100644
--- a/tests/integration/update_cluster/privatecilium2/in-v1alpha2.yaml
+++ b/tests/integration/update_cluster/privatecilium2/in-v1alpha2.yaml
@@ -30,6 +30,13 @@ spec:
     cilium:
       clusterName: privatecilium.example.com
       clusterID: 253
+      ingress:
+        enabled: true
+        defaultLoadBalancerMode: dedicated
+        enableSecretsSync: false
+        enforceHttps: false
+        loadBalancerAnnotationPrefixes: service.alpha.kubernetes.io
+        sharedLoadBalancerServiceName: private-ingress
       hubble:
         enabled: true
   nonMasqueradeCIDR: 100.64.0.0/10
diff --git a/tests/integration/update_cluster/privateciliumadvanced/data/aws_s3_object_cluster-completed.spec_content b/tests/integration/update_cluster/privateciliumadvanced/data/aws_s3_object_cluster-completed.spec_content
index 92f8802b52..6cdad8fed9 100644
--- a/tests/integration/update_cluster/privateciliumadvanced/data/aws_s3_object_cluster-completed.spec_content
+++ b/tests/integration/update_cluster/privateciliumadvanced/data/aws_s3_object_cluster-completed.spec_content
@@ -224,6 +224,8 @@ spec:
         enabled: false
       identityAllocationMode: crd
       identityChangeGracePeriod: 5s
+      ingress:
+        enabled: false
       ipam: eni
       memoryRequest: 128Mi
       monitorAggregation: medium