Repository: kubernetes/ingress-nginx
Pull request #7614 (2021-09-08 20:00:41)

Support cors-allow-origin with multiple origins
<!--- Provide a general summary of your changes in the Title above --->
<!--- Please don't @-mention people in PR or commit messages (do so in an additional comment). --->
Using CORS, the purpose is to allow multiple origins but only return the `origin` having access to the location block.

This way, if a client requests access and isn't in the list of allowed origins for ingress they will not receive the Access-Control* headers and it won't leak other origins that can access the resource.

## What this PR does / why we need it:
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

Previously, this was attempted in PR #7134 however, when returning multiple values this is not the intended behavior and has since been reverted.
Based off documentation from Mozilla the `Access-Control-Allow-Origin`  must always return 1 origin. See here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin

The new feature request is here: #5496 

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing functionality to change)

## Which issue/s this PR fixes
#5496

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
This was tested locally using the `kind-e2e-test` from the `Makefile` and using the `make dev-env` + deploying an echoserver with ingress. Having this, it was possible to use `curl -v` to see whether the headers were present or not. 

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] I've read the [CONTRIBUTION](https://github.com/kubernetes/ingress-nginx/blob/main/CONTRIBUTING.md) guide
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.


k8s-ci-robot: @larivierec: This issue is currently awaiting triage.

If Ingress contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
k8s-ci-robot: Welcome @larivierec! <br><br>It looks like this is your first PR to <a href='https://github.com/kubernetes/ingress-nginx'>kubernetes/ingress-nginx</a> 🎉. Please refer to our [pull request process documentation](https://git.k8s.io/community/contributors/guide/pull-requests.md) to help your PR have a smooth ride to approval. <br><br>You will be prompted by a bot to use commands during the review process. Do not be afraid to follow the prompts! It is okay to experiment. [Here is the bot commands documentation](https://go.k8s.io/bot-commands). <br><br>You can also check if kubernetes/ingress-nginx has [its own contribution guidelines](https://github.com/kubernetes/ingress-nginx/tree/master/CONTRIBUTING.md). <br><br>You may want to refer to our [testing guide](https://git.k8s.io/community/contributors/devel/sig-testing/testing.md) if you run into trouble with your tests not passing. <br><br>If you are having difficulty getting your pull request seen, please follow the [recommended escalation practices](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#why-is-my-pull-request-not-getting-reviewed). Also, for tips and tricks in the contribution process you may want to read the [Kubernetes contributor cheat sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md). We want to make sure your contribution gets all the attention it needs! <br><br>Thank you, and welcome to Kubernetes. :smiley:
k8s-ci-robot: Hi @larivierec. Thanks for your PR.

I'm waiting for a [kubernetes](https://github.com/orgs/kubernetes/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://git.k8s.io/community/community-membership.md#member) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/7614#" title="Author self-approved">larivierec</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **elvinefendi** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @elvinefendi` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["elvinefendi"]} -->
larivierec: this is still a work in progress, but will be able to track. some tests are failing
tao12345666333: Is this PR ready?
larivierec: Should be good
k8s-ci-robot: @larivierec: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-ingress-nginx-test | b0b70a96039bbc6ef6fba2d1a9d758e4fe0ec9d4 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/ingress-nginx/7614/pull-ingress-nginx-test/1435806512403451904) | `/test pull-ingress-nginx-test`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes&repo=ingress-nginx&pr=7614). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Alarivierec). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes/ingress-nginx/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
larivierec: I'll run these tests locally tomorrow thanks for review.
I'm expecting false positives but I could be wrong. 
longwuyuan: Without a XSS attack test, I am unaware of the diligence done to verify  this change. I can't myself check the changes for a few more days as I am sick, but I want to raise my concern on this. I hope we will not introduce a change which seems innocent now but becomes a vulnerability later.
jroper: What do you mean by XSS attack test? CORS and XSS are unrelated - XSS attacks are attacks where an attacker injects a script into a target site. CORS offers nothing to help prevent that, nor does a problem in CORS configuration have any impact on whether a site is vulnerable to XSS.

Perhaps you meant CSRF attack test? CORS and CSRF are related, since CORS by design undermines of CSRF protections, and a misconfiguration of CORS can possibly open a site up to more than intended, allowing unintended cross origin requests, which would be a form of CSRF attack.
longwuyuan: Yes, you are right. I was wrong to state xss. I should have said csrf. I am
sorry about that.

Thanks,
; Long

On Thu, 9 Sep, 2021, 10:20 AM James Roper, ***@***.***> wrote:

> What do you mean by XSS attack test? CORS and XSS are unrelated - XSS
> attacks are attacks where an attacker injects a script into a target site.
> CORS offers nothing to help prevent that, nor does a problem in CORS
> configuration have any impact on whether a site is vulnerable to XSS.
>
> Perhaps you meant CSRF attack test? CORS and CSRF are related, since CORS
> by design undermines of CSRF protections, and a misconfiguration of CORS
> can possibly open a site up to more than intended, allowing unintended
> cross origin requests, which would be a form of CSRF attack.
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-915760236>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWUA5SO7VGZDHQLY5RLUBA4JRANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

jroper: The most important header in CORS is the `Access-Control-Allow-Origin`. All the other headers (in our implementation at least) are just static values that need to be passed, but the big thing is, we need to make a decision about whether to send `Access-Control-Allow-Origin`, if we don't send that, the browser will apply it's normal cross origin policies, regardless of whatever other headers or response codes are there, and there can be no vulnerability caused by us. On the other hand, if we do send that, and we've made the wrong decision to send it, then we have a potential CSRF vulnerability on our hands.

The tests as written at the moment are asserting whether `Access-Control-Allow-Origin` is sent. In my opinion, that check is enough to say whether in the scenario simulated by the test, there is a vulnerability or not. So I don't think we need to try and simulate any real world CSRF scenarios, eg with a browser sites at multiple domains, in my opinion, just checking that header is enough. What matters then is whether we are testing enough scenarios.

Some scenarios that I can think of that aren't tested but may be worth testing are around trying to break the regex used to match origins. So for example, if the configured origin is a suffix or prefix of the incoming origin, that would be good to have a test for. Also the regex escaping, that needs to be tested - at a minimum, need to test that `fooxbar.com` doesn't match `foo.bar.com`, but would be good to see something even more comprehensive, and perhaps modify the code to validate that the configured domain name only contains letters, numbers, dashes, periods and colon, to protect against regex characters that we might not have thought of.
longwuyuan: I am so sad that I am not keeping well enough to write more. Also I am
nowhere near being an expert. But thanks tons because , from get go, my
thoughts are around regex as well as http method.

Thanks,
; Long

On Thu, 9 Sep, 2021, 11:54 AM James Roper, ***@***.***> wrote:

> ***@***.**** commented on this pull request.
> ------------------------------
>
> In internal/ingress/controller/template/template.go
> <https://github.com/kubernetes/ingress-nginx/pull/7614#discussion_r705006372>
> :
>
> > @@ -1672,3 +1673,42 @@ func convertGoSliceIntoLuaTable(goSliceInterface interface{}, emptyStringAsNil b
>  		return "", fmt.Errorf("could not process type: %s", kind)
>  	}
>  }
> +
> +// buildOriginRegex returns an origin as a regex
> +func buildOriginRegex(origin string) string {
> +	if !strings.HasPrefix(origin, "*") {
> +		return origin
> +	}
>
> This seems to be saying that if it doesn't start with a *, then it's not
> going to escape the regex, so foo.bar.com will match fooxbar.com?
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#pullrequestreview-749896280>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWRLXF6EDBEOSCGH5CLUBBHJPANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

larivierec: Awesome, thanks for the comments, the current regex is extremely permissive. If you have any other ideas let me know. 

I'll add that extra test as well @jroper it would be good. 
larivierec: > I am so sad that I am not keeping well enough to write more. Also I am nowhere near being an expert. But thanks tons because , from get go, my thoughts are around regex as well as http method. Thanks, ; Long

I'm unsure about this comment, you are referring to the way it is coded or the comments?
Thanks :)
longwuyuan: Ah apologies. I was just wishing I was in better  health to add some value.
My comment was unrelated to activity here.

Thanks,
; Long

On Fri, 10 Sep, 2021, 7:13 AM Christopher Larivière, <
***@***.***> wrote:

> I am so sad that I am not keeping well enough to write more. Also I am
> nowhere near being an expert. But thanks tons because , from get go, my
> thoughts are around regex as well as http method. Thanks, ; Long
>
> I'm unsure about this comment, you are referring to the way it is coded or
> the comments?
> Thanks :)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-916565339>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWR7QAGIPOF6H4UZ74TUBFPDTANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

larivierec: > Ah apologies. I was just wishing I was in better  health to add some value.
> My comment was unrelated to activity here.
> 
> Thanks,
> ; Long

Don't worry about it, get well, this is what's important
:) 
larivierec: Keep in mind that at the moment, if something goes wrong and origins aren't properly prefixed with http/https it will fallback onto a "*".

I don't know if this is the intention, but previously this is how cors on ingress functionned.

For example, if I put two or even three origins it'll fallback on "*".

The tests and current logic reflect this.
If this is not intended let me know
larivierec: /test
k8s-ci-robot: @larivierec: The `/test` command needs one or more targets.
The following commands are available to trigger required jobs:
* `/test pull-ingress-nginx-boilerplate`
* `/test pull-ingress-nginx-codegen`
* `/test pull-ingress-nginx-gofmt`
* `/test pull-ingress-nginx-golint`
* `/test pull-ingress-nginx-lualint`
* `/test pull-ingress-nginx-chart-lint`
* `/test pull-ingress-nginx-test-lua`
* `/test pull-ingress-nginx-test`
* `/test pull-ingress-nginx-e2e-1-16`
* `/test pull-ingress-nginx-e2e-1-17`
* `/test pull-ingress-nginx-e2e-1-18`
* `/test pull-ingress-nginx-e2e-1-19`
* `/test pull-ingress-nginx-e2e-helm-chart`

Use `/test all` to run the following jobs that were automatically triggered:
* `pull-ingress-nginx-boilerplate`
* `pull-ingress-nginx-codegen`
* `pull-ingress-nginx-gofmt`
* `pull-ingress-nginx-golint`
* `pull-ingress-nginx-test`



<details>

In response to [this](https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-916581708):

>/test


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
larivierec: The e2e test that failed has nothing to do with the changes.... :o
longwuyuan: I think we need someone to have enough insight to be able to conclusively
say yes or not. From a simple basic comprehension point of view, does
falling back to "*" , Literally translate loosely as "allow any origin"! If
yes, then it's not what we would want, is it not.

Thanks,
; Long

On Fri, 10 Sep, 2021, 7:58 AM Christopher Larivière, <
***@***.***> wrote:

