Repository: zalando-incubator/kube-ingress-aws-controller
Pull request #398 (2021-02-01 05:21:50)

Bump github.com/google/uuid from 1.1.3 to 1.2.0
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.1.3 to 1.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/google/uuid/releases">github.com/google/uuid's releases</a>.</em></p>
<blockquote>
<h2>Add NewString()</h2>
<p>This release introduces the NewString() function which is the equivalent of uuid.New().String().</p>
<h2>Syntactic cleanup</h2>
<p>There are no code changes.  A missing period was add to a godoc comment and the linter was told to not complain that the results of hash.Write() are ignored (the function cannot fail)</p>
<h2>Further error optimizations</h2>
<p>Do not allocate memory for errors (it is only one word)</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/google/uuid/commit/bfb86fa49a73e4194d93bea18d7acfe3694438ce"><code>bfb86fa</code></a> Add NewString Func (<a href="https://github-redirect.dependabot.com/google/uuid/issues/73">#73</a>)</li>
<li><a href="https://github.com/google/uuid/commit/b5d0d36c983b13c35a93a356a9d2c14da0a1c3ab"><code>b5d0d36</code></a> hash.go hash error covered and linter error fixed (<a href="https://github-redirect.dependabot.com/google/uuid/issues/71">#71</a>)</li>
<li><a href="https://github.com/google/uuid/commit/93777840becdb80e2f5e915fbd3ea9becd5f9718"><code>9377784</code></a> Update sql.go (<a href="https://github-redirect.dependabot.com/google/uuid/issues/72">#72</a>)</li>
<li><a href="https://github.com/google/uuid/commit/85223faccf99409308cd169d9608753a95d21083"><code>85223fa</code></a> Reduce custom error allocation (<a href="https://github-redirect.dependabot.com/google/uuid/issues/70">#70</a>)</li>
<li>See full diff in <a href="https://github.com/google/uuid/compare/v1.1.3...v1.2.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/google/uuid&package-manager=go_modules&previous-version=1.1.3&new-version=1.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/README.md b/README.md
index 3eba71c..9c0a8dc 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ This information is used to manage AWS resources for each ingress objects of the
 - Support for both [Application Load Balancers][alb] and [Network Load Balancers][nlb].
 - Support for internet-facing and internal load balancers
 - Support for ignoring cluster-internal ingress, that only have `--cluster-local-domain=cluster.local` domains.
+- Support for denying traffic for internal domains.
 - Support for multiple Auto Scaling Groups
 - Support for instances that are not part of Auto Scaling Group
 - Support for SSLPolicy, set default and per ingress
@@ -357,7 +358,49 @@ spec:
 ```
 
 If you pass `--cluster-local-domain=".cluster.local"`, you can change
-what domain is considered cluster internal.
+what domain is considered cluster internal. If you're using the [deny
+internal traffic](#deny-traffic-for-internal-domains) feature, you might
+want to sync this configuration with the `--internal-domains` one.
+
+#### Deny traffic for internal domains
+
+Since `>=v0.11.18` the controller supports the flag
+`--deny-internal-domains`. It's a boolean config item that when enabled
+configures the ALBs' cloudformation templates with a
+[`AWS::ElasticLoadBalancingV2::ListenerRule`][ListenerRule] resource.
+This rule will be configured with the [condition][HostHeaderConfig]
+values from the `--internal-domains` flag and the
+[action `fixedresponseconfig`][FixedResponse] with the respective response
+`--deny-internal-domains-response` flags. This feature is not enabled by
+default. The following are the default values to its config flags:
+
+- `internal-domains`: `*.cluster.local`
+- `deny-internal-domains`: `false`
+- `deny-internal-domains-response`: `Unauthorized`
+- `deny-internal-domains-response-content-type`: `text/plain`
+- `deny-internal-domains-response-status-code`: `401`
+
+Note that `--internal-domains` differs from `--cluster-local-domain`,
+which is used exclusively to [avoid load balancers creation for the
+cluster internal
+domain](#omit-to-create-a-load-balancer-for-cluster-internal-domains).
+The `--internal-domains` flag can be set multiple times and accept AWS'
+wildcard characters. Check the AWS' docs on the [Host Header
+config][HostHeaderConfig] for more details.
+
+This feature is not supported by NLBs.
+
+Example:
+
+Running the controller with `--deny-internal-domains=true` and
+`--internal-domains=*.cluster.local` will generate a rule in the ALB
+that matches any request to domains ending in `.cluster.local` and answer
+the request with an [HTTP 401 Unauthorized][401].
+
+[ListenerRule]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenerrule.html
+[HostHeaderConfig]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-hostheaderconfig.html
+[FixedResponse]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-action.html#cfn-elasticloadbalancingv2-listenerrule-action-fixedresponseconfig
+[401]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401
 
 #### Create Load Balancer with SSL Policy
 
diff --git a/aws/adapter.go b/aws/adapter.go
index e2bd2dc..3c26f32 100644
--- a/aws/adapter.go
+++ b/aws/adapter.go
@@ -37,31 +37,36 @@ type Adapter struct {
 	iam            iamiface.IAMAPI
 	cloudformation cloudformationiface.CloudFormationAPI
 
-	manifest                   *manifest
-	healthCheckPath            string
-	healthCheckPort            uint
-	healthCheckInterval        time.Duration
-	healthCheckTimeout         time.Duration
-	targetPort                 uint
-	creationTimeout            time.Duration
-	idleConnectionTimeout      time.Duration
-	deregistrationDelayTimeout time.Duration
-	TargetedAutoScalingGroups  map[string]*autoScalingGroupDetails
-	OwnedAutoScalingGroups     map[string]*autoScalingGroupDetails
-	ec2Details                 map[string]*instanceDetails
-	singleInstances            map[string]*instanceDetails
-	obsoleteInstances          []string
-	stackTerminationProtection bool
-	stackTags                  map[string]string
-	controllerID               string
-	sslPolicy                  string
-	ipAddressType              string
-	albLogsS3Bucket            string
-	albLogsS3Prefix            string
-	httpRedirectToHTTPS        bool
-	nlbCrossZone               bool
-	nlbHTTPEnabled             bool
-	customFilter               string
+	manifest                    *manifest
+	healthCheckPath             string
+	healthCheckPort             uint
+	healthCheckInterval         time.Duration
+	healthCheckTimeout          time.Duration
+	targetPort                  uint
+	creationTimeout             time.Duration
+	idleConnectionTimeout       time.Duration
+	deregistrationDelayTimeout  time.Duration
+	TargetedAutoScalingGroups   map[string]*autoScalingGroupDetails
+	OwnedAutoScalingGroups      map[string]*autoScalingGroupDetails
+	ec2Details                  map[string]*instanceDetails
+	singleInstances             map[string]*instanceDetails
+	obsoleteInstances           []string
+	stackTerminationProtection  bool
+	stackTags                   map[string]string
+	controllerID                string
+	sslPolicy                   string
+	ipAddressType               string
+	albLogsS3Bucket             string
+	albLogsS3Prefix             string
+	httpRedirectToHTTPS         bool
+	nlbCrossZone                bool
+	nlbHTTPEnabled              bool
+	customFilter                string
+	internalDomains             []string
+	denyInternalDomains         bool
+	denyInternalRespBody        string
+	denyInternalRespContentType string
+	denyInternalRespStatusCode  int
 }
 
 type manifest struct {
@@ -365,6 +370,48 @@ func (a *Adapter) WithCustomFilter(customFilter string) *Adapter {
 	return a
 }
 
+// WithInternalDomains returns the receiver adapter after changing the
+// internal domains that will be used by the resources created by the
+// adapter.
+func (a *Adapter) WithInternalDomains(domains []string) *Adapter {
+	a.internalDomains = domains
+	return a
+}
+
+// WithDenyInternalDomains returns the receiver adapter after setting
+// the denyInternalDomains config.
+func (a *Adapter) WithDenyInternalDomains(deny bool) *Adapter {
+	a.denyInternalDomains = deny
+	return a
+}
+
+// WithInternalDomainsDenyResponse returns the receiver adapter after
+// changing the Body of the response that will be returned by the
+// resources created by the adapter when denyInternalDomains is set to
+// true.
+func (a *Adapter) WithInternalDomainsDenyResponse(body string) *Adapter {
+	a.denyInternalRespBody = body
+	return a
+}
+
+// WithInternalDomainsDenyResponseStatusCode returns the receiver
+// adapter after changing the status code of the response that will be
+// returned by the resources created by the adapter when
+// denyInternalDomains is set to true.
+func (a *Adapter) WithInternalDomainsDenyResponseStatusCode(code int) *Adapter {
+	a.denyInternalRespStatusCode = code
+	return a
+}
+
+// WithInternalDomainsDenyResponseContenType returns the receiver
+// adapter after changing the content-type of the response that will be
+// returned by the resources created by the adapter when
+// denyInternalDomains is set to true.
+func (a *Adapter) WithInternalDomainsDenyResponseContenType(contentType string) *Adapter {
+	a.denyInternalRespContentType = contentType
+	return a
+}
+
 // ClusterID returns the ClusterID tag that all resources from the same Kubernetes cluster share.
 // It's taken from the current ec2 instance.
 func (a *Adapter) ClusterID() string {
@@ -558,6 +605,13 @@ func (a *Adapter) CreateStack(certificateARNs []string, scheme, securityGroup, o
 		nlbHTTPEnabled:                    a.nlbHTTPEnabled,
 		http2:                             http2,
 		tags:                              a.stackTags,
+		internalDomains:                   a.internalDomains,
+		denyInternalDomains:               a.denyInternalDomains,
+		denyInternalDomainsResponse: denyResp{
+			body:        a.denyInternalRespBody,
+			statusCode:  a.denyInternalRespStatusCode,
+			contentType: a.denyInternalRespContentType,
+		},
 	}
 
 	return createStack(a.cloudformation, spec)
@@ -601,6 +655,13 @@ func (a *Adapter) UpdateStack(stackName string, certificateARNs map[string]time.
 		nlbHTTPEnabled:                    a.nlbHTTPEnabled,
 		http2:                             http2,
 		tags:                              a.stackTags,
+		internalDomains:                   a.internalDomains,
+		denyInternalDomains:               a.denyInternalDomains,
+		denyInternalDomainsResponse: denyResp{
+			body:        a.denyInternalRespBody,
+			statusCode:  a.denyInternalRespStatusCode,
+			contentType: a.denyInternalRespContentType,
+		},
 	}
 
 	return updateStack(a.cloudformation, spec)
diff --git a/aws/cf.go b/aws/cf.go
index 6fc4b42..0d2cdfd 100644
--- a/aws/cf.go
+++ b/aws/cf.go
@@ -147,6 +147,9 @@ type stackSpec struct {
 	nlbCrossZone                      bool
 	nlbHTTPEnabled                    bool
 	http2                             bool
+	denyInternalDomains               bool
+	denyInternalDomainsResponse       denyResp
+	internalDomains                   []string
 	tags                              map[string]string
 }
 
@@ -157,6 +160,12 @@ type healthCheck struct {
 	timeout  time.Duration
 }
 
+type denyResp struct {
+	statusCode  int
+	contentType string
+	body        string
+}
+
 func createStack(svc cloudformationiface.CloudFormationAPI, spec *stackSpec) (string, error) {
 	template, err := generateTemplate(spec)
 	if err != nil {
diff --git a/aws/cf_template.go b/aws/cf_template.go
index eb6d869..86b9324 100644
--- a/aws/cf_template.go
+++ b/aws/cf_template.go
@@ -11,6 +11,21 @@ import (
 	cloudformation "github.com/mweagle/go-cloudformation"
 )
 
+const (
+	// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-rulecondition.html#cfn-elasticloadbalancingv2-listenerrule-rulecondition-field
+	listenerRuleConditionHostField = "host-header"
+
+	// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-elasticloadbalancingv2-listenerrule-action.html#cfn-elasticloadbalancingv2-listenerrule-action-type
+	listenerRuleActionTypeFixedRes = "fixed-response"
+
+	// internalTrafficDenyRulePriority defines the [priority][0] for the
+	// rule that denies requests directed to internal domains on the HTTP
+	// and HTTPS listener.
+	//
+	// [0]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenerrule.html#cfn-elasticloadbalancingv2-listenerrule-priority
+	internalTrafficDenyRulePriority int64 = 1
+)
+
 func hashARNs(certARNs []string) []byte {
 	hash := sha256.New()
 
@@ -124,7 +139,8 @@ func generateTemplate(spec *stackSpec) (string, error) {
 			Protocol:        cloudformation.String("HTTP"),
 		})
 	} else if spec.loadbalancerType == LoadBalancerTypeApplication || spec.nlbHTTPEnabled {
-		template.AddResource("HTTPListener", &cloudformation.ElasticLoadBalancingV2Listener{
+		listenerName := "HTTPListener"
+		template.AddResource(listenerName, &cloudformation.ElasticLoadBalancingV2Listener{
 			DefaultActions: &cloudformation.ElasticLoadBalancingV2ListenerActionList{
 				{
 					Type:           cloudformation.String("forward"),
@@ -135,6 +151,17 @@ func generateTemplate(spec *stackSpec) (string, error) {
 			Port:            cloudformation.Integer(80),
 			Protocol:        cloudformation.String(protocol),
 		})
+		if spec.denyInternalDomains {
+			template.AddResource(
+				"HTTPRuleBlockInternalTraffic",
+				generateDenyInternalTrafficRule(
+					listenerName,
+					internalTrafficDenyRulePriority,
+					spec.internalDomains,
+					spec.denyInternalDomainsResponse,
+				),
+			)
+		}
 	}
 
 	if len(spec.certificateARNs) > 0 {
@@ -148,7 +175,8 @@ func generateTemplate(spec *stackSpec) (string, error) {
 		})
 
 		// Add an HTTPS Listener resource with the first certificate as the default one
-		template.AddResource("HTTPSListener", &cloudformation.ElasticLoadBalancingV2Listener{
+		listenerName := "HTTPSListener"
+		template.AddResource(listenerName, &cloudformation.ElasticLoadBalancingV2Listener{
 			DefaultActions: &cloudformation.ElasticLoadBalancingV2ListenerActionList{
 				{
 					Type:           cloudformation.String("forward"),
@@ -165,6 +193,17 @@ func generateTemplate(spec *stackSpec) (string, error) {
 			Protocol:        cloudformation.String(tlsProtocol),
 			SslPolicy:       cloudformation.Ref(parameterListenerSslPolicyParameter).String(),
 		})
+		if spec.denyInternalDomains {
+			template.AddResource(
+				"HTTPSRuleBlockInternalTraffic",
+				generateDenyInternalTrafficRule(
+					listenerName,
+					internalTrafficDenyRulePriority,
+					spec.internalDomains,
+					spec.denyInternalDomainsResponse,
+				),
+			)
+		}
 
 		// Add a ListenerCertificate resource with all of the certificates, including the default one
 		certificateList := make(cloudformation.ElasticLoadBalancingV2ListenerCertificateCertificateList, 0, len(certificateARNs))
@@ -179,8 +218,9 @@ func generateTemplate(spec *stackSpec) (string, error) {
 		resourceName := fmt.Sprintf("HTTPSListenerCertificate%x", hashARNs(certificateARNs))
 		template.AddResource(resourceName, &cloudformation.ElasticLoadBalancingV2ListenerCertificate{
 			Certificates: &certificateList,
-			ListenerArn:  cloudformation.Ref("HTTPSListener").String(),
+			ListenerArn:  cloudformation.Ref(listenerName).String(),
 		})
+
 	}
 
 	// Build up the LoadBalancerAttributes list, as there is no way to make attributes conditional in the template
@@ -277,18 +317,23 @@ func generateTemplate(spec *stackSpec) (string, error) {
 			Value: cloudformation.String(fmt.Sprintf("%d", spec.deregistrationDelayTimeoutSeconds)),
 		},
 	}
-	template.AddResource("TG", &cloudformation.ElasticLoadBalancingV2TargetGroup{
+	targetGroup := &cloudformation.ElasticLoadBalancingV2TargetGroup{
 		TargetGroupAttributes: &targetGroupAttributes,
 
 		HealthCheckIntervalSeconds: cloudformation.Ref(parameterTargetGroupHealthCheckIntervalParameter).Integer(),
-		HealthCheckTimeoutSeconds:  cloudformation.Ref(parameterTargetGroupHealthCheckTimeoutParameter).Integer(),
 		HealthCheckPath:            cloudformation.Ref(parameterTargetGroupHealthCheckPathParameter).String(),
 		HealthCheckPort:            cloudformation.Ref(parameterTargetGroupHealthCheckPortParameter).String(),
 		HealthCheckProtocol:        cloudformation.String("HTTP"),
 		Port:                       cloudformation.Ref(parameterTargetTargetPortParameter).Integer(),
 		Protocol:                   cloudformation.String(protocol),
 		VPCID:                      cloudformation.Ref(parameterTargetGroupVPCIDParameter).String(),
-	})
+	}
+
+	// custom target group healthcheck only supported when the target group protocol is != TCP
+	if protocol != "TCP" {
+		targetGroup.HealthCheckTimeoutSeconds = cloudformation.Ref(parameterTargetGroupHealthCheckTimeoutParameter).Integer()
+	}
+	template.AddResource("TG", targetGroup)
 
 	if spec.loadbalancerType == LoadBalancerTypeApplication && spec.wafWebAclId != "" {
 		if strings.HasPrefix(spec.wafWebAclId, "arn:aws:wafv2:") {
@@ -346,3 +391,31 @@ func generateTemplate(spec *stackSpec) (string, error) {
 
 	return string(stackTemplate), nil
 }
+
+func generateDenyInternalTrafficRule(listenerName string, rulePriority int64, internalDomains []string, resp denyResp) cloudformation.ElasticLoadBalancingV2ListenerRule {
+	values := cloudformation.StringList()
+	for _, domain := range internalDomains {
+		values.Literal = append(values.Literal, cloudformation.String(domain))
+	}
+
+	return cloudformation.ElasticLoadBalancingV2ListenerRule{
+		Conditions: &cloudformation.ElasticLoadBalancingV2ListenerRuleRuleConditionList{
+			cloudformation.ElasticLoadBalancingV2ListenerRuleRuleCondition{
+				Field:  cloudformation.String(listenerRuleConditionHostField),
+				Values: values,
+			},
+		},
+		Actions: &cloudformation.ElasticLoadBalancingV2ListenerRuleActionList{
+			cloudformation.ElasticLoadBalancingV2ListenerRuleAction{
+				Type: cloudformation.String(listenerRuleActionTypeFixedRes),
+				FixedResponseConfig: &cloudformation.ElasticLoadBalancingV2ListenerRuleFixedResponseConfig{
+					ContentType: cloudformation.String(resp.contentType),
+					MessageBody: cloudformation.String(resp.body),
+					StatusCode:  cloudformation.String(fmt.Sprintf("%d", resp.statusCode)),
+				},
+			},
+		},
+		Priority:    cloudformation.Integer(rulePriority),
+		ListenerArn: cloudformation.Ref(listenerName).String(),
+	}
+}
diff --git a/controller.go b/controller.go
index 5cfe82a..f2418d9 100644
--- a/controller.go
+++ b/controller.go
@@ -74,6 +74,12 @@ var (
 	nlbCrossZone                  bool
 	nlbHTTPEnabled                bool
 	ingressAPIVersion             string
+	internalDomains               []string
+	denyInternalDomains           bool
+	denyInternalRespBody          string
+	denyInternalRespContentType   string
+	denyInternalRespStatusCode    int
+	defaultInternalDomains        = fmt.Sprintf("*%s", kubernetes.DefaultClusterLocalDomain)
 )
 
 func loadSettings() error {
@@ -148,6 +154,16 @@ func loadSettings() error {
 		Default("false").BoolVar(&nlbHTTPEnabled)
 	kingpin.Flag("ingress-api-version", "APIversion used for listing/updating ingresses.").
 		Default(kubernetes.IngressAPIVersionNetworking).EnumVar(&ingressAPIVersion, kubernetes.IngressAPIVersionNetworking, kubernetes.IngressAPIVersionExtensions)
+	kingpin.Flag("deny-internal-domains", "Sets a rule on ALB's Listeners that denies requests with the Host header as a internal domain. Domains can be set with the -internal-domains flag.").
+		Default("false").BoolVar(&denyInternalDomains)
+	kingpin.Flag("internal-domains", "Define the internal domains to be blocked when -deny-internal-domains is set to true. Set it multiple times for multiple domains. The maximum size of each name is 128 characters. The following wildcard characters are supported: * (matches 0 or more characters) and ? (matches exactly 1 character).").
+		Default(defaultInternalDomains).StringsVar(&internalDomains)
+	kingpin.Flag("deny-internal-domains-response", "Defines the response body for a request identified as to an internal domain when -deny-internal-domains is set.").
+		Default("Unauthorized").StringVar(&denyInternalRespBody)
+	kingpin.Flag("deny-internal-domains-response-content-type", "Defines the response conten-type for a request identified as to an internal domain when -deny-internal-domains is set.").
+		Default("text/plain").StringVar(&denyInternalRespContentType)
+	kingpin.Flag("deny-internal-domains-response-status-code", "Defines the response status code for a request identified as to an internal domain when -deny-internal-domains is set.").
+		Default("401").IntVar(&denyInternalRespStatusCode)
 	kingpin.Parse()
 
 	blacklistCertArnMap = make(map[string]bool)
@@ -249,7 +265,12 @@ func main() {
 		WithNLBCrossZone(nlbCrossZone).
 		WithNLBHTTPEnabled(nlbHTTPEnabled).
 		WithCustomFilter(customFilter).
-		WithStackTags(additionalStackTags)
+		WithStackTags(additionalStackTags).
+		WithInternalDomains(internalDomains).
+		WithDenyInternalDomains(denyInternalDomains).
+		WithInternalDomainsDenyResponse(denyInternalRespBody).
+		WithInternalDomainsDenyResponseStatusCode(denyInternalRespStatusCode).
+		WithInternalDomainsDenyResponseContenType(denyInternalRespContentType)
 
 	log.Debug("certs.NewCachingProvider")
 	certificatesProvider, err := certs.NewCachingProvider(
diff --git a/go.mod b/go.mod
index 3f09e0b..caa3444 100644
--- a/go.mod
+++ b/go.mod
@@ -3,18 +3,18 @@ module github.com/zalando-incubator/kube-ingress-aws-controller
 go 1.13
 
 require (
-	github.com/aws/aws-sdk-go v1.36.19
+	github.com/aws/aws-sdk-go v1.37.1
 	github.com/ghodss/yaml v1.0.0
 	github.com/go-playground/locales v0.12.1 // indirect
 	github.com/go-playground/universal-translator v0.16.0 // indirect
-	github.com/google/uuid v1.1.3
+	github.com/google/uuid v1.2.0
 	github.com/leodido/go-urn v1.1.0 // indirect
 	github.com/linki/instrumented_http v0.3.0
 	github.com/mweagle/go-cloudformation v0.0.0-20200214045450-244753d8838e
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.9.0
 	github.com/sirupsen/logrus v1.7.0
-	github.com/stretchr/testify v1.6.1
+	github.com/stretchr/testify v1.7.0
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
 	gopkg.in/go-playground/validator.v9 v9.30.0 // indirect
diff --git a/go.sum b/go.sum
index b8e3c7c..5a88a6e 100644
--- a/go.sum
+++ b/go.sum
@@ -24,8 +24,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
 github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
 github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
-github.com/aws/aws-sdk-go v1.36.19 h1:zbJZKkxeDiYxUYFjymjWxPye+qa1G2gRVyhIzZrB9zA=
-github.com/aws/aws-sdk-go v1.36.19/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
+github.com/aws/aws-sdk-go v1.37.1 h1:BTHmuN+gzhxkvU9sac2tZvaY0gV9ihbHw+KxZOecYvY=
+github.com/aws/aws-sdk-go v1.37.1/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -108,8 +108,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.3 h1:twObb+9XcuH5B9V1TBCvvvZoO6iEdILi2a76PYn5rJI=
-github.com/google/uuid v1.1.3/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
+github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -297,8 +297,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=


Test patch:
diff --git a/aws/cf_template_test.go b/aws/cf_template_test.go
index 98ed35c..33a9219 100644
--- a/aws/cf_template_test.go
+++ b/aws/cf_template_test.go
@@ -2,7 +2,10 @@ package aws
 
 import (
 	"encoding/json"
+	"fmt"
+	"sort"
 	"testing"
+	"time"
 
 	cloudformation "github.com/mweagle/go-cloudformation"
 	"github.com/stretchr/testify/assert"
@@ -10,6 +13,42 @@ import (
 )
 
 func TestGenerateTemplate(t *testing.T) {
+	internalDomains := []string{"*.ingress.cluster.local", "*.internal.domain.local"}
+	denyResp := denyResp{
+		statusCode:  404,
+		contentType: "text/plain",
+		body:        "Not Found",
+	}
+
+	validateDenyRule := func(t *testing.T, resource *cloudformation.Resource) {
+		rules, ok := resource.Properties.(*cloudformation.ElasticLoadBalancingV2ListenerRule)
+		require.True(t, ok, "couldn't convert resource to ElasticLoadBalancingV2ListenerRule")
+
+		conditions := *rules.Conditions
+		require.Equal(t, len(conditions), 1)
+		condition := conditions[0]
+
+		actions := *rules.Actions
+		require.Equal(t, len(actions), 1)
+		action := actions[0]
+
+		require.Equal(t, internalTrafficDenyRulePriority, rules.Priority.Literal)
+
+		require.Equal(t, listenerRuleConditionHostField, condition.Field.Literal)
+		var conditionValues []string
+		for _, v := range condition.Values.Literal {
+			conditionValues = append(conditionValues, v.Literal)
+		}
+		require.Equal(t, len(internalDomains), len(conditionValues))
+		sort.Strings(conditionValues)
+		require.Equal(t, internalDomains, conditionValues)
+
+		require.Equal(t, listenerRuleActionTypeFixedRes, action.Type.Literal)
+		require.Equal(t, denyResp.contentType, action.FixedResponseConfig.ContentType.Literal)
+		require.Equal(t, denyResp.body, action.FixedResponseConfig.MessageBody.Literal)
+		require.Equal(t, fmt.Sprintf("%d", denyResp.statusCode), action.FixedResponseConfig.StatusCode.Literal)
+	}
+
 	for _, test := range []struct {
 		name     string
 		spec     *stackSpec
@@ -209,6 +248,99 @@ func TestGenerateTemplate(t *testing.T) {
 				require.Equal(t, &expected, props.TargetGroupAttributes)
 			},
 		},
+		{
+			name: "Does not set healthcheck timeout on NLBs",
+			spec: &stackSpec{
+				loadbalancerType: LoadBalancerTypeNetwork,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources, "TG")
+				tg, ok := template.Resources["TG"].Properties.(*cloudformation.ElasticLoadBalancingV2TargetGroup)
+				require.True(t, ok, "couldn't convert resource to ElasticLoadBalancingV2TargetGroup")
+				require.Nil(t, tg.HealthCheckTimeoutSeconds)
+			},
+		},
+		{
+			name: "Set healthcheck timeout when not NLB",
+			spec: &stackSpec{
+				loadbalancerType: LoadBalancerTypeApplication,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources, "TG")
+				tg, ok := template.Resources["TG"].Properties.(*cloudformation.ElasticLoadBalancingV2TargetGroup)
+				require.True(t, ok, "couldn't convert resource to ElasticLoadBalancingV2TargetGroup")
+				require.NotNil(t, tg.HealthCheckTimeoutSeconds)
+			},
+		},
+		{
+			name: "deny internal traffic for HTTPS correctly",
+			spec: &stackSpec{
+				loadbalancerType:    LoadBalancerTypeApplication,
+				certificateARNs:     map[string]time.Time{"domain.company.com": time.Now()},
+				httpRedirectToHTTPS: true,
+
+				denyInternalDomains:         true,
+				internalDomains:             internalDomains,
+				denyInternalDomainsResponse: denyResp,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources["HTTPSRuleBlockInternalTraffic"])
+				resource := template.Resources["HTTPSRuleBlockInternalTraffic"]
+				validateDenyRule(t, resource)
+				require.NotContains(t, template.Resources, "HTTPRuleBlockInternalTraffic")
+			},
+		},
+		{
+			name: "deny internal traffic for HTTP correctly",
+			spec: &stackSpec{
+				loadbalancerType: LoadBalancerTypeApplication,
+
+				denyInternalDomains:         true,
+				internalDomains:             internalDomains,
+				denyInternalDomainsResponse: denyResp,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources["HTTPRuleBlockInternalTraffic"])
+				resource := template.Resources["HTTPRuleBlockInternalTraffic"]
+				validateDenyRule(t, resource)
+				require.NotContains(t, template.Resources, "HTTPSRuleBlockInternalTraffic")
+			},
+		},
+		{
+			name: "deny internal traffic for HTTP/HTTPS correctly",
+			spec: &stackSpec{
+				loadbalancerType:    LoadBalancerTypeApplication,
+				certificateARNs:     map[string]time.Time{"domain.company.com": time.Now()},
+				httpRedirectToHTTPS: false,
+
+				denyInternalDomains:         true,
+				internalDomains:             internalDomains,
+				denyInternalDomainsResponse: denyResp,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotNil(t, template.Resources["HTTPRuleBlockInternalTraffic"])
+				resource := template.Resources["HTTPRuleBlockInternalTraffic"]
+				validateDenyRule(t, resource)
+
+				require.NotNil(t, template.Resources["HTTPSRuleBlockInternalTraffic"])
+				resource = template.Resources["HTTPSRuleBlockInternalTraffic"]
+				validateDenyRule(t, resource)
+			},
+		},
+		{
+			name: "Does not deny internal traffic if not required",
+			spec: &stackSpec{
+				loadbalancerType:    LoadBalancerTypeApplication,
+				certificateARNs:     map[string]time.Time{"domain.company.com": time.Now()},
+				httpRedirectToHTTPS: false,
+
+				denyInternalDomains: false,
+			},
+			validate: func(t *testing.T, template *cloudformation.Template) {
+				require.NotContains(t, template.Resources, "HTTPSRuleBlockInternalTraffic")
+				require.NotContains(t, template.Resources, "HTTPRuleBlockInternalTraffic")
+			},
+		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
 			generated, err := generateTemplate(test.spec)