> The e2e test that failed has nothing to do with the changes.... :o
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-916582672>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWSCNEWWI2XF6SUGOS3UBFUMDANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

larivierec: > I think we need someone to have enough insight to be able to conclusively
> say yes or not. From a simple basic comprehension point of view, does
> falling back to "*" , Literally translate loosely as "allow any origin"! If
> yes, then it's not what we would want, is it not.
> 
> Thanks,
> ; Long
> 

Agreed! I was just mentioning how it currently is :) 
longwuyuan: Thank you. Ack that.

Thanks,
; Long

On Fri, 10 Sep, 2021, 9:20 AM Christopher Larivière, <
***@***.***> wrote:

> I think we need someone to have enough insight to be able to conclusively
> say yes or not. From a simple basic comprehension point of view, does
> falling back to "*" , Literally translate loosely as "allow any origin"! If
> yes, then it's not what we would want, is it not.
>
> Thanks,
> ; Long
>
> Agreed! I was just mentioning how it currently is :)
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-916608551>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWXBKYHVX6OJTF47DADUBF6BBANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

larivierec: is there a way to rerun `e2e` tests?
larivierec: /retest
larivierec: /test all
larivierec: Small poke. 

Is there something else I could do to help out? I'd most probably like to rerun all the tests and ensure everything still works
arvtiwar: any update, just wondering how this solution works/is implemented? Thanks
longwuyuan: We are looking for authoritative comment on if this implements "allow from any origin" or not. I have pinged someone who could be taking a look soon.
larivierec: > 
> 
> any update, just wondering how this solution works/is implemented? Thanks

The bulk is in the `nginx.tmpl` it uses regex and some Go.
The go part basically get the information from the ingress-nginx annotations for CORS loops through them and creates them in the `nginx.conf` location blocks.

Mostly integration tests to ensure that the `nginx.conf` follow allows the correct origin and doesn't return more than one domain. (this is the previous issue with the older PR)

With CORS, we are only supposed to be returning one domain if it there is a match and not exposing other ones that may/may not be in the list. That's why `$http_origin` is used.
theunrealgeek: > I think we need someone to have enough insight to be able to conclusively
say yes or not. From a simple basic comprehension point of view, does
falling back to "*" , Literally translate loosely as "allow any origin"! If
yes, then it's not what we would want, is it not.

Yes an `Access-Control-Allow-Origin: *` header would indeed allow any origin. I know we are trying to be backwards compatible here, but I did notice that there is an Ingress Admission controller as well (didn’t check if there is a validating step in it), but that would be a very nice place to prevent any bad user input from falling into a bad default while giving the user visibility into the problem early on before bad things happen.
larivierec: > 
> 
> Some minor comments but overall I think this is a good start. The suggestion made on this PR earlier to let the user provide the regex policy and explicitly mention all subdomains is probably the better longer term approach.

1. The suggestion can be added definitely, however, should they co-exist?
example:
i input both a regex and a list of origins should the list of origins be appended to the defined regex?

2. For validation i'm unsure, someone would have to validate however, I don't believe so I believe the ingress controllers crashes if the regex is incorrect because the generated `nginx.conf` has bad location blocks
theunrealgeek: > The suggestion can be added definitely, however, should they co-exist?
example:
i input both a regex and a list of origins should the list of origins be appended to the defined regex?

I am not sure at the moment. I feel the suggestion for the user to provide the regex needs a little more thought and meanwhile I feel this PR is a welcome addition until that happens.

> For validation i'm unsure, someone would have to validate however, I don't believe so I believe the ingress controllers crashes if the regex is incorrect because the generated nginx.conf has bad location blocks

The validation comment was more in the context of downgrading to `*` when the list of origins is incorrect being done silently today and trying to solve for that. I’m quite new to the project, so still need to catch up on how failure scenarios like a bad user input causing a bad nginx conf are handled.


theunrealgeek: @longwuyuan I’m not sure how docs are handled, but this PR is changing how one of the annotations work and there is a doc for it at https://github.com/kubernetes/ingress-nginx/blob/a7566e58d8ae153f5de25a7a6056596ba78b9922/docs/user-guide/nginx-configuration/annotations.md#enable-cors and not sure if that will need to be modified in this PR or someone will take care of it later.
larivierec: > @longwuyuan I’m not sure how docs are handled, but this PR is changing how one of the annotations work and there is a doc for it at https://github.com/kubernetes/ingress-nginx/blob/a7566e58d8ae153f5de25a7a6056596ba78b9922/docs/user-guide/nginx-configuration/annotations.md#enable-cors and not sure if that will need to be modified in this PR or someone will take care of it later.

I could do it at the same time. I actually should've thought about it 😳
longwuyuan: I don't have the depth needed. I can chip in based on what you all discuss
and if any action item is within my abilities.

Thanks,
; Long

On Fri, 1 Oct, 2021, 2:26 AM Christopher Larivière, <
***@***.***> wrote:

> @longwuyuan <https://github.com/longwuyuan> I’m not sure how docs are
> handled, but this PR is changing how one of the annotations work and there
> is a doc for it at
> https://github.com/kubernetes/ingress-nginx/blob/a7566e58d8ae153f5de25a7a6056596ba78b9922/docs/user-guide/nginx-configuration/annotations.md#enable-cors
> and not sure if that will need to be modified in this PR or someone will
> take care of it later.
>
> I could do it at the same time. I actually should've thought about it 😳
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/kubernetes/ingress-nginx/pull/7614#issuecomment-931670141>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABGZVWTTRPD44HCGY4TPFZ3UETFHBANCNFSM5DVSS4EA>
> .
> Triage notifications on the go with GitHub Mobile for iOS
> <https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
> or Android
> <https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
>
>

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/ingress-nginx/pull/7614#" title="Author self-approved">larivierec</a>*, *<a href="https://github.com/kubernetes/ingress-nginx/pull/7614#pullrequestreview-772727965" title="Approved">theunrealgeek</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **elvinefendi** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @elvinefendi` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fingress-nginx).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/ingress-nginx/blob/main/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["elvinefendi"]} -->
larivierec: @theunrealgeek is a squash required? or is that handled automatically before i tag the one responsible
theunrealgeek: @larivierec I’m not familiar with all of the process this project follows, so I don’t know
theunrealgeek: @rikatz Could you please review this PR when you get a chance and answer the question on what the PR commit history should look like before merging. Thanks
larivierec: Would it be worth mentioning that "*.abc.com" is not supported in the documentation? 
jfbrennan: Thanks @larivierec, we're looking forward to this
iamNoah1: /triage accepted 
/priority important-soon
/kind feature
larivierec: > 
> 
> > > @longwuyuan I’m not sure how docs are handled, but this PR is changing how one of the annotations work and there is a doc for it at https://github.com/kubernetes/ingress-nginx/blob/a7566e58d8ae153f5de25a7a6056596ba78b9922/docs/user-guide/nginx-configuration/annotations.md#enable-cors and not sure if that will need to be modified in this PR or someone will take care of it later.
> > 
> > 
> > I could do it at the same time. I actually should've thought about it 😳
> 
> @larivierec It would be good, if you could also adjust the docs :)

Hey there Noah, I have already adjusted

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index c39d6e2c2..63a7bfbf6 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -100,7 +100,7 @@ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main
 ## Create an ingress (please add any additional annotation required)
 
 echo "
-  apiVersion: networking.k8s.io/v1beta1
+  apiVersion: networking.k8s.io/v1
   kind: Ingress
   metadata:
     name: foo-bar
@@ -109,10 +109,13 @@ echo "
     - host: foo.bar
       http:
         paths:
-        - backend:
-            serviceName: http-svc
-            servicePort: 80
-          path: /
+        - path: /
+          pathType: Prefix
+          backend:
+            service:
+              name: http-svc
+              port: 
+                number: 80
 " | kubectl apply -f -
 
 ## make a request
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index b313f36b8..f147d0f89 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -30,3 +30,6 @@ aliases:
   ingress-nginx-helm-reviewers:
   - ChiefAlexander
   - cpanato
+
+  ingress-nginx-kube-webhook-certgen-reviewers:
+  - invidian
diff --git a/build/build-plugin.sh b/build/build-plugin.sh
index 578957eb4..93b6efbdf 100755
--- a/build/build-plugin.sh
+++ b/build/build-plugin.sh
@@ -57,7 +57,7 @@ function build_for_arch(){
     -trimpath -ldflags="-buildid= -w -s" \
       -X ${PKG}/version.RELEASE=${TAG} \
       -X ${PKG}/version.COMMIT=${COMMIT_SHA} \
-      -X ${PKG}/version.REPO=${REPO_INFO}" \
+      -X ${PKG}/version.REPO=${REPO_INFO} \
     -o "${release}/kubectl-ingress_nginx${extension}" "${PKG}/cmd/plugin"
 
     cp LICENSE ${release}
diff --git a/build/dev-env.sh b/build/dev-env.sh
index b089de206..d98f0bfbd 100755
--- a/build/dev-env.sh
+++ b/build/dev-env.sh
@@ -61,7 +61,7 @@ echo "[dev-env] building image"
 make build image
 docker tag "${REGISTRY}/controller:${TAG}" "${DEV_IMAGE}"
 
-export K8S_VERSION=${K8S_VERSION:-v1.20.2@sha256:8f7ea6e7642c0da54f04a7ee10431549c0257315b3a634f6ef2fecaaedb19bab}
+export K8S_VERSION=${K8S_VERSION:-v1.21.1@sha256:69860bda5563ac81e3c0057d654b5253219618a22ec3a346306239bba8cfa1a6}
 
 KIND_CLUSTER_NAME="ingress-nginx-dev"
 
diff --git a/cmd/nginx/main.go b/cmd/nginx/main.go
index 69d12d05e..fb87ad84a 100644
--- a/cmd/nginx/main.go
+++ b/cmd/nginx/main.go
@@ -138,7 +138,9 @@ func main() {
 			klog.Fatalf("Error creating prometheus collector:  %v", err)
 		}
 	}
-	mc.Start()
+	// Pass the ValidationWebhook status to determine if we need to start the collector
+	// for the admissionWebhook
+	mc.Start(conf.ValidationWebhook)
 
 	if conf.EnableProfiling {
 		go registerProfiler()
diff --git a/deploy/static/provider/aws/deploy-tls-termination.yaml b/deploy/static/provider/aws/deploy-tls-termination.yaml
index 99e6397c3..a0e07e952 100644
--- a/deploy/static/provider/aws/deploy-tls-termination.yaml
+++ b/deploy/static/provider/aws/deploy-tls-termination.yaml
@@ -269,12 +269,11 @@ apiVersion: v1
 kind: Service
 metadata:
   annotations:
-    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
     service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: '60'
     service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true'
     service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX
     service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https
-    service.beta.kubernetes.io/aws-load-balancer-type: elb
+    service.beta.kubernetes.io/aws-load-balancer-type: nlb
   labels:
     helm.sh/chart: ingress-nginx-4.0.6
     app.kubernetes.io/name: ingress-nginx
diff --git a/docs/deploy/index.md b/docs/deploy/index.md
index 5c98aa996..f5074b078 100644
--- a/docs/deploy/index.md
+++ b/docs/deploy/index.md
@@ -38,7 +38,7 @@ You can wait until it is ready to run the next command:
   - [Digital Ocean](#digital-ocean)
   - [Scaleway](#scaleway)
   - [Exoscale](#exoscale)
-  - [Oracle Cloud Infrastructure](#oracle-cloud-infrastructure)  
+  - [Oracle Cloud Infrastructure](#oracle-cloud-infrastructure)
   - [Bare-metal](#bare-metal)
   - [Verify installation](#verify-installation)
   - [Detect installed version](#detect-installed-version)
@@ -82,13 +82,17 @@ Please check the microk8s [documentation page](https://microk8s.io/docs/addon-in
 
 In AWS we use a Network load balancer (NLB) to expose the NGINX Ingress controller behind a Service of `Type=LoadBalancer`.
 
+!!! info
+    The provided templates illustrate the setup for legacy in-tree service load balancer for AWS NLB.
+    AWS provides the documentation on how to use [Network load balancing on Amazon EKS](https://docs.aws.amazon.com/eks/latest/userguide/network-load-balancing.html) with [AWS Load Balancer Controller](https://github.com/kubernetes-sigs/aws-load-balancer-controller).
+
 ##### Network Load Balancer (NLB)
 
 ```console
 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.4/deploy/static/provider/aws/deploy.yaml
 ```
 
-##### TLS termination in AWS Load Balancer (ELB)
+##### TLS termination in AWS Load Balancer (NLB)
 
 In some scenarios is required to terminate TLS in the Load Balancer and not in the ingress controller.
 
diff --git a/docs/deploy/upgrade.md b/docs/deploy/upgrade.md
index b42a09865..933829a80 100644
--- a/docs/deploy/upgrade.md
+++ b/docs/deploy/upgrade.md
@@ -41,11 +41,11 @@ For interactive editing, use `kubectl edit deployment nginx-ingress-controller -
 
 ## With Helm
 
-If you installed ingress-nginx using the Helm command in the deployment docs so its name is `ngx-ingress`,
+If you installed ingress-nginx using the Helm command in the deployment docs so its name is `ingress-nginx`,
 you should be able to upgrade using
 
 ```shell
-helm upgrade --reuse-values ngx-ingress ingress-nginx/ingress-nginx
+helm upgrade --reuse-values ingress-nginx ingress-nginx/ingress-nginx
 ```
 
 ### Migrating from stable/nginx-ingress
diff --git a/docs/examples/affinity/cookie/ingress-samesite.yaml b/docs/examples/affinity/cookie/ingress-samesite.yaml
index edfa1b653..d03a25e39 100644
--- a/docs/examples/affinity/cookie/ingress-samesite.yaml
+++ b/docs/examples/affinity/cookie/ingress-samesite.yaml
@@ -15,10 +15,13 @@ spec:
   - host: stickyingress-samesite-none.example.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
 ---
 apiVersion: networking.k8s.io/v1
 kind: Ingress
@@ -35,7 +38,10 @@ spec:
   - host: stickyingress-samesite-strict.example.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
\ No newline at end of file
diff --git a/docs/examples/affinity/cookie/ingress.yaml b/docs/examples/affinity/cookie/ingress.yaml
index eac973fde..d3053021e 100644
--- a/docs/examples/affinity/cookie/ingress.yaml
+++ b/docs/examples/affinity/cookie/ingress.yaml
@@ -13,7 +13,10 @@ spec:
   - host: stickyingress.example.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port: 
+              number: 80
diff --git a/docs/examples/auth/basic/README.md b/docs/examples/auth/basic/README.md
index a5fa579e0..d3d255afb 100644
--- a/docs/examples/auth/basic/README.md
+++ b/docs/examples/auth/basic/README.md
@@ -47,9 +47,12 @@ spec:
     http:
       paths:
       - path: /
+        pathType: Prefix
         backend:
-          serviceName: http-svc
-          servicePort: 80
+          service: 
+            name: http-svc
+            port: 
+              number: 80
 " | kubectl create -f -
 ```
 
diff --git a/docs/examples/auth/client-certs/ingress.yaml b/docs/examples/auth/client-certs/ingress.yaml
index 7172081b4..90ff87514 100644
--- a/docs/examples/auth/client-certs/ingress.yaml
+++ b/docs/examples/auth/client-certs/ingress.yaml
@@ -19,10 +19,13 @@ spec:
   - host: mydomain.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
   tls:
   - hosts:
     - mydomain.com
diff --git a/docs/examples/auth/external-auth/README.md b/docs/examples/auth/external-auth/README.md
index 235cca9cb..62ae72e2c 100644
--- a/docs/examples/auth/external-auth/README.md
+++ b/docs/examples/auth/external-auth/README.md
@@ -30,10 +30,13 @@ spec:
   - host: external-auth-01.sample.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service: 
+            name: http-svc
+            port: 
+              number: 80
 status:
   loadBalancer:
     ingress:
diff --git a/docs/examples/auth/external-auth/ingress.yaml b/docs/examples/auth/external-auth/ingress.yaml
index 2a58ca2e3..df80cbb25 100644
--- a/docs/examples/auth/external-auth/ingress.yaml
+++ b/docs/examples/auth/external-auth/ingress.yaml
@@ -9,7 +9,10 @@ spec:
   - host: external-auth-01.sample.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
\ No newline at end of file
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
\ No newline at end of file
diff --git a/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml b/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
index 725bf1dc5..4c0a2d373 100644
--- a/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
+++ b/docs/examples/auth/oauth-external-auth/dashboard-ingress.yaml
@@ -11,11 +11,13 @@ spec:
   - host: __INGRESS_HOST__
     http:
       paths:
-      - backend:
-          serviceName: kubernetes-dashboard
-          servicePort: 80
-        path: /
-
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: kubernetes-dashboard
+            port:
+              number: 80
 ---
 
 apiVersion: networking.k8s.io/v1
@@ -28,10 +30,13 @@ spec:
   - host: __INGRESS_HOST__
     http:
       paths:
-      - backend:
-          serviceName: oauth2-proxy
-          servicePort: 4180
-        path: /oauth2
+      - path: /oauth2
+        pathType: Prefix
+        backend:
+          service:
+            name: oauth2-proxy
+            port:
+              number: 4180
   tls:
   - hosts:
     - __INGRESS_HOST__
diff --git a/docs/examples/chashsubset/deployment.yaml b/docs/examples/chashsubset/deployment.yaml
index 24a41dcc2..867cd01c0 100644
--- a/docs/examples/chashsubset/deployment.yaml
+++ b/docs/examples/chashsubset/deployment.yaml
@@ -64,7 +64,15 @@ metadata:
   name: nginxhello-ingress
   namespace: default
 spec:
-  backend:
-    serviceName: nginxhello
-    servicePort: 80
+  rules:
+  - host: foo.bar.com
+    http:
+      paths:
+        - path: /
+          pathType: Prefix
+          backend:
+            service:
+              name: nginxhello
+              port:
+                number: 80
 
diff --git a/docs/examples/customization/configuration-snippets/ingress.yaml b/docs/examples/customization/configuration-snippets/ingress.yaml
index 70d9042c7..aaf31f0bb 100644
--- a/docs/examples/customization/configuration-snippets/ingress.yaml
+++ b/docs/examples/customization/configuration-snippets/ingress.yaml
@@ -10,7 +10,10 @@ spec:
   - host: custom.configuration.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
diff --git a/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml b/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
index 075421807..3a80ae834 100644
--- a/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
+++ b/docs/examples/customization/external-auth-headers/deploy/echo-service.yaml
@@ -56,10 +56,13 @@ spec:
   - host: public-demo-echo-service.kube.local
     http:
       paths:
-      - backend:
-          serviceName: demo-echo-service
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: demo-echo-service
+            port:
+              number: 80
 ---
 apiVersion: networking.k8s.io/v1
 kind: Ingress
@@ -74,7 +77,10 @@ spec:
   - host: secure-demo-echo-service.kube.local
     http:
       paths:
-      - backend:
-          serviceName: demo-echo-service
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: demo-echo-service
+            port:
+              number: 80
diff --git a/docs/examples/docker-registry/ingress-with-tls.yaml b/docs/examples/docker-registry/ingress-with-tls.yaml
index 11ccf6627..bef35c50b 100644
--- a/docs/examples/docker-registry/ingress-with-tls.yaml
+++ b/docs/examples/docker-registry/ingress-with-tls.yaml
@@ -17,7 +17,10 @@ spec:
   - host: registry.<your domain>
     http:
       paths:
-      - backend:
-          serviceName: docker-registry
-          servicePort: 5000
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: docker-registry
+            port:
+              number: 5000
\ No newline at end of file
diff --git a/docs/examples/docker-registry/ingress-without-tls.yaml b/docs/examples/docker-registry/ingress-without-tls.yaml
index 2d713cb8c..f64cbb295 100644
--- a/docs/examples/docker-registry/ingress-without-tls.yaml
+++ b/docs/examples/docker-registry/ingress-without-tls.yaml
@@ -12,7 +12,10 @@ spec:
   - host: registry.<your domain>
     http:
       paths:
-      - backend:
-          serviceName: docker-registry
-          servicePort: 5000
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: docker-registry
+            port:
+              number: 5000
diff --git a/docs/examples/multi-tls/multi-tls.yaml b/docs/examples/multi-tls/multi-tls.yaml
index b3985439e..aa2cf54d6 100644
--- a/docs/examples/multi-tls/multi-tls.yaml
+++ b/docs/examples/multi-tls/multi-tls.yaml
@@ -115,14 +115,20 @@ spec:
   - host: foo.bar.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port:
+              number: 80
   - host: bar.baz.com
     http:
       paths:
-      - backend:
-          serviceName: nginx
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service
+            name: nginx
+            port:
+              number: 80
diff --git a/docs/examples/rewrite/README.md b/docs/examples/rewrite/README.md
index 6ae71a5d8..3c48b100f 100644
--- a/docs/examples/rewrite/README.md
+++ b/docs/examples/rewrite/README.md
@@ -46,10 +46,13 @@ spec:
   - host: rewrite.bar.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /something(/|$)(.*)
+      - path: /something(/|$)(.*)
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port: 
+              number: 80
 ' | kubectl create -f -
 ```
 
@@ -78,10 +81,13 @@ spec:
   - host: approot.bar.com
     http:
       paths:
-      - backend:
-          serviceName: http-svc
-          servicePort: 80
-        path: /
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: http-svc
+            port: 
+              number: 80
 " | kubectl create -f -
 ```
 
diff --git a/docs/examples/static-ip/nginx-ingress.yaml b/docs/examples/static-ip/nginx-ingress.yaml
index 358942f5c..740f46e89 100644
--- a/docs/examples/static-ip/nginx-ingress.yaml
+++ b/docs/examples/static-ip/nginx-ingress.yaml
@@ -9,7 +9,11 @@ spec:
   rules:
   - http:
       paths:
-      - backend:
+      - path: /
+        pathType: Prefix
+        backend:
           # This assumes http-svc exists and routes to healthy endpoints.
-          serviceName: http-svc
-          servicePort: 80
+          service:
+            name: http-svc
+            port:
+              number: 80
diff --git a/docs/examples/tls-termination/README.md b/docs/examples/tls-termination/README.md
index f3096db2e..2df767e26 100644
--- a/docs/examples/tls-termination/README.md
+++ b/docs/examples/tls-termination/README.md
@@ -27,10 +27,13 @@ spec:
       http:
         paths:
         - path: /
+          pathType: Prefix
           backend:
             # This assumes http-svc exists and routes to healthy endpoints
-            serviceName: http-svc
-            servicePort: 80
+            service:
+              name: http-svc
+              port: 
+                number: 80
 ```
 
 The following command instructs the controller to terminate traffic using the provided
diff --git a/docs/examples/tls-termination/ingress.yaml b/docs/examples/tls-termination/ingress.yaml
index 2e989d1b0..debaa45b2 100644
--- a/docs/examples/tls-termination/ingress.yaml
+++ b/docs/examples/tls-termination/ingress.yaml
@@ -14,7 +14,10 @@ spec:
       http:
         paths:
         - path: /
+          pathType: Prefix
           backend:
             # This assumes http-svc exists and routes to healthy endpoints
-            serviceName: http-svc
-            servicePort: 80
+            service:
+              name: http-svc
+              port:
+                number: 80
diff --git a/docs/index.md b/docs/index.md
index 1154ba32b..6aa2ea81c 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -154,10 +154,12 @@ In this scenario, you need to create multiple ingressClasses (see example one).
 
 - Ingress-Nginx-IngressClass-1 with .spec.controller equals to "k8s.io/ingress-nginx1"
 - Ingress-Nginx-IngressClass-2 with .spec.controller equals to "k8s.io/ingress-nginx2"
+
 When deploying your ingress controllers, you will have to change the `--controller-class` field as follows:
 
-Ingress-Nginx-Controller-nginx1 with `k8s.io/ingress-nginx1`
-Ingress-Nginx-Controller-nginx2 with `k8s.io/ingress-nginx2`
+- Ingress-Nginx-Controller-nginx1 with `k8s.io/ingress-nginx1`
+- Ingress-Nginx-Controller-nginx2 with `k8s.io/ingress-nginx2`
+
 Then, when you create an Ingress Object with IngressClassName = `ingress-nginx2`, it will look for controllers with `controller-class=k8s.io/ingress-nginx2` and as `Ingress-Nginx-Controller-nginx2` is watching objects that points to `ingressClass="k8s.io/ingress-nginx2`, it will serve that object, while `Ingress-Nginx-Controller-nginx1` will ignore the ingress object.
 
 Bear in mind that, if your `Ingress-Nginx-Controller-nginx2` is started with the flag `--watch-ingress-without-class=true`, then it will serve:
diff --git a/docs/user-guide/fcgi-services.md b/docs/user-guide/fcgi-services.md
index 62271c2ce..03afc89d1 100644
--- a/docs/user-guide/fcgi-services.md
+++ b/docs/user-guide/fcgi-services.md
@@ -74,9 +74,13 @@ spec:
   - host: app.example.com
     http:
       paths:
-      - backend:
-          serviceName: example-service
-          servicePort: fastcgi
+      - path: /
+        pathType: Prefix
+        backend:
+          service:
+            name: example-service
+            port:
+              name: fastcgi
 ```
 
 ## FastCGI Ingress Annotations
diff --git a/docs/user-guide/ingress-path-matching.md b/docs/user-guide/ingress-path-matching.md
index 6812e2cb5..e5e0d9a50 100644
--- a/docs/user-guide/ingress-path-matching.md
+++ b/docs/user-guide/ingress-path-matching.md
@@ -27,9 +27,12 @@ spec:
     http:
       paths:
       - path: /foo/.*
+        pathType: Prefix
         backend:
-          serviceName: test
-          servicePort: 80
+          service:
+            name: test
+            port:
+              number: 80
 ```
 
 The preceding ingress definition would translate to the following location block within the NGINX configuration for the `test.com` server:
@@ -61,13 +64,19 @@ spec:
     http:
       paths:
       - path: /foo/bar
+        pathType: Prefix
         backend:
-          serviceName: service1
-          servicePort: 80
+          service:
+            name: service1
+            port:
+              number: 80
       - path: /foo/bar/
+        pathType: Prefix
         backend:
-          serviceName: service2
-          servicePort: 80
+          service:
+            name: service2
+            port:
+              number: 80
 ```
 
 ```yaml
@@ -83,9 +92,12 @@ spec:
     http:
       paths:
       - path: /foo/bar/(.+)
+        pathType: Prefix
         backend:
-          serviceName: service3
-          servicePort: 80
+          service:
+            name: service3
+            port: 
+              number: 80
 ```
 
 The ingress controller would define the following location blocks, in order of descending length, within the NGINX template for the `test.com` server:
@@ -137,13 +149,19 @@ spec:
     http:
       paths:
       - path: /foo/bar/bar
+        pathType: Prefix
         backend:
-          serviceName: test
-          servicePort: 80
+          service:
+            name: test
+            port: 
+              number: 80
       - path: /foo/bar/[A-Z0-9]{3}
+        pathType: Prefix
         backend:
-          serviceName: test
-          servicePort: 80
+          service:
+            name: test
+            port: 
+              number: 80
 ```
 
 The ingress controller would define the following location blocks (in this order) within the NGINX template for the `test.com` server:
diff --git a/docs/user-guide/nginx-configuration/annotations.md b/docs/user-guide/nginx-configuration/annotations.md
index ca515f9e2..19bd3947f 100755
--- a/docs/user-guide/nginx-configuration/annotations.md
+++ b/docs/user-guide/nginx-configuration/annotations.md
@@ -354,10 +354,13 @@ CORS can be controlled with the following annotations:
 
 * `nginx.ingress.kubernetes.io/cors-allow-origin`: Controls what's the accepted Origin for CORS.
 
-    This is a single field value, with the following format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
+    This is a multi-valued field, separated by ','. It must follow this format: `http(s)://origin-site.com` or `http(s)://origin-site.com:port`
 
     - Default: `*`
-    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443, http://origin-site.com, https://example.org:1199"`
+
+    It also supports single level wildcard subdomains and follows this format: `http(s)://*.foo.bar`, `http(s)://*.bar.foo:8080` or `http(s)://*.abc.bar.foo:9000`
+    - Example: `nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.origin-site.com:4443, http://*.origin-site.com, https://example.org:1199"`
 
 * `nginx.ingress.kubernetes.io/cors-allow-credentials`: Controls if credentials can be passed during CORS operations.
 
diff --git a/docs/user-guide/nginx-configuration/configmap.md b/docs/user-guide/nginx-configuration/configmap.md
index 4fdda21ba..21cb2799e 100755
--- a/docs/user-guide/nginx-configuration/configmap.md
+++ b/docs/user-guide/nginx-configuration/configmap.md
@@ -98,6 +98,7 @@ The following table shows a configuration option's name, type, and the default v
 |[use-geoip2](#use-geoip2)|bool|"false"|
 |[enable-brotli](#enable-brotli)|bool|"false"|
 |[brotli-level](#brotli-level)|int|4|
+|[brotli-min-length](#brotli-min-length)|int|20|
 |[brotli-types](#brotli-types)|string|"application/xml+rss application/atom+xml application/javascript application/x-javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/svg+xml image/x-icon text/css text/javascript text/plain text/x-component"|
 |[use-http2](#use-http2)|bool|"true"|
 |[gzip-level](#gzip-level)|int|1|
@@ -665,6 +666,10 @@ The default mime type list to compress is: `application/xml+rss application/atom
 
 Sets the Brotli Compression Level that will be used. _**default:**_ 4
 
+## brotli-min-length
+
+Minimum length of responses, in bytes, that will be eligible for brotli compression. _**default:**_ 20
+
 ## brotli-types
 
 Sets the MIME Types that will be compressed on-the-fly by brotli.
diff --git a/docs/user-guide/third-party-addons/opentracing.md b/docs/user-guide/third-party-addons/opentracing.md
index 6c2918046..e27f32f6b 100644
--- a/docs/user-guide/third-party-addons/opentracing.md
+++ b/docs/user-guide/third-party-addons/opentracing.md
@@ -189,10 +189,13 @@ In the Zipkin interface we can see the details:
         - host: example.com
           http:
             paths:
-            - backend:
-                serviceName: echoheaders-x
-                servicePort: 80
-              path: /echo
+            - path: /echo
+              pathType: Prefix
+              backend:
+                service:
+                  name: echoheaders-x
+                  port:
+                    number: 80
       ' | kubectl apply -f -
     ```
 
diff --git a/go.mod b/go.mod
index 2f64cfd89..49e380774 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
 	github.com/mitchellh/hashstructure v1.1.0
 	github.com/mitchellh/mapstructure v1.4.2
 	github.com/moul/pb v0.0.0-20180404114147-54bdd96e6a52
-	github.com/ncabatoff/process-exporter v0.7.5
+	github.com/ncabatoff/process-exporter v0.7.9
 	github.com/onsi/ginkgo v1.16.4
 	github.com/opencontainers/runc v1.0.2
 	github.com/pkg/errors v0.9.1
@@ -78,13 +78,14 @@ require (
 	github.com/go-openapi/jsonreference v0.19.3 // indirect
 	github.com/go-openapi/spec v0.19.5 // indirect
 	github.com/go-openapi/swag v0.19.5 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/godbus/dbus/v5 v5.0.4 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/gomarkdown/markdown v0.0.0-20200824053859-8c8b3816f167 // indirect
 	github.com/google/btree v1.0.0 // indirect
-	github.com/google/go-cmp v0.5.5 // indirect
+	github.com/google/go-cmp v0.5.6 // indirect
 	github.com/google/go-querystring v1.0.0 // indirect
 	github.com/google/gofuzz v1.1.0 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
@@ -108,7 +109,7 @@ require (
 	github.com/nxadm/tail v1.4.8 // indirect
 	github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 // indirect
 	github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
-	github.com/prometheus/procfs v0.6.0 // indirect
+	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 718fba62f..21a2bb5c4 100644
--- a/go.sum
+++ b/go.sum
@@ -269,6 +269,7 @@ github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2K
 github.com/go-openapi/validate v0.19.8/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
 github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
@@ -329,8 +330,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -445,6 +447,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@@ -528,9 +532,8 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
 github.com/ncabatoff/fakescraper v0.0.0-20201102132415-4b37ba603d65/go.mod h1:Tx6UMSMyIsjLG/VU/F6xA1+0XI+/f9o1dGJnf1l+bPg=
 github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833 h1:t4WWQ9I797y7QUgeEjeXnVb+oYuEDQc6gLvrZJTYo94=
 github.com/ncabatoff/go-seq v0.0.0-20180805175032-b08ef85ed833/go.mod h1:0CznHmXSjMEqs5Tezj/w2emQoM41wzYM9KpDKUHPYag=
-github.com/ncabatoff/process-exporter v0.7.5 h1:KPB7abQEny4kaSge/WFRQb04/UbLmUgHxrpWAU+voRE=
-github.com/ncabatoff/process-exporter v0.7.5/go.mod h1:RMjrx3Qn8l2pgCD27g45xbko4UDpVVuHC8Cd2YXPtWA=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/ncabatoff/process-exporter v0.7.9 h1:RRvLF8pbWwLryEZiNMKJGfCTQkJW8SNabneiKQq3DBY=
+github.com/ncabatoff/process-exporter v0.7.9/go.mod h1:I/1yUBpScNw5UC/Qf2DC9vWqol2cR6jPqSVulMEnzxQ=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -578,6 +581,7 @@ github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+v
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -594,7 +598,6 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
 github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0/7TSWwj+ITvv0TnM=
 github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
 github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@@ -610,7 +613,6 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
 github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
 github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
 github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
 github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
@@ -621,13 +623,17 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
 github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
+github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
@@ -956,7 +962,6 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1190,8 +1195,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
diff --git a/hack/generate-deploy-scripts.sh b/hack/generate-deploy-scripts.sh
index b8aec4104..28625721b 100755
--- a/hack/generate-deploy-scripts.sh
+++ b/hack/generate-deploy-scripts.sh
@@ -89,11 +89,12 @@ controller:
     externalTrafficPolicy: Local
 
     annotations:
-      service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
-      service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: 'true'
+      # This example is for legacy in-tree service load balancer controller for AWS NLB,
+      # that has been phased out from Kubernetes mainline.
+      service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
       service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
       service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-west-2:XXXXXXXX:certificate/XXXXXX-XXXXXXX-XXXXXXX-XXXXXXXX"
-      service.beta.kubernetes.io/aws-load-balancer-type: elb
+      service.beta.kubernetes.io/aws-load-balancer-type: nlb
       # Ensure the ELB idle timeout is less than nginx keep-alive timeout. By default,
       # NGINX keep-alive is set to 75s. If using WebSockets, the value will need to be
       # increased to '3600' to avoid any potential issues.
diff --git a/images/kube-webhook-certgen/OWNERS b/images/kube-webhook-certgen/OWNERS
new file mode 100644
index 000000000..ad47b2e6d
--- /dev/null
+++ b/images/kube-webhook-certgen/OWNERS
@@ -0,0 +1,10 @@
+# See the OWNERS docs: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
+
+approvers:
+- ingress-nginx-admins
+- ingress-nginx-maintainers
+
+reviewers:
+- ingress-nginx-admins
+- ingress-nginx-maintainers
+- ingress-nginx-kube-webhook-certgen-reviewers
diff --git a/internal/ingress/annotations/cors/main.go b/internal/ingress/annotations/cors/main.go
index 44947a014..d2f232af1 100644
--- a/internal/ingress/annotations/cors/main.go
+++ b/internal/ingress/annotations/cors/main.go
@@ -18,8 +18,10 @@ package cors
 
 import (
 	"regexp"
+	"strings"
 
 	networking "k8s.io/api/networking/v1"
+	"k8s.io/klog/v2"
 
 	"k8s.io/ingress-nginx/internal/ingress/annotations/parser"
 	"k8s.io/ingress-nginx/internal/ingress/resolver"
@@ -36,7 +38,7 @@ var (
 	// Regex are defined here to prevent information leak, if user tries to set anything not valid
 	// that could cause the Response to contain some internal value/variable (like returning $pid, $upstream_addr, etc)
 	// Origin must contain a http/s Origin (including or not the port) or the value '*'
-	corsOriginRegex = regexp.MustCompile(`^(https?://[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
+	corsOriginRegex = regexp.MustCompile(`^(https?://(\*\.)?[A-Za-z0-9\-\.]*(:[0-9]+)?|\*)?$`)
 	// Method must contain valid methods list (PUT, GET, POST, BLA)
 	// May contain or not spaces between each verb
 	corsMethodsRegex = regexp.MustCompile(`^([A-Za-z]+,?\s?)+$`)
@@ -54,13 +56,13 @@ type cors struct {
 
 // Config contains the Cors configuration to be used in the Ingress
 type Config struct {
-	CorsEnabled          bool   `json:"corsEnabled"`
-	CorsAllowOrigin      string `json:"corsAllowOrigin"`
-	CorsAllowMethods     string `json:"corsAllowMethods"`
-	CorsAllowHeaders     string `json:"corsAllowHeaders"`
-	CorsAllowCredentials bool   `json:"corsAllowCredentials"`
-	CorsExposeHeaders    string `json:"corsExposeHeaders"`
-	CorsMaxAge           int    `json:"corsMaxAge"`
+	CorsEnabled          bool     `json:"corsEnabled"`
+	CorsAllowOrigin      []string `json:"corsAllowOrigin"`
+	CorsAllowMethods     string   `json:"corsAllowMethods"`
+	CorsAllowHeaders     string   `json:"corsAllowHeaders"`
+	CorsAllowCredentials bool     `json:"corsAllowCredentials"`
+	CorsExposeHeaders    string   `json:"corsExposeHeaders"`
+	CorsMaxAge           int      `json:"corsMaxAge"`
 }
 
 // NewParser creates a new CORS annotation parser
@@ -91,13 +93,20 @@ func (c1 *Config) Equal(c2 *Config) bool {
 	if c1.CorsAllowMethods != c2.CorsAllowMethods {
 		return false
 	}
-	if c1.CorsAllowOrigin != c2.CorsAllowOrigin {
+	if c1.CorsEnabled != c2.CorsEnabled {
 		return false
 	}
-	if c1.CorsEnabled != c2.CorsEnabled {
+
+	if len(c1.CorsAllowOrigin) != len(c2.CorsAllowOrigin) {
 		return false
 	}
 
+	for i, v := range c1.CorsAllowOrigin {
+		if v != c2.CorsAllowOrigin[i] {
+			return false
+		}
+	}
+
 	return true
 }
 
@@ -112,9 +121,23 @@ func (c cors) Parse(ing *networking.Ingress) (interface{}, error) {
 		config.CorsEnabled = false
 	}
 
-	config.CorsAllowOrigin, err = parser.GetStringAnnotation("cors-allow-origin", ing)
-	if err != nil || !corsOriginRegex.MatchString(config.CorsAllowOrigin) {
-		config.CorsAllowOrigin = "*"
+	unparsedOrigins, err := parser.GetStringAnnotation("cors-allow-origin", ing)
+	if err == nil {
+		config.CorsAllowOrigin = strings.Split(unparsedOrigins, ",")
+		for i, origin := range config.CorsAllowOrigin {
+			origin = strings.TrimSpace(origin)
+			if origin == "*" {
+				config.CorsAllowOrigin = []string{"*"}
+				break
+			}
+			if !corsOriginRegex.MatchString(origin) {
+				klog.Errorf("Error parsing cors-allow-origin parameters. Supplied incorrect origin: %s. Skipping.", origin)
+				config.CorsAllowOrigin = append(config.CorsAllowOrigin[:i], config.CorsAllowOrigin[i+1:]...)
+			}
+			klog.Infof("Current config.corsAllowOrigin %v", config.CorsAllowOrigin)
+		}
+	} else {
+		config.CorsAllowOrigin = []string{"*"}
 	}
 
 	config.CorsAllowHeaders, err = parser.GetStringAnnotation("cors-allow-headers", ing)
@@ -143,5 +166,4 @@ func (c cors) Parse(ing *networking.Ingress) (interface{}, error) {
 	}
 
 	return config, nil
-
 }
diff --git a/internal/ingress/controller/config/config.go b/internal/ingress/controller/config/config.go
index b1a5fc8c4..2d853cfb0 100644
--- a/internal/ingress/controller/config/config.go
+++ b/internal/ingress/controller/config/config.go
@@ -413,6 +413,9 @@ type Configuration struct {
 	// Brotli Compression Level that will be used
 	BrotliLevel int `json:"brotli-level,omitempty"`
 
+	// Minimum length of responses, in bytes, that will be eligible for brotli compression
+	BrotliMinLength int `json:"brotli-min-length,omitempty"`
+
 	// MIME Types that will be compressed on-the-fly using Brotli module
 	BrotliTypes string `json:"brotli-types,omitempty"`
 
@@ -778,6 +781,7 @@ func NewDefault() Configuration {
 		BlockUserAgents:                  defBlockEntity,
 		BlockReferers:                    defBlockEntity,
 		BrotliLevel:                      4,
+		BrotliMinLength:                  20,
 		BrotliTypes:                      brotliTypes,
 		ClientHeaderBufferSize:           "1k",
 		ClientHeaderTimeout:              60,
diff --git a/internal/ingress/controller/controller.go b/internal/ingress/controller/controller.go
index f935f5fff..d648bb3ae 100644
--- a/internal/ingress/controller/controller.go
+++ b/internal/ingress/controller/controller.go
@@ -215,6 +215,8 @@ func (n *NGINXController) syncIngress(interface{}) error {
 // CheckIngress returns an error in case the provided ingress, when added
 // to the current configuration, generates an invalid configuration
 func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
+	startCheck := time.Now().UnixNano() / 1000000
+
 	if ing == nil {
 		// no ingress to add, no state change
 		return nil
@@ -233,7 +235,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 	if n.cfg.DisableCatchAll && ing.Spec.DefaultBackend != nil {
 		return fmt.Errorf("This deployment is trying to create a catch-all ingress while DisableCatchAll flag is set to true. Remove '.spec.backend' or set DisableCatchAll flag to false.")
 	}
-
+	startRender := time.Now().UnixNano() / 1000000
 	cfg := n.store.GetBackendConfiguration()
 	cfg.Resolver = n.resolver
 
@@ -267,7 +269,7 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		Ingress:           *ing,
 		ParsedAnnotations: annotations.NewAnnotationExtractor(n.store).Extract(ing),
 	})
-
+	startTest := time.Now().UnixNano() / 1000000
 	_, servers, pcfg := n.getConfiguration(ings)
 
 	err := checkOverlap(ing, allIngresses, servers)
@@ -275,9 +277,10 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
 		return err
 	}
-
+	testedSize := len(ings)
 	if n.cfg.DisableFullValidationTest {
 		_, _, pcfg = n.getConfiguration(ings[len(ings)-1:])
+		testedSize = 1
 	}
 
 	content, err := n.generateTemplate(cfg, *pcfg)
@@ -291,8 +294,16 @@ func (n *NGINXController) CheckIngress(ing *networking.Ingress) error {
 		n.metricCollector.IncCheckErrorCount(ing.ObjectMeta.Namespace, ing.Name)
 		return err
 	}
-
 	n.metricCollector.IncCheckCount(ing.ObjectMeta.Namespace, ing.Name)
+	endCheck := time.Now().UnixNano() / 1000000
+	n.metricCollector.SetAdmissionMetrics(
+		float64(testedSize),
+		float64(endCheck-startTest)/1000,
+		float64(len(ings)),
+		float64(startTest-startRender)/1000,
+		float64(len(content)),
+		float64(endCheck-startCheck)/1000,
+	)
 	return nil
 }
 
diff --git a/internal/ingress/controller/nginx.go b/internal/ingress/controller/nginx.go
index ddde11bc8..b77d1d870 100644
--- a/internal/ingress/controller/nginx.go
+++ b/internal/ingress/controller/nginx.go
@@ -241,7 +241,8 @@ type NGINXController struct {
 
 	store store.Storer
 
-	metricCollector metric.Collector
+	metricCollector    metric.Collector
+	admissionCollector metric.Collector
 
 	validationWebhookServer *http.Server
 
diff --git a/internal/ingress/controller/template/template.go b/internal/ingress/controller/template/template.go
index dc4b78c1a..1147c8e5d 100644
--- a/internal/ingress/controller/template/template.go
+++ b/internal/ingress/controller/template/template.go
@@ -276,6 +276,7 @@ var (
 		"shouldLoadAuthDigestModule":         shouldLoadAuthDigestModule,
 		"shouldLoadInfluxDBModule":           shouldLoadInfluxDBModule,
 		"buildServerName":                    buildServerName,
+		"buildCorsOriginRegex":               buildCorsOriginRegex,
 	}
 )
 
@@ -1676,3 +1677,29 @@ func convertGoSliceIntoLuaTable(goSliceInterface interface{}, emptyStringAsNil b
 		return "", fmt.Errorf("could not process type: %s", kind)
 	}
 }
+
+func buildOriginRegex(origin string) string {
+	origin = regexp.QuoteMeta(origin)
+	origin = strings.Replace(origin, "\\*", "[A-Za-z0-9]+", 1)
+	return fmt.Sprintf("(%s)", origin)
+}
+
+func buildCorsOriginRegex(corsOrigins []string) string {
+	if len(corsOrigins) == 1 && corsOrigins[0] == "*" {
+		return "set $http_origin *;\nset $cors 'true';"
+	}
+
+	var originsRegex string = "if ($http_origin ~* ("
+	for i, origin := range corsOrigins {
+		originTrimmed := strings.TrimSpace(origin)
+		if len(originTrimmed) > 0 {
+			builtOrigin := buildOriginRegex(originTrimmed)
+			originsRegex += builtOrigin
+			if i != len(corsOrigins)-1 {
+				originsRegex = originsRegex + "|"
+			}
+		}
+	}
+	originsRegex = originsRegex + ")$ ) { set $cors 'true'; }"
+	return originsRegex
+}
diff --git a/internal/ingress/metric/collectors/admission.go b/internal/ingress/metric/collectors/admission.go
new file mode 100644
index 000000000..cf42fbaa1
--- /dev/null
+++ b/internal/ingress/metric/collectors/admission.go
@@ -0,0 +1,157 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package collectors
+
+import (
+	"fmt"
+
+	"github.com/prometheus/client_golang/prometheus"
+	"k8s.io/klog/v2"
+)
+
+// AdmissionCollector stores prometheus metrics of the admission webhook
+type AdmissionCollector struct {
+	prometheus.Collector
+
+	testedIngressLength prometheus.Gauge
+	testedIngressTime   prometheus.Gauge
+
+	renderingIngressLength prometheus.Gauge
+	renderingIngressTime   prometheus.Gauge
+
+	admissionTime prometheus.Gauge
+
+	testedConfigurationSize prometheus.Gauge
+
+	constLabels prometheus.Labels
+	labels      prometheus.Labels
+}
+
+// NewAdmissionCollector creates a new AdmissionCollector instance for the admission collector
+func NewAdmissionCollector(pod, namespace, class string) *AdmissionCollector {
+	constLabels := prometheus.Labels{
+		"controller_namespace": namespace,
+		"controller_class":     class,
+		"controller_pod":       pod,
+	}
+
+	am := &AdmissionCollector{
+		constLabels: constLabels,
+
+		labels: prometheus.Labels{
+			"namespace": namespace,
+			"class":     class,
+		},
+
+		testedIngressLength: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_tested_ingresses",
+				Help:        "The length of ingresses processed by the admission controller",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		testedIngressTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_tested_duration",
+				Help:        "The processing duration of the admission controller tests (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		renderingIngressLength: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_render_ingresses",
+				Help:        "The length of ingresses rendered by the admission controller",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		renderingIngressTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_render_duration",
+				Help:        "The processing duration of ingresses rendering by the admission controller (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+		testedConfigurationSize: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Namespace:   PrometheusNamespace,
+				Name:        "admission_config_size",
+				Help:        "The size of the tested configuration",
+				ConstLabels: constLabels,
+			}),
+		admissionTime: prometheus.NewGauge(
+			prometheus.GaugeOpts{
+				Name:        "admission_roundtrip_duration",
+				Help:        "The complete duration of the admission controller at the time to process a new event (float seconds)",
+				Namespace:   PrometheusNamespace,
+				ConstLabels: constLabels,
+			}),
+	}
+	return am
+}
+
+// Describe implements prometheus.Collector
+func (am AdmissionCollector) Describe(ch chan<- *prometheus.Desc) {
+	am.testedIngressLength.Describe(ch)
+	am.testedIngressTime.Describe(ch)
+	am.renderingIngressLength.Describe(ch)
+	am.renderingIngressTime.Describe(ch)
+	am.testedConfigurationSize.Describe(ch)
+	am.admissionTime.Describe(ch)
+}
+
+// Collect implements the prometheus.Collector interface.
+func (am AdmissionCollector) Collect(ch chan<- prometheus.Metric) {
+	am.testedIngressLength.Collect(ch)
+	am.testedIngressTime.Collect(ch)
+	am.renderingIngressLength.Collect(ch)
+	am.renderingIngressTime.Collect(ch)
+	am.testedConfigurationSize.Collect(ch)
+	am.admissionTime.Collect(ch)
+}
+
+// ByteFormat formats humanReadable bytes
+func ByteFormat(bytes int64) string {
+	const unit = 1000
+	if bytes < unit {
+		return fmt.Sprintf("%d B", bytes)
+	}
+	div, exp := int64(unit), 0
+	for n := bytes / unit; n >= unit; n /= unit {
+		div *= unit
+		exp++
+	}
+	return fmt.Sprintf("%.1f%cB",
+		float64(bytes)/float64(div), "kMGTPE"[exp])
+}
+
+// SetAdmissionMetrics sets the values for AdmissionMetrics that can be called externally
+func (am *AdmissionCollector) SetAdmissionMetrics(testedIngressLength float64, testedIngressTime float64, renderingIngressLength float64, renderingIngressTime float64, testedConfigurationSize float64, admissionTime float64) {
+	am.testedIngressLength.Set(testedIngressLength)
+	am.testedIngressTime.Set(testedIngressTime)
+	am.renderingIngressLength.Set(renderingIngressLength)
+	am.renderingIngressTime.Set(renderingIngressTime)
+	am.testedConfigurationSize.Set(testedConfigurationSize)
+	am.admissionTime.Set(admissionTime)
+	klog.Infof("processed ingress via admission controller {testedIngressLength:%v testedIngressTime:%vs renderingIngressLength:%v renderingIngressTime:%vs admissionTime:%vs testedConfigurationSize:%v}",
+		testedIngressLength,
+		testedIngressTime,
+		renderingIngressLength,
+		renderingIngressTime,
+		ByteFormat(int64(testedConfigurationSize)),
+		admissionTime,
+	)
+}
diff --git a/internal/ingress/metric/dummy.go b/internal/ingress/metric/dummy.go
index 59a9144e0..922a21604 100644
--- a/internal/ingress/metric/dummy.go
+++ b/internal/ingress/metric/dummy.go
@@ -32,6 +32,9 @@ type DummyCollector struct{}
 // ConfigSuccess ...
 func (dc DummyCollector) ConfigSuccess(uint64, bool) {}
 
+// SetAdmissionMetrics ...
+func (dc DummyCollector) SetAdmissionMetrics(float64, float64, float64, float64, float64, float64) {}
+
 // IncReloadCount ...
 func (dc DummyCollector) IncReloadCount() {}
 
@@ -48,10 +51,10 @@ func (dc DummyCollector) IncCheckErrorCount(string, string) {}
 func (dc DummyCollector) RemoveMetrics(ingresses, endpoints []string) {}
 
 // Start ...
-func (dc DummyCollector) Start() {}
+func (dc DummyCollector) Start(admissionStatus string) {}
 
 // Stop ...
-func (dc DummyCollector) Stop() {}
+func (dc DummyCollector) Stop(admissionStatus string) {}
 
 // SetSSLExpireTime ...
 func (dc DummyCollector) SetSSLExpireTime([]*ingress.Server) {}
diff --git a/internal/ingress/metric/main.go b/internal/ingress/metric/main.go
index 64810dd36..0cc07fe28 100644
--- a/internal/ingress/metric/main.go
+++ b/internal/ingress/metric/main.go
@@ -36,6 +36,8 @@ type Collector interface {
 	IncReloadCount()
 	IncReloadErrorCount()
 
+	SetAdmissionMetrics(float64, float64, float64, float64, float64, float64)
+
 	OnStartedLeading(string)
 	OnStoppedLeading(string)
 
@@ -49,15 +51,16 @@ type Collector interface {
 	// SetHosts sets the hostnames that are being served by the ingress controller
 	SetHosts(sets.String)
 
-	Start()
-	Stop()
+	Start(string)
+	Stop(string)
 }
 
 type collector struct {
 	nginxStatus  collectors.NGINXStatusCollector
 	nginxProcess collectors.NGINXProcessCollector
 
-	ingressController *collectors.Controller
+	ingressController   *collectors.Controller
+	admissionController *collectors.AdmissionCollector
 
 	socket *collectors.SocketCollector
 
@@ -90,11 +93,14 @@ func NewCollector(metricsPerHost bool, registry *prometheus.Registry, ingresscla
 
 	ic := collectors.NewController(podName, podNamespace, ingressclass)
 
+	am := collectors.NewAdmissionCollector(podName, podNamespace, ingressclass)
+
 	return Collector(&collector{
 		nginxStatus:  nc,
 		nginxProcess: pc,
 
-		ingressController: ic,
+		admissionController: am,
+		ingressController:   ic,
 
 		socket: s,
 
@@ -127,9 +133,12 @@ func (c *collector) RemoveMetrics(ingresses, hosts []string) {
 	c.ingressController.RemoveMetrics(hosts, c.registry)
 }
 
-func (c *collector) Start() {
+func (c *collector) Start(admissionStatus string) {
 	c.registry.MustRegister(c.nginxStatus)
 	c.registry.MustRegister(c.nginxProcess)
+	if admissionStatus != "" {
+		c.registry.MustRegister(c.admissionController)
+	}
 	c.registry.MustRegister(c.ingressController)
 	c.registry.MustRegister(c.socket)
 
@@ -143,9 +152,12 @@ func (c *collector) Start() {
 	go c.socket.Start()
 }
 
-func (c *collector) Stop() {
+func (c *collector) Stop(admissionStatus string) {
 	c.registry.Unregister(c.nginxStatus)
 	c.registry.Unregister(c.nginxProcess)
+	if admissionStatus != "" {
+		c.registry.Unregister(c.admissionController)
+	}
 	c.registry.Unregister(c.ingressController)
 	c.registry.Unregister(c.socket)
 
@@ -167,6 +179,17 @@ func (c *collector) SetHosts(hosts sets.String) {
 	c.socket.SetHosts(hosts)
 }
 
+func (c *collector) SetAdmissionMetrics(testedIngressLength float64, testedIngressTime float64, renderingIngressLength float64, renderingIngressTime float64, testedConfigurationSize float64, admissionTime float64) {
+	c.admissionController.SetAdmissionMetrics(
+		testedIngressLength,
+		testedIngressTime,
+		renderingIngressLength,
+		renderingIngressTime,
+		testedConfigurationSize,
+		admissionTime,
+	)
+}
+
 // OnStartedLeading indicates the pod was elected as the leader
 func (c *collector) OnStartedLeading(electionID string) {
 	setLeader(true)
diff --git a/rootfs/etc/nginx/template/nginx.tmpl b/rootfs/etc/nginx/template/nginx.tmpl
index bfe0703ff..d579165b7 100755
--- a/rootfs/etc/nginx/template/nginx.tmpl
+++ b/rootfs/etc/nginx/template/nginx.tmpl
@@ -320,6 +320,7 @@ http {
     {{ if $cfg.EnableBrotli }}
     brotli on;
     brotli_comp_level {{ $cfg.BrotliLevel }};
+    brotli_min_length {{ $cfg.BrotliMinLength }};
     brotli_types {{ $cfg.BrotliTypes }};
     {{ end }}
 
@@ -866,22 +867,33 @@ stream {
 {{ define "CORS" }}
      {{ $cors := .CorsConfig }}
      # Cors Preflight methods needs additional options and different Return Code
+     {{ if $cors.CorsAllowOrigin }}
+        {{ buildCorsOriginRegex $cors.CorsAllowOrigin }}
+     {{ end }}
      if ($request_method = 'OPTIONS') {
-        more_set_headers 'Access-Control-Allow-Origin: {{ $cors.CorsAllowOrigin }}';
+        set $cors ${cors}options;
+     }
+
+     if ($cors = "true") {
+        more_set_headers 'Access-Control-Allow-Origin: $http_origin';
         {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }}
         more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}';
         more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}';
         {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }}
         more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}';
-        more_set_headers 'Content-Type: text/plain charset=UTF-8';
-        more_set_headers 'Content-Length: 0';
-        return 204;
      }
 
-        more_set_headers 'Access-Control-Allow-Origin: {{ $cors.CorsAllowOrigin }}';
+     if ($cors = "trueoptions") {
+        more_set_headers 'Access-Control-Allow-Origin: $http_origin';
         {{ if $cors.CorsAllowCredentials }} more_set_headers 'Access-Control-Allow-Credentials: {{ $cors.CorsAllowCredentials }}'; {{ end }}
+        more_set_headers 'Access-Control-Allow-Methods: {{ $cors.CorsAllowMethods }}';
+        more_set_headers 'Access-Control-Allow-Headers: {{ $cors.CorsAllowHeaders }}';
         {{ if not (empty $cors.CorsExposeHeaders) }} more_set_headers 'Access-Control-Expose-Headers: {{ $cors.CorsExposeHeaders }}'; {{ end }}
-
+        more_set_headers 'Access-Control-Max-Age: {{ $cors.CorsMaxAge }}';
+        more_set_headers 'Content-Type: text/plain charset=UTF-8';
+        more_set_headers 'Content-Length: 0';
+        return 204;
+     }
 {{ end }}
 
 {{/* definition of server-template to avoid repetitions with server-alias */}}


Test patch:
diff --git a/internal/ingress/annotations/annotations_test.go b/internal/ingress/annotations/annotations_test.go
index ada81117d..8eec999cc 100644
--- a/internal/ingress/annotations/annotations_test.go
+++ b/internal/ingress/annotations/annotations_test.go
@@ -215,15 +215,15 @@ func TestCors(t *testing.T) {
 		corsenabled bool
 		methods     string
 		headers     string
-		origin      string
+		origin      []string
 		credentials bool
 		expose      string
 	}{
-		{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
-		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false", annotationCorsExposeHeaders: "X-CustomResponseHeader"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, "*", false, "X-CustomResponseHeader"},
-		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, "*", false, ""},
-		{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
-		{nil, false, defaultCorsMethods, defaultCorsHeaders, "*", true, ""},
+		{map[string]string{annotationCorsEnabled: "true"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
+		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowMethods: "POST, GET, OPTIONS", annotationCorsAllowHeaders: "$nginx_version", annotationCorsAllowCredentials: "false", annotationCorsExposeHeaders: "X-CustomResponseHeader"}, true, "POST, GET, OPTIONS", defaultCorsHeaders, []string{"*"}, false, "X-CustomResponseHeader"},
+		{map[string]string{annotationCorsEnabled: "true", annotationCorsAllowCredentials: "false"}, true, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, false, ""},
+		{map[string]string{}, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
+		{nil, false, defaultCorsMethods, defaultCorsHeaders, []string{"*"}, true, ""},
 	}
 
 	for _, foo := range fooAnns {
@@ -243,12 +243,18 @@ func TestCors(t *testing.T) {
 			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowMethods, foo.methods)
 		}
 
-		if r.CorsAllowOrigin != foo.origin {
-			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowOrigin, foo.origin)
+		if len(r.CorsAllowOrigin) != len(foo.origin) {
+			t.Errorf("Lengths of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
+		}
+
+		for i, v := range r.CorsAllowOrigin {
+			if v != foo.origin[i] {
+				t.Errorf("Values of Cors Origins are not equal. Expected %v - Actual %v", r.CorsAllowOrigin, foo.origin)
+			}
 		}
 
 		if r.CorsAllowCredentials != foo.credentials {
-			t.Errorf("Returned %v but expected %v for Cors Methods", r.CorsAllowCredentials, foo.credentials)
+			t.Errorf("Returned %v but expected %v for Cors Credentials", r.CorsAllowCredentials, foo.credentials)
 		}
 
 	}
diff --git a/internal/ingress/annotations/cors/main_test.go b/internal/ingress/annotations/cors/main_test.go
index a65bf12cd..086a59d89 100644
--- a/internal/ingress/annotations/cors/main_test.go
+++ b/internal/ingress/annotations/cors/main_test.go
@@ -110,7 +110,7 @@ func TestIngressCorsConfigValid(t *testing.T) {
 		t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-methods")], nginxCors.CorsAllowMethods)
 	}
 
-	if nginxCors.CorsAllowOrigin != "https://origin123.test.com:4443" {
+	if nginxCors.CorsAllowOrigin[0] != "https://origin123.test.com:4443" {
 		t.Errorf("expected %v but returned %v", data[parser.GetAnnotationWithPrefix("cors-allow-origin")], nginxCors.CorsAllowOrigin)
 	}
 
@@ -164,10 +164,6 @@ func TestIngressCorsConfigInvalid(t *testing.T) {
 		t.Errorf("expected %v but returned %v", defaultCorsHeaders, nginxCors.CorsAllowMethods)
 	}
 
-	if nginxCors.CorsAllowOrigin != "*" {
-		t.Errorf("expected %v but returned %v", "*", nginxCors.CorsAllowOrigin)
-	}
-
 	if nginxCors.CorsExposeHeaders != "" {
 		t.Errorf("expected %v but returned %v", "", nginxCors.CorsExposeHeaders)
 	}
diff --git a/internal/ingress/metric/collectors/admission_test.go b/internal/ingress/metric/collectors/admission_test.go
new file mode 100644
index 000000000..68208ad3e
--- /dev/null
+++ b/internal/ingress/metric/collectors/admission_test.go
@@ -0,0 +1,122 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package collectors
+
+import (
+	"testing"
+
+	"github.com/prometheus/client_golang/prometheus"
+)
+
+func TestAdmissionCounters(t *testing.T) {
+	const (
+		metadataFirst = `
+			# HELP nginx_ingress_controller_admission_config_size The size of the tested configuration
+			# TYPE nginx_ingress_controller_admission_config_size gauge
+			# HELP nginx_ingress_controller_admission_roundtrip_duration The complete duration of the admission controller at the time to process a new event (float seconds)
+			# TYPE nginx_ingress_controller_admission_roundtrip_duration gauge
+		`
+		metadataSecond = `
+			# HELP nginx_ingress_controller_admission_render_ingresses The length of ingresses rendered by the admission controller
+			# TYPE nginx_ingress_controller_admission_render_ingresses gauge
+			# HELP nginx_ingress_controller_admission_tested_duration The processing duration of the admission controller tests (float seconds)
+			# TYPE nginx_ingress_controller_admission_tested_duration gauge
+		`
+		metadataThird = `
+			# HELP nginx_ingress_controller_admission_config_size The size of the tested configuration
+			# TYPE nginx_ingress_controller_admission_config_size gauge
+			# HELP nginx_ingress_controller_admission_render_duration The processing duration of ingresses rendering by the admission controller (float seconds)
+			# TYPE nginx_ingress_controller_admission_render_duration gauge
+			# HELP nginx_ingress_controller_admission_render_ingresses The length of ingresses rendered by the admission controller
+			# TYPE nginx_ingress_controller_admission_render_ingresses gauge
+			# HELP nginx_ingress_controller_admission_roundtrip_duration The complete duration of the admission controller at the time to process a new event (float seconds)
+			# TYPE nginx_ingress_controller_admission_roundtrip_duration gauge
+			# HELP nginx_ingress_controller_admission_tested_ingresses The length of ingresses processed by the admission controller
+			# TYPE nginx_ingress_controller_admission_tested_ingresses gauge
+			# HELP nginx_ingress_controller_admission_tested_duration The processing duration of the admission controller tests (float seconds)
+			# TYPE nginx_ingress_controller_admission_tested_duration gauge
+		`
+	)
+	cases := []struct {
+		name    string
+		test    func(*AdmissionCollector)
+		metrics []string
+		want    string
+	}{
+		{
+			name: "should return 0 as values on a fresh initiated collector",
+			test: func(am *AdmissionCollector) {
+			},
+			want: metadataFirst + `
+				nginx_ingress_controller_admission_config_size{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 0
+				nginx_ingress_controller_admission_roundtrip_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 0
+			`,
+			metrics: []string{"nginx_ingress_controller_admission_config_size", "nginx_ingress_controller_admission_roundtrip_duration"},
+		},
+		{
+			name: "set admission metrics to 1 in all fields and validate next set",
+			test: func(am *AdmissionCollector) {
+				am.SetAdmissionMetrics(1, 1, 1, 1, 1, 1)
+			},
+			want: metadataSecond + `
+				nginx_ingress_controller_admission_render_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 1
+				nginx_ingress_controller_admission_tested_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 1
+			`,
+			metrics: []string{"nginx_ingress_controller_admission_render_ingresses", "nginx_ingress_controller_admission_tested_duration"},
+		},
+		{
+			name: "set admission metrics to 5 in all fields and validate all sets",
+			test: func(am *AdmissionCollector) {
+				am.SetAdmissionMetrics(5, 5, 5, 5, 5, 5)
+			},
+			want: metadataThird + `
+				nginx_ingress_controller_admission_config_size{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_render_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_render_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_roundtrip_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_tested_ingresses{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+				nginx_ingress_controller_admission_tested_duration{controller_class="nginx",controller_namespace="default",controller_pod="pod"} 5
+			`,
+			metrics: []string{
+				"nginx_ingress_controller_admission_config_size",
+				"nginx_ingress_controller_admission_render_duration",
+				"nginx_ingress_controller_admission_render_ingresses",
+				"nginx_ingress_controller_admission_roundtrip_duration",
+				"nginx_ingress_controller_admission_tested_ingresses",
+				"nginx_ingress_controller_admission_tested_duration",
+			},
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			am := NewAdmissionCollector("pod", "default", "nginx")
+			reg := prometheus.NewPedanticRegistry()
+			if err := reg.Register(am); err != nil {
+				t.Errorf("registering collector failed: %s", err)
+			}
+
+			c.test(am)
+
+			if err := GatherAndCompare(am, c.want, c.metrics, reg); err != nil {
+				t.Errorf("unexpected collecting result:\n%s", err)
+			}
+
+			reg.Unregister(am)
+		})
+	}
+}
diff --git a/test/e2e/annotations/cors.go b/test/e2e/annotations/cors.go
index e4cdd32b4..c17eb0b20 100644
--- a/test/e2e/annotations/cors.go
+++ b/test/e2e/annotations/cors.go
@@ -44,10 +44,12 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 		f.WaitForNginxServer(host,
 			func(server string) bool {
 				return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Methods: GET, PUT, POST, DELETE, PATCH, OPTIONS';") &&
-					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: *';") &&
+					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: $http_origin';") &&
 					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Headers: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';") &&
 					strings.Contains(server, "more_set_headers 'Access-Control-Max-Age: 1728000';") &&
-					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';")
+					strings.Contains(server, "more_set_headers 'Access-Control-Allow-Credentials: true';") &&
+					strings.Contains(server, "set $http_origin *;") &&
+					strings.Contains(server, "$cors 'true';")
 			})
 
 		f.HTTPTestClient().
@@ -107,6 +109,7 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 
 	ginkgo.It("should allow origin for cors", func() {
 		host := "cors.foo.com"
+		origin := "https://origin.cors.com:8080"
 		annotations := map[string]string{
 			"nginx.ingress.kubernetes.io/enable-cors":       "true",
 			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin.cors.com:8080",
@@ -115,10 +118,20 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
 		f.EnsureIngress(ing)
 
-		f.WaitForNginxServer(host,
-			func(server string) bool {
-				return strings.Contains(server, "more_set_headers 'Access-Control-Allow-Origin: https://origin.cors.com:8080';")
-			})
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
 	})
 
 	ginkgo.It("should allow headers for cors", func() {
@@ -152,4 +165,450 @@ var _ = framework.DescribeAnnotation("cors-*", func() {
 				return strings.Contains(server, "more_set_headers 'Access-Control-Expose-Headers: X-CustomResponseHeader, X-CustomSecondHeader';")
 			})
 	})
+
+	ginkgo.It("should allow - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		origin := "https://origin.cors.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin.cors.com:8080, https://origin2.cors.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		origin := "http://no.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin2.cors.com, https://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow correct origins - single origin for multiple cors values", func() {
+		host := "cors.foo.com"
+		badOrigin := "origin.cors.com:8080"
+		origin1 := "https://origin2.cors.com"
+		origin2 := "https://origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "origin.cors.com:8080, https://origin2.cors.com, https://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", badOrigin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin1).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin1).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin1})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin2})
+	})
+
+	ginkgo.It("should not break functionality", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "*",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not break functionality - without `*`", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors": "true",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not break functionality with extra domain", func() {
+		host := "cors.foo.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "*, foo.bar.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
+
+	ginkgo.It("should not match", func() {
+		host := "cors.foo.com"
+		origin := "https://fooxbar.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://foo.bar.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - single origin with required port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - single origin with port and origin without port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "https://origin2.cors.com, http://origin.com",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should not allow - single origin without port and origin with required port", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - matching origin with wildcard origin (2 subdomains)", func() {
+		host := "cors.foo.com"
+		origin := "http://foo.origin.cors.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://*.origin.cors.com, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - unmatching origin with wildcard origin (2 subdomains)", func() {
+		host := "cors.foo.com"
+		origin := "http://bar.foo.origin.cors.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://*.origin.cors.com, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow - matching origin+port with wildcard origin", func() {
+		host := "cors.foo.com"
+		origin := "http://abc.origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should not allow - portless origin with wildcard origin", func() {
+		host := "cors.foo.com"
+		origin := "http://abc.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+	})
+
+	ginkgo.It("should allow correct origins - missing subdomain + origin with wildcard origin and correct origin", func() {
+		host := "cors.foo.com"
+		badOrigin := "http://origin.com:8080"
+		origin := "http://bar.origin.com:8080"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "http://origin.cors.com:8080, http://*.origin.com:8080",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", badOrigin).
+			Expect().
+			Headers().NotContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{origin})
+	})
+
+	ginkgo.It("should allow - missing origins (should allow all origins)", func() {
+		host := "cors.foo.com"
+		origin := "http://origin.com"
+		origin2 := "http://book.origin.com"
+		origin3 := "test.origin.com"
+		annotations := map[string]string{
+			"nginx.ingress.kubernetes.io/enable-cors":       "true",
+			"nginx.ingress.kubernetes.io/cors-allow-origin": "      ",
+		}
+
+		ing := framework.NewSingleIngress(host, "/", host, f.Namespace, framework.EchoService, 80, annotations)
+		f.EnsureIngress(ing)
+
+		// the client should still receive a response but browsers should block the request
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin2).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin3).
+			Expect().
+			Headers().ContainsKey("Access-Control-Allow-Origin")
+
+		f.HTTPTestClient().
+			GET("/").
+			WithHeader("Host", host).
+			WithHeader("Origin", origin3).
+			Expect().
+			Status(http.StatusOK).Headers().
+			ValueEqual("Access-Control-Allow-Origin", []string{"*"})
+	})
 })
diff --git a/test/e2e/settings/brotli.go b/test/e2e/settings/brotli.go
new file mode 100644
index 000000000..52092ee83
--- /dev/null
+++ b/test/e2e/settings/brotli.go
@@ -0,0 +1,74 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package settings
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/onsi/ginkgo"
+
+	"k8s.io/ingress-nginx/test/e2e/framework"
+)
+
+var _ = framework.IngressNginxDescribe("brotli", func() {
+	f := framework.NewDefaultFramework("brotli")
+
+	host := "brotli"
+
+	ginkgo.BeforeEach(func() {
+		f.NewHttpbinDeployment()
+	})
+
+	ginkgo.It("should only compress responses that meet the `brotli-min-length` condition", func() {
+		brotliMinLength := 24
+		contentEncoding := "application/octet-stream"
+		f.UpdateNginxConfigMapData("enable-brotli", "true")
+		f.UpdateNginxConfigMapData("brotli-types", contentEncoding)
+		f.UpdateNginxConfigMapData("brotli-min-length", strconv.Itoa(brotliMinLength))
+
+		f.EnsureIngress(framework.NewSingleIngress(host, "/", host, f.Namespace, framework.HTTPBinService, 80, nil))
+
+		f.WaitForNginxConfiguration(
+			func(server string) bool {
+				return strings.Contains(server, fmt.Sprintf("server_name %v", host)) &&
+					strings.Contains(server, "brotli on") &&
+					strings.Contains(server, fmt.Sprintf("brotli_types %v", contentEncoding)) &&
+					strings.Contains(server, fmt.Sprintf("brotli_min_length %d", brotliMinLength))
+			})
+
+		f.HTTPTestClient().
+			GET(fmt.Sprintf("/bytes/%d", brotliMinLength)).
+			WithHeader("Host", host).
+			WithHeader("Accept-Encoding", "br").
+			Expect().
+			Status(http.StatusOK).
+			ContentType(contentEncoding).
+			ContentEncoding("br")
+
+		f.HTTPTestClient().
+			GET(fmt.Sprintf("/bytes/%d", brotliMinLength-1)).
+			WithHeader("Host", host).
+			WithHeader("Accept-Encoding", "br").
+			Expect().
+			Status(http.StatusOK).
+			ContentType(contentEncoding).
+			ContentEncoding()
+	})
+})