Repository: jetstack/cert-manager
Pull request #3288 (2020-09-17 09:15:05)

Add support for IPs in ACME
**What this PR does / why we need it**:
Let's encrypt announced support for validating IP ownership with ACME. This PR adds support for this inside cert-manager to validate these with HTTP01. 
Tested against pebble and it is working fine! 

**Which issue this PR fixes**:
fixes https://github.com/jetstack/cert-manager/issues/3285

**Release note**:
<!--  Steps to write your release note:
1. Use the release-note-* labels to set the release note state (if you have access)
2. Enter your extended release note in the below block; leaving it blank means using the PR title as the release note. If no release note is required, just write `NONE`.
-->
```release-note
Add support for issuing IP certificates in ACME
```


jetstack-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/jetstack/cert-manager/pull/3288#" title="Author self-approved">meyskens</a>*

The full list of commands accepted by this bot can be found [here](?repo=jetstack%2Fcert-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/jetstack/cert-manager/blob/master/OWNERS)~~ [meyskens]

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":[]} -->
meyskens: /kind feature
jetstack-bot: @meyskens: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | 1c067d18e284e6f02acbf937619dda5320786430 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1306522135497281536) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | 1c067d18e284e6f02acbf937619dda5320786430 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1306522135497281536) | `/test pull-cert-manager-bazel`
pull-cert-manager-e2e-v1-19 | 1c067d18e284e6f02acbf937619dda5320786430 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1306522135493087232) | `/test pull-cert-manager-e2e-v1-19`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-e2e-v1-19 | 1c067d18e284e6f02acbf937619dda5320786430 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1306522135493087232) | `/test pull-cert-manager-e2e-v1-19`
pull-cert-manager-bazel | 715e4f4054c0547b7bca6fa26c865888cf1dabfe | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1308762191238795265) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-e2e-v1-19 | 1c067d18e284e6f02acbf937619dda5320786430 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1306522135493087232) | `/test pull-cert-manager-e2e-v1-19`
pull-cert-manager-bazel | e037b54749e4b30190d0ad03c8425b1a0e33716a | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1308767350396293120) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | e037b54749e4b30190d0ad03c8425b1a0e33716a | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1308767350396293120) | `/test pull-cert-manager-bazel`
pull-cert-manager-e2e-v1-19 | e037b54749e4b30190d0ad03c8425b1a0e33716a | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1308767350421458944) | `/test pull-cert-manager-e2e-v1-19`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-e2e-v1-19 | e037b54749e4b30190d0ad03c8425b1a0e33716a | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1308767350421458944) | `/test pull-cert-manager-e2e-v1-19`
pull-cert-manager-bazel | 42eb4786eacbab282f2a32f6ca9270590ab20ab9 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1308782575682064385) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | 42eb4786eacbab282f2a32f6ca9270590ab20ab9 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1308782575682064385) | `/test pull-cert-manager-bazel`
pull-cert-manager-e2e-v1-19 | 42eb4786eacbab282f2a32f6ca9270590ab20ab9 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-e2e-v1-19/1308782575686258689) | `/test pull-cert-manager-e2e-v1-19`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | 4d7611e4681479580c3b525e61dad4313dc2c056 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1309118840059203585) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
meyskens: Ready for review now!
jetstack-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/jetstack/cert-manager/pull/3288#" title="Author self-approved">meyskens</a>*, *<a href="https://github.com/jetstack/cert-manager/pull/3288#pullrequestreview-495759631" title="Approved">wallrj</a>*

The full list of commands accepted by this bot can be found [here](?repo=jetstack%2Fcert-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/jetstack/cert-manager/blob/master/OWNERS)~~ [meyskens,wallrj]

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":[]} -->
meyskens: Thanks @wallrj will implement those!
jetstack-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/jetstack/cert-manager/pull/3288#" title="Author self-approved">meyskens</a>*, *<a href="https://github.com/jetstack/cert-manager/pull/3288#pullrequestreview-500971962" title="LGTM">wallrj</a>*

The full list of commands accepted by this bot can be found [here](?repo=jetstack%2Fcert-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/jetstack/cert-manager/blob/master/OWNERS)~~ [meyskens,wallrj]

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":[]} -->
jetstack-bot: New changes are detected. LGTM label has been removed.
jetstack-bot: @meyskens: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | ee3dc992032d6322c602a883c4ec7cb6894f4d57 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1313493334383136769) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | ee3dc992032d6322c602a883c4ec7cb6894f4d57 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1313493334383136769) | `/test pull-cert-manager-bazel`
pull-cert-manager-deps | ee3dc992032d6322c602a883c4ec7cb6894f4d57 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-deps/1313493334383136770) | `/test pull-cert-manager-deps`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
jetstack-bot: @meyskens: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-deps | ee3dc992032d6322c602a883c4ec7cb6894f4d57 | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-deps/1313493334383136770) | `/test pull-cert-manager-deps`
pull-cert-manager-bazel | 989a3c4fc9302e71c03665afc3658321903e801d | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1313504407479914497) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
meyskens: /retest

not sure what is up with the network today...
meyskens: /unhold
meyskens: ping @wallrj for re-review
jetstack-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/jetstack/cert-manager/pull/3288#" title="Author self-approved">meyskens</a>*, *<a href="https://github.com/jetstack/cert-manager/pull/3288#pullrequestreview-504769752" title="LGTM">wallrj</a>*

The full list of commands accepted by this bot can be found [here](?repo=jetstack%2Fcert-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/jetstack/cert-manager/blob/master/OWNERS)~~ [meyskens,wallrj]

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":[]} -->
jetstack-bot: @meyskens: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-cert-manager-bazel | 989a3c4fc9302e71c03665afc3658321903e801d | [link](https://prow.build-infra.jetstack.net/view/gcs/jetstack-logs/pr-logs/pull/jetstack_cert-manager/3288/pull-cert-manager-bazel/1314193045096239105) | `/test pull-cert-manager-bazel`

[Full PR test history](https://prow.build-infra.jetstack.net/pr-history?org=jetstack&repo=cert-manager&pr=3288). [Your PR dashboard](https://jetstack-build-infra.appspot.com/pr/meyskens). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/jetstack/cert-manager/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 -->
meyskens: seems to need rebasing as e2e tests got improved

/hold

Patch:
diff --git a/deploy/crds/crd-orders.v1beta1.yaml b/deploy/crds/crd-orders.v1beta1.yaml
index 8ab56a070..e5dbcc2af 100644
--- a/deploy/crds/crd-orders.v1beta1.yaml
+++ b/deploy/crds/crd-orders.v1beta1.yaml
@@ -68,11 +68,10 @@ spec:
               type: object
               required:
                 - csr
-                - dnsNames
                 - issuerRef
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 csr:
                   description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.
@@ -83,6 +82,11 @@ spec:
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -200,11 +204,10 @@ spec:
               type: object
               required:
                 - csr
-                - dnsNames
                 - issuerRef
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 csr:
                   description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.
@@ -215,6 +218,11 @@ spec:
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -332,18 +340,22 @@ spec:
             spec:
               type: object
               required:
-                - dnsNames
                 - issuerRef
                 - request
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 dnsNames:
                   description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -465,18 +477,22 @@ spec:
             spec:
               type: object
               required:
-                - dnsNames
                 - issuerRef
                 - request
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 dnsNames:
                   description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
diff --git a/deploy/crds/crd-orders.yaml b/deploy/crds/crd-orders.yaml
index c46cc9703..ea48e7332 100644
--- a/deploy/crds/crd-orders.yaml
+++ b/deploy/crds/crd-orders.yaml
@@ -68,11 +68,10 @@ spec:
               type: object
               required:
                 - csr
-                - dnsNames
                 - issuerRef
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 csr:
                   description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.
@@ -83,6 +82,11 @@ spec:
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -218,11 +222,10 @@ spec:
               type: object
               required:
                 - csr
-                - dnsNames
                 - issuerRef
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 csr:
                   description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.
@@ -233,6 +236,11 @@ spec:
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -368,18 +376,22 @@ spec:
             spec:
               type: object
               required:
-                - dnsNames
                 - issuerRef
                 - request
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 dnsNames:
                   description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
@@ -519,18 +531,22 @@ spec:
             spec:
               type: object
               required:
-                - dnsNames
                 - issuerRef
                 - request
               properties:
                 commonName:
-                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames`. This field must match the corresponding field on the DER encoded CSR.
+                  description: CommonName is the common name as specified on the DER encoded CSR. If specified, this value must also be present in `dnsNames` or `ipAddresses`. This field must match the corresponding field on the DER encoded CSR.
                   type: string
                 dnsNames:
                   description: DNSNames is a list of DNS names that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
                   type: array
                   items:
                     type: string
+                ipAddresses:
+                  description: IPAddresses is a list of IP addresses that should be included as part of the Order validation process. This field must match the corresponding field on the DER encoded CSR.
+                  type: array
+                  items:
+                    type: string
                 issuerRef:
                   description: IssuerRef references a properly configured ACME-type Issuer which should be used to create this Order. If the Issuer does not exist, processing will be retried. If the Issuer is not an 'ACME' Issuer, an error will be returned and the Order will be marked as failed.
                   type: object
diff --git a/devel/lib/lib.sh b/devel/lib/lib.sh
index a6789943c..353a82202 100644
--- a/devel/lib/lib.sh
+++ b/devel/lib/lib.sh
@@ -33,6 +33,7 @@ export IS_OPENSHIFT="${IS_OPENSHIFT:-"false"}"
 export OPENSHIFT_VERSION="${OPENSHIFT_VERSION:-"3.11"}"
 export SERVICE_IP_PREFIX="${SERVICE_IP_PREFIX:-10.0.0}"
 export DNS_SERVER="${SERVICE_IP_PREFIX}.16"
+export INGRESS_IP="${SERVICE_IP_PREFIX}.15"
 
 # setup_tools will build and set up the environment to use bazel-provided
 # versions of the tools required for development
diff --git a/pkg/apis/acme/v1/types_order.go b/pkg/apis/acme/v1/types_order.go
index e08ac5c93..9650ddfd3 100644
--- a/pkg/apis/acme/v1/types_order.go
+++ b/pkg/apis/acme/v1/types_order.go
@@ -61,7 +61,7 @@ type OrderSpec struct {
 	IssuerRef cmmeta.ObjectReference `json:"issuerRef"`
 
 	// CommonName is the common name as specified on the DER encoded CSR.
-	// If specified, this value must also be present in `dnsNames`.
+	// If specified, this value must also be present in `dnsNames` or `ipAddresses`.
 	// This field must match the corresponding field on the DER encoded CSR.
 	// +optional
 	CommonName string `json:"commonName,omitempty"`
@@ -69,7 +69,14 @@ type OrderSpec struct {
 	// DNSNames is a list of DNS names that should be included as part of the Order
 	// validation process.
 	// This field must match the corresponding field on the DER encoded CSR.
-	DNSNames []string `json:"dnsNames"`
+	//+optonal
+	DNSNames []string `json:"dnsNames,omitempty"`
+
+	// IPAddresses is a list of IP addresses that should be included as part of the Order
+	// validation process.
+	// This field must match the corresponding field on the DER encoded CSR.
+	// +optional
+	IPAddresses []string `json:"ipAddresses,omitempty"`
 }
 
 type OrderStatus struct {
diff --git a/pkg/apis/acme/v1/zz_generated.deepcopy.go b/pkg/apis/acme/v1/zz_generated.deepcopy.go
index 093dadc1f..eef202c69 100644
--- a/pkg/apis/acme/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/acme/v1/zz_generated.deepcopy.go
@@ -784,6 +784,11 @@ func (in *OrderSpec) DeepCopyInto(out *OrderSpec) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.IPAddresses != nil {
+		in, out := &in.IPAddresses, &out.IPAddresses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
diff --git a/pkg/apis/acme/v1alpha2/types_order.go b/pkg/apis/acme/v1alpha2/types_order.go
index c4d585ef5..0df925288 100644
--- a/pkg/apis/acme/v1alpha2/types_order.go
+++ b/pkg/apis/acme/v1alpha2/types_order.go
@@ -59,7 +59,7 @@ type OrderSpec struct {
 	IssuerRef cmmeta.ObjectReference `json:"issuerRef"`
 
 	// CommonName is the common name as specified on the DER encoded CSR.
-	// If specified, this value must also be present in `dnsNames`.
+	// If specified, this value must also be present in `dnsNames` or `ipAddresses`.
 	// This field must match the corresponding field on the DER encoded CSR.
 	// +optional
 	CommonName string `json:"commonName,omitempty"`
@@ -67,7 +67,14 @@ type OrderSpec struct {
 	// DNSNames is a list of DNS names that should be included as part of the Order
 	// validation process.
 	// This field must match the corresponding field on the DER encoded CSR.
-	DNSNames []string `json:"dnsNames"`
+	//+optonal
+	DNSNames []string `json:"dnsNames,omitempty"`
+
+	// IPAddresses is a list of IP addresses that should be included as part of the Order
+	// validation process.
+	// This field must match the corresponding field on the DER encoded CSR.
+	// +optional
+	IPAddresses []string `json:"ipAddresses,omitempty"`
 }
 
 type OrderStatus struct {
diff --git a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go
index 5c36bac90..373a118ef 100644
--- a/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go
+++ b/pkg/apis/acme/v1alpha2/zz_generated.deepcopy.go
@@ -784,6 +784,11 @@ func (in *OrderSpec) DeepCopyInto(out *OrderSpec) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.IPAddresses != nil {
+		in, out := &in.IPAddresses, &out.IPAddresses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
diff --git a/pkg/apis/acme/v1alpha3/types_order.go b/pkg/apis/acme/v1alpha3/types_order.go
index 133384391..82b7f67aa 100644
--- a/pkg/apis/acme/v1alpha3/types_order.go
+++ b/pkg/apis/acme/v1alpha3/types_order.go
@@ -59,7 +59,7 @@ type OrderSpec struct {
 	IssuerRef cmmeta.ObjectReference `json:"issuerRef"`
 
 	// CommonName is the common name as specified on the DER encoded CSR.
-	// If specified, this value must also be present in `dnsNames`.
+	// If specified, this value must also be present in `dnsNames` or `ipAddresses`.
 	// This field must match the corresponding field on the DER encoded CSR.
 	// +optional
 	CommonName string `json:"commonName,omitempty"`
@@ -67,7 +67,14 @@ type OrderSpec struct {
 	// DNSNames is a list of DNS names that should be included as part of the Order
 	// validation process.
 	// This field must match the corresponding field on the DER encoded CSR.
-	DNSNames []string `json:"dnsNames"`
+	//+optonal
+	DNSNames []string `json:"dnsNames,omitempty"`
+
+	// IPAddresses is a list of IP addresses that should be included as part of the Order
+	// validation process.
+	// This field must match the corresponding field on the DER encoded CSR.
+	// +optional
+	IPAddresses []string `json:"ipAddresses,omitempty"`
 }
 
 type OrderStatus struct {
diff --git a/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go b/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go
index 4d99267a2..6d2113872 100644
--- a/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go
+++ b/pkg/apis/acme/v1alpha3/zz_generated.deepcopy.go
@@ -784,6 +784,11 @@ func (in *OrderSpec) DeepCopyInto(out *OrderSpec) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.IPAddresses != nil {
+		in, out := &in.IPAddresses, &out.IPAddresses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
diff --git a/pkg/apis/acme/v1beta1/types_order.go b/pkg/apis/acme/v1beta1/types_order.go
index 0908354a5..965a9337c 100644
--- a/pkg/apis/acme/v1beta1/types_order.go
+++ b/pkg/apis/acme/v1beta1/types_order.go
@@ -60,7 +60,7 @@ type OrderSpec struct {
 	IssuerRef cmmeta.ObjectReference `json:"issuerRef"`
 
 	// CommonName is the common name as specified on the DER encoded CSR.
-	// If specified, this value must also be present in `dnsNames`.
+	// If specified, this value must also be present in `dnsNames` or `ipAddresses`.
 	// This field must match the corresponding field on the DER encoded CSR.
 	// +optional
 	CommonName string `json:"commonName,omitempty"`
@@ -68,7 +68,14 @@ type OrderSpec struct {
 	// DNSNames is a list of DNS names that should be included as part of the Order
 	// validation process.
 	// This field must match the corresponding field on the DER encoded CSR.
-	DNSNames []string `json:"dnsNames"`
+	//+optonal
+	DNSNames []string `json:"dnsNames,omitempty"`
+
+	// IPAddresses is a list of IP addresses that should be included as part of the Order
+	// validation process.
+	// This field must match the corresponding field on the DER encoded CSR.
+	// +optional
+	IPAddresses []string `json:"ipAddresses,omitempty"`
 }
 
 type OrderStatus struct {
diff --git a/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go b/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go
index 64abd212b..1ef56a911 100644
--- a/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go
+++ b/pkg/apis/acme/v1beta1/zz_generated.deepcopy.go
@@ -784,6 +784,11 @@ func (in *OrderSpec) DeepCopyInto(out *OrderSpec) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.IPAddresses != nil {
+		in, out := &in.IPAddresses, &out.IPAddresses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
diff --git a/pkg/controller/acmeorders/sync.go b/pkg/controller/acmeorders/sync.go
index 91027b0cf..74a9187ce 100644
--- a/pkg/controller/acmeorders/sync.go
+++ b/pkg/controller/acmeorders/sync.go
@@ -188,12 +188,17 @@ func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cm
 	}
 	log.V(logf.DebugLevel).Info("order URL not set, submitting Order to ACME server")
 
-	identifierSet := sets.NewString(o.Spec.DNSNames...)
+	dnsIdentifierSet := sets.NewString(o.Spec.DNSNames...)
 	if o.Spec.CommonName != "" {
-		identifierSet.Insert(o.Spec.CommonName)
+		dnsIdentifierSet.Insert(o.Spec.CommonName)
 	}
-	log.V(logf.DebugLevel).Info("build set of domains for Order", "domains", identifierSet.List())
-	authzIDs := acmeapi.DomainIDs(identifierSet.List()...)
+	log.V(logf.DebugLevel).Info("build set of domains for Order", "domains", dnsIdentifierSet.List())
+
+	ipIdentifierSet := sets.NewString(o.Spec.IPAddresses...)
+	log.V(logf.DebugLevel).Info("build set of IPs for Order", "domains", dnsIdentifierSet.List())
+
+	authzIDs := acmeapi.DomainIDs(dnsIdentifierSet.List()...)
+	authzIDs = append(authzIDs, acmeapi.IPIDs(ipIdentifierSet.List()...)...)
 	// create a new order with the acme server
 	acmeOrder, err := cl.AuthorizeOrder(ctx, authzIDs)
 	if acmeErr, ok := err.(*acmeapi.Error); ok {
diff --git a/pkg/controller/certificaterequests/acme/acme.go b/pkg/controller/certificaterequests/acme/acme.go
index 29bfee2a6..3829706f5 100644
--- a/pkg/controller/certificaterequests/acme/acme.go
+++ b/pkg/controller/certificaterequests/acme/acme.go
@@ -92,10 +92,10 @@ func (a *ACME) Sign(ctx context.Context, cr *v1.CertificateRequest, issuer v1.Ge
 		return nil, nil
 	}
 
-	// If the CommonName is also not present in the DNS names of the Request then hard fail.
-	if len(csr.Subject.CommonName) > 0 && !util.Contains(csr.DNSNames, csr.Subject.CommonName) {
-		err = fmt.Errorf("%q does not exist in %s", csr.Subject.CommonName, csr.DNSNames)
-		message := "The CSR PEM requests a commonName that is not present in the list of dnsNames. If a commonName is set, ACME requires that the value is also present in the list of dnsNames"
+	// If the CommonName is also not present in the DNS names or IP Addresses of the Request then hard fail.
+	if len(csr.Subject.CommonName) > 0 && !util.Contains(csr.DNSNames, csr.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(csr.IPAddresses), csr.Subject.CommonName) {
+		err = fmt.Errorf("%q does not exist in %s or %s", csr.Subject.CommonName, csr.DNSNames, pki.IPAddressesToString(csr.IPAddresses))
+		message := "The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses"
 
 		a.reporter.Failed(cr, err, "InvalidOrder", message)
 
@@ -200,11 +200,22 @@ func (a *ACME) Sign(ctx context.Context, cr *v1.CertificateRequest, issuer v1.Ge
 
 // Build order. If we error here it is a terminating failure.
 func buildOrder(cr *v1.CertificateRequest, csr *x509.CertificateRequest) (*cmacme.Order, error) {
+	var ipAddresses []string
+	for _, ip := range csr.IPAddresses {
+		ipAddresses = append(ipAddresses, ip.String())
+	}
+
+	var dnsNames []string
+	if csr.DNSNames != nil {
+		dnsNames = csr.DNSNames
+	}
+
 	spec := cmacme.OrderSpec{
-		Request:    cr.Spec.Request,
-		IssuerRef:  cr.Spec.IssuerRef,
-		CommonName: csr.Subject.CommonName,
-		DNSNames:   csr.DNSNames,
+		Request:     cr.Spec.Request,
+		IssuerRef:   cr.Spec.IssuerRef,
+		CommonName:  csr.Subject.CommonName,
+		DNSNames:    dnsNames,
+		IPAddresses: ipAddresses,
 	}
 
 	computeNameSpec := spec.DeepCopy()
diff --git a/pkg/internal/apis/acme/types_order.go b/pkg/internal/apis/acme/types_order.go
index 8e7eb5707..bd7c3658a 100644
--- a/pkg/internal/apis/acme/types_order.go
+++ b/pkg/internal/apis/acme/types_order.go
@@ -57,7 +57,7 @@ type OrderSpec struct {
 	IssuerRef cmmeta.ObjectReference
 
 	// CommonName is the common name as specified on the DER encoded CSR.
-	// If specified, this value must also be present in `dnsNames`.
+	// If specified, this value must also be present in `dnsNames` or `ipAddresses`.
 	// This field must match the corresponding field on the DER encoded CSR.
 	CommonName string
 
@@ -65,6 +65,11 @@ type OrderSpec struct {
 	// validation process.
 	// This field must match the corresponding field on the DER encoded CSR.
 	DNSNames []string
+
+	// IPAddresses is a list of IP addresses that should be included as part of the Order
+	// validation process.
+	// This field must match the corresponding field on the DER encoded CSR.
+	IPAddresses []string
 }
 
 type OrderStatus struct {
diff --git a/pkg/internal/apis/acme/v1/zz_generated.conversion.go b/pkg/internal/apis/acme/v1/zz_generated.conversion.go
index ef1b10e52..40686f1e5 100644
--- a/pkg/internal/apis/acme/v1/zz_generated.conversion.go
+++ b/pkg/internal/apis/acme/v1/zz_generated.conversion.go
@@ -1244,6 +1244,7 @@ func autoConvert_v1_OrderSpec_To_acme_OrderSpec(in *v1.OrderSpec, out *acme.Orde
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
@@ -1260,6 +1261,7 @@ func autoConvert_acme_OrderSpec_To_v1_OrderSpec(in *acme.OrderSpec, out *v1.Orde
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
diff --git a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go
index 108b746a9..724256d52 100644
--- a/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go
+++ b/pkg/internal/apis/acme/v1alpha2/zz_generated.conversion.go
@@ -1254,6 +1254,7 @@ func autoConvert_v1alpha2_OrderSpec_To_acme_OrderSpec(in *v1alpha2.OrderSpec, ou
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
@@ -1265,6 +1266,7 @@ func autoConvert_acme_OrderSpec_To_v1alpha2_OrderSpec(in *acme.OrderSpec, out *v
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
diff --git a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go
index b7d851a33..41a421080 100644
--- a/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go
+++ b/pkg/internal/apis/acme/v1alpha3/zz_generated.conversion.go
@@ -1254,6 +1254,7 @@ func autoConvert_v1alpha3_OrderSpec_To_acme_OrderSpec(in *v1alpha3.OrderSpec, ou
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
@@ -1265,6 +1266,7 @@ func autoConvert_acme_OrderSpec_To_v1alpha3_OrderSpec(in *acme.OrderSpec, out *v
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
diff --git a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go
index dcfff9511..be3ea4e26 100644
--- a/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go
+++ b/pkg/internal/apis/acme/v1beta1/zz_generated.conversion.go
@@ -1244,6 +1244,7 @@ func autoConvert_v1beta1_OrderSpec_To_acme_OrderSpec(in *v1beta1.OrderSpec, out
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
@@ -1260,6 +1261,7 @@ func autoConvert_acme_OrderSpec_To_v1beta1_OrderSpec(in *acme.OrderSpec, out *v1
 	}
 	out.CommonName = in.CommonName
 	out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames))
+	out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses))
 	return nil
 }
 
diff --git a/pkg/internal/apis/acme/zz_generated.deepcopy.go b/pkg/internal/apis/acme/zz_generated.deepcopy.go
index 67439df0f..c06f36bdd 100644
--- a/pkg/internal/apis/acme/zz_generated.deepcopy.go
+++ b/pkg/internal/apis/acme/zz_generated.deepcopy.go
@@ -784,6 +784,11 @@ func (in *OrderSpec) DeepCopyInto(out *OrderSpec) {
 		*out = make([]string, len(*in))
 		copy(*out, *in)
 	}
+	if in.IPAddresses != nil {
+		in, out := &in.IPAddresses, &out.IPAddresses
+		*out = make([]string, len(*in))
+		copy(*out, *in)
+	}
 	return
 }
 
diff --git a/pkg/internal/apis/certmanager/validation/certificate.go b/pkg/internal/apis/certmanager/validation/certificate.go
index 744b44cdd..7fde9623b 100644
--- a/pkg/internal/apis/certmanager/validation/certificate.go
+++ b/pkg/internal/apis/certmanager/validation/certificate.go
@@ -40,8 +40,8 @@ func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field.
 
 	el = append(el, validateIssuerRef(crt.IssuerRef, fldPath)...)
 
-	if len(crt.CommonName) == 0 && len(crt.DNSNames) == 0 && len(crt.URISANs) == 0 && len(crt.EmailSANs) == 0 {
-		el = append(el, field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris or emailAddresses must be set"))
+	if len(crt.CommonName) == 0 && len(crt.DNSNames) == 0 && len(crt.URISANs) == 0 && len(crt.EmailSANs) == 0 && len(crt.IPAddresses) == 0 {
+		el = append(el, field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris ipAddresses, or emailAddresses must be set"))
 	}
 
 	// if a common name has been specified, ensure it is no longer than 64 chars
diff --git a/pkg/issuer/acme/http/BUILD.bazel b/pkg/issuer/acme/http/BUILD.bazel
index 097b8574a..6e4918a6c 100644
--- a/pkg/issuer/acme/http/BUILD.bazel
+++ b/pkg/issuer/acme/http/BUILD.bazel
@@ -27,6 +27,7 @@ go_library(
         "@io_k8s_apimachinery//pkg/util/intstr:go_default_library",
         "@io_k8s_client_go//listers/core/v1:go_default_library",
         "@io_k8s_client_go//listers/extensions/v1beta1:go_default_library",
+        "@io_k8s_utils//net:go_default_library",
     ],
 )
 
diff --git a/pkg/issuer/acme/http/http.go b/pkg/issuer/acme/http/http.go
index f13fc37e2..bb0405a1b 100644
--- a/pkg/issuer/acme/http/http.go
+++ b/pkg/issuer/acme/http/http.go
@@ -21,11 +21,14 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"net/url"
 	"strings"
 	"time"
 
+	k8snet "k8s.io/utils/net"
+
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 	corev1listers "k8s.io/client-go/listers/core/v1"
 	extv1beta1listers "k8s.io/client-go/listers/extensions/v1beta1"
@@ -157,6 +160,11 @@ func (s *Solver) buildChallengeUrl(ch *cmacme.Challenge) *url.URL {
 	url := &url.URL{}
 	url.Scheme = "http"
 	url.Host = ch.Spec.DNSName
+
+	// we need brackets for IPv6 addresses for the HTTP client to work
+	if k8snet.IsIPv6(net.ParseIP(url.Host)) {
+		url.Host = fmt.Sprintf("[%s]", url.Host)
+	}
 	url.Path = fmt.Sprintf("%s/%s", solver.HTTPChallengePath, ch.Spec.Token)
 
 	return url
diff --git a/pkg/issuer/acme/http/ingress.go b/pkg/issuer/acme/http/ingress.go
index abeffc83a..76bce56e4 100644
--- a/pkg/issuer/acme/http/ingress.go
+++ b/pkg/issuer/acme/http/ingress.go
@@ -19,6 +19,7 @@ package http
 import (
 	"context"
 	"fmt"
+	"net"
 
 	extv1beta1 "k8s.io/api/extensions/v1beta1"
 	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
@@ -156,6 +157,11 @@ func buildIngressResource(ch *cmacme.Challenge, svcName string) (*extv1beta1.Ing
 
 	ingPathToAdd := ingressPath(ch.Spec.Token, svcName)
 
+	httpHost := ch.Spec.DNSName
+	// if we need to verify ownership of an IP the challenge should propagate on all hosts
+	if net.ParseIP(httpHost) != nil {
+		httpHost = ""
+	}
 	return &extv1beta1.Ingress{
 		ObjectMeta: metav1.ObjectMeta{
 			GenerateName:    "cm-acme-http-solver-",
@@ -167,7 +173,7 @@ func buildIngressResource(ch *cmacme.Challenge, svcName string) (*extv1beta1.Ing
 		Spec: extv1beta1.IngressSpec{
 			Rules: []extv1beta1.IngressRule{
 				{
-					Host: ch.Spec.DNSName,
+					Host: httpHost,
 					IngressRuleValue: extv1beta1.IngressRuleValue{
 						HTTP: &extv1beta1.HTTPIngressRuleValue{
 							Paths: []extv1beta1.HTTPIngressPath{ingPathToAdd},
diff --git a/pkg/util/pki/csr.go b/pkg/util/pki/csr.go
index 8b059c9fe..b941e529b 100644
--- a/pkg/util/pki/csr.go
+++ b/pkg/util/pki/csr.go
@@ -193,7 +193,7 @@ func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) {
 		return nil, err
 	}
 
-	if len(commonName) == 0 && len(dnsNames) == 0 && len(uriNames) == 0 && len(crt.Spec.EmailAddresses) == 0 {
+	if len(commonName) == 0 && len(dnsNames) == 0 && len(uriNames) == 0 && len(crt.Spec.EmailAddresses) == 0 && len(crt.Spec.IPAddresses) == 0 {
 		return nil, fmt.Errorf("no common name, DNS name, URI SAN, or Email SAN specified on certificate")
 	}
 


Test patch:
diff --git a/devel/run-e2e.sh b/devel/run-e2e.sh
index f7125bc77..5f201ced6 100755
--- a/devel/run-e2e.sh
+++ b/devel/run-e2e.sh
@@ -45,4 +45,5 @@ ginkgo -nodes 10 -flakeAttempts ${FLAKE_ATTEMPTS:-1} \
 	--repo-root="${REPO_ROOT}" \
 	--report-dir="${ARTIFACTS:-$REPO_ROOT/_artifacts}" \
 	--acme-dns-server="$DNS_SERVER" \
+	--acme-ingress-ip="$INGRESS_IP" \
 	"$@"
diff --git a/pkg/controller/acmeorders/sync_test.go b/pkg/controller/acmeorders/sync_test.go
index e433db087..65ef1aa47 100644
--- a/pkg/controller/acmeorders/sync_test.go
+++ b/pkg/controller/acmeorders/sync_test.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"encoding/pem"
 	"errors"
+	"fmt"
 	"testing"
 	"time"
 
@@ -42,6 +43,16 @@ func TestSyncHappyPath(t *testing.T) {
 	nowMetaTime := metav1.NewTime(nowTime)
 	fixedClock := fakeclock.NewFakeClock(nowTime)
 
+	testIssuerHTTP01 := gen.Issuer("testissuer", gen.SetIssuerACME(cmacme.ACMEIssuer{
+		Solvers: []cmacme.ACMEChallengeSolver{
+			{
+				HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
+					Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{},
+				},
+			},
+		},
+	}))
+
 	testIssuerHTTP01TestCom := gen.Issuer("testissuer", gen.SetIssuerACME(cmacme.ACMEIssuer{
 		Solvers: []cmacme.ACMEChallengeSolver{
 			{
@@ -76,6 +87,8 @@ func TestSyncHappyPath(t *testing.T) {
 		}),
 	)
 
+	testOrderIP := gen.Order("testorder", gen.SetOrderIssuer(cmmeta.ObjectReference{Name: testIssuerHTTP01.Name}), gen.SetOrderIPAddresses("10.0.0.1"))
+
 	pendingStatus := cmacme.OrderStatus{
 		State:       cmacme.Pending,
 		URL:         "http://testurl.com/abcde",
@@ -228,7 +241,48 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt
 					return testACMEOrderPending, nil
 				},
 				FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) {
-					// TODO: assert url = "http://authzurl"
+					if url != "http://authzurl" {
+						return nil, fmt.Errorf("Invalid URL: expected http://authzurl got %q", url)
+					}
+					return testACMEAuthorizationPending, nil
+				},
+				FakeHTTP01ChallengeResponse: func(s string) (string, error) {
+					// TODO: assert s = "token"
+					return "key", nil
+				},
+			},
+		},
+		"create a new order with the acme server with an IP address": {
+			order: testOrderIP,
+			builder: &testpkg.Builder{
+				CertManagerObjects: []runtime.Object{testIssuerHTTP01, testOrderIP},
+				ExpectedActions: []testpkg.Action{
+					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"),
+						"status",
+						testOrderPending.Namespace,
+						gen.OrderFrom(testOrderIP, gen.SetOrderStatus(cmacme.OrderStatus{
+							State:       cmacme.Pending,
+							URL:         "http://testurl.com/abcde",
+							FinalizeURL: "http://testurl.com/abcde/finalize",
+							Authorizations: []cmacme.ACMEAuthorization{
+								{
+									URL: "http://authzurl",
+								},
+							},
+						})))),
+				},
+			},
+			acmeClient: &acmecl.FakeACME{
+				FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) {
+					if id[0].Value != "10.0.0.1" || id[0].Type != "ip" {
+						return nil, errors.New("AuthzID needs to be the IP")
+					}
+					return testACMEOrderPending, nil
+				},
+				FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) {
+					if url != "http://authzurl" {
+						return nil, fmt.Errorf("Invalid URL: expected http://authzurl got %q", url)
+					}
 					return testACMEAuthorizationPending, nil
 				},
 				FakeHTTP01ChallengeResponse: func(s string) (string, error) {
diff --git a/pkg/controller/certificaterequests/acme/acme_test.go b/pkg/controller/certificaterequests/acme/acme_test.go
index b83ed9d77..f869dbd20 100644
--- a/pkg/controller/certificaterequests/acme/acme_test.go
+++ b/pkg/controller/certificaterequests/acme/acme_test.go
@@ -24,6 +24,7 @@ import (
 	"crypto/x509/pkix"
 	"encoding/pem"
 	"errors"
+	"net"
 	"testing"
 	"time"
 
@@ -63,8 +64,34 @@ func generateCSR(t *testing.T, secretKey crypto.Signer, commonName string, dnsNa
 
 	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey)
 	if err != nil {
-		t.Error(err)
-		t.FailNow()
+		t.Fatal(err)
+	}
+
+	csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
+
+	return csr
+}
+
+func generateCSRWithIPs(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames []string, ips []string) []byte {
+	// The CommonName of the certificate request must also be present in the DNS
+	// Names.
+
+	var certIPs []net.IP
+	for _, ip := range ips {
+		certIPs = append(certIPs, net.ParseIP(ip))
+	}
+	template := x509.CertificateRequest{
+		Subject: pkix.Name{
+			CommonName: commonName,
+		},
+		SignatureAlgorithm: x509.SHA256WithRSA,
+		DNSNames:           dnsNames,
+		IPAddresses:        certIPs,
+	}
+
+	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey)
+	if err != nil {
+		t.Fatal(err)
 	}
 
 	csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
@@ -83,8 +110,7 @@ func TestSign(t *testing.T) {
 
 	sk, err := pki.GenerateRSAPrivateKey(2048)
 	if err != nil {
-		t.Error(err)
-		t.FailNow()
+		t.Fatal(err)
 	}
 
 	csrPEM := generateCSR(t, sk, "example.com", "example.com", "foo.com")
@@ -103,8 +129,7 @@ func TestSign(t *testing.T) {
 
 	csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
 	if err != nil {
-		t.Error(err)
-		t.FailNow()
+		t.Fatal(err)
 	}
 
 	template, err := pki.GenerateTemplateFromCertificateRequest(baseCR)
@@ -114,14 +139,23 @@ func TestSign(t *testing.T) {
 
 	certPEM, _, err := pki.SignCSRTemplate([]*x509.Certificate{template}, sk, template)
 	if err != nil {
-		t.Error(err)
-		t.FailNow()
+		t.Fatal(err)
+	}
+
+	ipCSRPEM := generateCSRWithIPs(t, sk, "10.0.0.1", nil, []string{"10.0.0.1"})
+	ipCSR, err := pki.DecodeX509CertificateRequestBytes(ipCSRPEM)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ipBaseCR := gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR(ipCSRPEM))
+	ipBaseOrder, err := buildOrder(ipBaseCR, ipCSR)
+	if err != nil {
+		t.Fatalf("failed to build order during testing: %s", err)
 	}
 
 	baseOrder, err := buildOrder(baseCR, csr)
 	if err != nil {
-		t.Errorf("failed to build order during testing: %s", err)
-		t.FailNow()
+		t.Fatalf("failed to build order during testing: %s", err)
 	}
 
 	metaFixedClockStart := metav1.NewTime(fixedClockStart)
@@ -164,7 +198,7 @@ func TestSign(t *testing.T) {
 			builder: &testpkg.Builder{
 				CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
 				ExpectedEvents: []string{
-					`Warning InvalidOrder The CSR PEM requests a commonName that is not present in the list of dnsNames. If a commonName is set, ACME requires that the value is also present in the list of dnsNames: "example.com" does not exist in [foo.com]`,
+					`Warning InvalidOrder The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "example.com" does not exist in [foo.com] or []`,
 				},
 				ExpectedActions: []testpkg.Action{
 					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
@@ -177,7 +211,37 @@ func TestSign(t *testing.T) {
 								Type:               cmapi.CertificateRequestConditionReady,
 								Status:             cmmeta.ConditionFalse,
 								Reason:             cmapi.CertificateRequestReasonFailed,
-								Message:            `The CSR PEM requests a commonName that is not present in the list of dnsNames. If a commonName is set, ACME requires that the value is also present in the list of dnsNames: "example.com" does not exist in [foo.com]`,
+								Message:            `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "example.com" does not exist in [foo.com] or []`,
+								LastTransitionTime: &metaFixedClockStart,
+							}),
+							gen.SetCertificateRequestFailureTime(metaFixedClockStart),
+						),
+					)),
+				},
+			},
+		},
+
+		"if the common name is not present in the IP Addresses then should hard fail": {
+			certificateRequest: gen.CertificateRequestFrom(baseCR,
+				gen.SetCertificateRequestCSR(generateCSR(t, sk, "10.0.0.1", "example.com")),
+			),
+			builder: &testpkg.Builder{
+				CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()},
+				ExpectedEvents: []string{
+					`Warning InvalidOrder The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "10.0.0.1" does not exist in [example.com] or []`,
+				},
+				ExpectedActions: []testpkg.Action{
+					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
+						cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
+						"status",
+						gen.DefaultTestNamespace,
+						gen.CertificateRequestFrom(baseCR,
+							gen.SetCertificateRequestCSR(generateCSR(t, sk, "10.0.0.1", "example.com")),
+							gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
+								Type:               cmapi.CertificateRequestConditionReady,
+								Status:             cmmeta.ConditionFalse,
+								Reason:             cmapi.CertificateRequestReasonFailed,
+								Message:            `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "10.0.0.1" does not exist in [example.com] or []`,
 								LastTransitionTime: &metaFixedClockStart,
 							}),
 							gen.SetCertificateRequestFailureTime(metaFixedClockStart),
@@ -187,6 +251,40 @@ func TestSign(t *testing.T) {
 			},
 		},
 
+		"pass if the CN is set in the IPs": {
+			certificateRequest: gen.CertificateRequestFrom(ipBaseCR,
+				gen.SetCertificateRequestCSR(ipCSRPEM),
+			),
+			builder: &testpkg.Builder{
+				CertManagerObjects: []runtime.Object{ipBaseCR.DeepCopy(), baseIssuer.DeepCopy()},
+				ExpectedEvents: []string{
+					"Normal OrderCreated Created Order resource default-unit-test-ns/test-cr-3104426127",
+				},
+				ExpectedActions: []testpkg.Action{
+					testpkg.NewAction(coretesting.NewCreateAction(
+						cmacme.SchemeGroupVersion.WithResource("orders"),
+						gen.DefaultTestNamespace,
+						ipBaseOrder,
+					)),
+					testpkg.NewAction(coretesting.NewUpdateSubresourceAction(
+						cmapi.SchemeGroupVersion.WithResource("certificaterequests"),
+						"status",
+						gen.DefaultTestNamespace,
+						gen.CertificateRequestFrom(ipBaseCR,
+							gen.SetCertificateRequestCSR(ipCSRPEM),
+							gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{
+								Type:               cmapi.CertificateRequestConditionReady,
+								Status:             cmmeta.ConditionFalse,
+								Reason:             cmapi.CertificateRequestReasonPending,
+								Message:            "Created Order resource default-unit-test-ns/test-cr-3104426127",
+								LastTransitionTime: &metaFixedClockStart,
+							}),
+						),
+					)),
+				},
+			},
+		},
+
 		//TODO: Think of a creative way to get `buildOrder` to fail :thinking_face:
 
 		"if order doesn't exist then attempt to create one": {
diff --git a/pkg/internal/apis/certmanager/validation/certificate_test.go b/pkg/internal/apis/certmanager/validation/certificate_test.go
index 0c3c2060d..bf62ef407 100644
--- a/pkg/internal/apis/certmanager/validation/certificate_test.go
+++ b/pkg/internal/apis/certmanager/validation/certificate_test.go
@@ -125,7 +125,7 @@ func TestValidateCertificate(t *testing.T) {
 				},
 			},
 			errs: []*field.Error{
-				field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris or emailAddresses must be set"),
+				field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris ipAddresses, or emailAddresses must be set"),
 			},
 		},
 		"certificate with no issuerRef": {
diff --git a/pkg/issuer/acme/http/ingress_test.go b/pkg/issuer/acme/http/ingress_test.go
index 08184f5da..e08f014df 100644
--- a/pkg/issuer/acme/http/ingress_test.go
+++ b/pkg/issuer/acme/http/ingress_test.go
@@ -71,6 +71,39 @@ func TestGetIngressesForChallenge(t *testing.T) {
 				}
 			},
 		},
+		"should return one ingress for IP that matches": {
+			Challenge: &cmacme.Challenge{
+				Spec: cmacme.ChallengeSpec{
+					DNSName: "10.0.0.1",
+					Solver: cmacme.ACMEChallengeSolver{
+						HTTP01: &cmacme.ACMEChallengeSolverHTTP01{
+							Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{},
+						},
+					},
+				},
+			},
+			PreFn: func(t *testing.T, s *solverFixture) {
+				ing, err := s.Solver.createIngress(s.Challenge, "fakeservice")
+				if err != nil {
+					t.Errorf("error preparing test: %v", err)
+				}
+
+				s.testResources[createdIngressKey] = ing
+				s.Builder.Sync()
+			},
+			CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) {
+				createdIngress := s.testResources[createdIngressKey].(*v1beta1.Ingress)
+				resp := args[0].([]*v1beta1.Ingress)
+				if len(resp) != 1 {
+					t.Errorf("expected one ingress to be returned, but got %d", len(resp))
+					t.Fail()
+					return
+				}
+				if !reflect.DeepEqual(resp[0], createdIngress) {
+					t.Errorf("Expected %v to equal %v", resp[0], createdIngress)
+				}
+			},
+		},
 		"should not return an ingress for the same certificate but different domain": {
 			Challenge: &cmacme.Challenge{
 				Spec: cmacme.ChallengeSpec{
diff --git a/test/e2e/framework/config/acme.go b/test/e2e/framework/config/acme.go
index 623636c9e..aa8fdef4e 100644
--- a/test/e2e/framework/config/acme.go
+++ b/test/e2e/framework/config/acme.go
@@ -23,11 +23,13 @@ import (
 type ACMEServer struct {
 	URL       string
 	DNSServer string
+	IngressIP string
 }
 
 func (p *ACMEServer) AddFlags(fs *flag.FlagSet) {
 	fs.StringVar(&p.URL, "acme-server-url", "https://pebble.pebble.svc.cluster.local/dir", "URL for the ACME server used during end-to-end tests")
 	fs.StringVar(&p.DNSServer, "acme-dns-server", "10.0.0.16", "DNS server for ACME DNS01 tests to run against using RFC2136")
+	fs.StringVar(&p.IngressIP, "acme-ingress-ip", "10.0.0.15", "IP of the ingress server that solves HTTP01 ACME challenges")
 }
 
 func (p *ACMEServer) Validate() []error {
diff --git a/test/e2e/framework/helper/certificates.go b/test/e2e/framework/helper/certificates.go
index ec1f69d15..37367a359 100644
--- a/test/e2e/framework/helper/certificates.go
+++ b/test/e2e/framework/helper/certificates.go
@@ -142,6 +142,7 @@ func (h *Helper) ValidateIssuedCertificate(certificate *cmapi.Certificate, rootC
 	// check the provided certificate is valid
 	expectedOrganization := pki.OrganizationForCertificate(certificate)
 	expectedDNSNames := certificate.Spec.DNSNames
+	expectedIPAddresses := certificate.Spec.IPAddresses
 	uris, err := pki.URIsForCertificate(certificate)
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse URIs: %s", err)
@@ -162,7 +163,8 @@ func (h *Helper) ValidateIssuedCertificate(certificate *cmapi.Certificate, rootC
 	commonNameCorrect := true
 	expectedCN := certificate.Spec.CommonName
 	if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 {
-		if !util.Contains(cert.DNSNames, cert.Subject.CommonName) {
+		// issuers might set an IP or DNSName as CN
+		if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) {
 			commonNameCorrect = false
 		}
 	} else if expectedCN != cert.Subject.CommonName {
@@ -170,9 +172,10 @@ func (h *Helper) ValidateIssuedCertificate(certificate *cmapi.Certificate, rootC
 	}
 
 	if !commonNameCorrect || !util.Subset(cert.DNSNames, expectedDNSNames) || !util.EqualUnsorted(pki.URLsToString(cert.URIs), expectedURIs) ||
+		!util.Subset(pki.IPAddressesToString(cert.IPAddresses), expectedIPAddresses) ||
 		!(len(cert.Subject.Organization) == 0 || util.EqualUnsorted(cert.Subject.Organization, expectedOrganization)) {
-		return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, uriSANs %v,but got a certificate valid for CN %q, O %v, dnsNames %v, uriSANs %v",
-			expectedCN, expectedOrganization, expectedDNSNames, expectedURIs, cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.URIs)
+		return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, uriSANs %v,but got a certificate valid for CN %q, O %v, dnsNames %v, uriSANs %v, ipAddresses %v",
+			expectedCN, expectedOrganization, expectedDNSNames, expectedURIs, cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.URIs, cert.IPAddresses)
 	}
 
 	if certificate.Status.NotAfter == nil {
diff --git a/test/e2e/framework/helper/validations/certificates.go b/test/e2e/framework/helper/validations/certificates.go
index eafbbc7ff..a753e1ed0 100644
--- a/test/e2e/framework/helper/validations/certificates.go
+++ b/test/e2e/framework/helper/validations/certificates.go
@@ -162,9 +162,9 @@ func ExpectValidCommonName(certificate *cmapi.Certificate, secret *corev1.Secret
 	expectedCN := certificate.Spec.CommonName
 
 	if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 {
-		// no CN is specified but our CA set one, checking if it is one of our DNS names
-		if !util.Contains(cert.DNSNames, cert.Subject.CommonName) {
-			return fmt.Errorf("Expected a common name for one of our DNSNames %v, but got a CN of %v", cert.DNSNames, cert.Subject.CommonName)
+		// no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses
+		if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) {
+			return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName)
 		}
 	} else if expectedCN != cert.Subject.CommonName {
 		return fmt.Errorf("Expected a common name of %v, but got a CN of %v", expectedCN, cert.Subject.CommonName)
diff --git a/test/e2e/suite/issuers/acme/certificate/http01.go b/test/e2e/suite/issuers/acme/certificate/http01.go
index efe408d85..e3682a746 100644
--- a/test/e2e/suite/issuers/acme/certificate/http01.go
+++ b/test/e2e/suite/issuers/acme/certificate/http01.go
@@ -469,4 +469,98 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() {
 		Expect(err).NotTo(HaveOccurred())
 	})
 
+	It("should obtain a signed certificate with a single IP Address from the ACME server", func() {
+		certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name)
+
+		By("Creating a Certificate")
+		cert := gen.Certificate(certificateName,
+			gen.SetCertificateSecretName(certificateSecretName),
+			gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}),
+			gen.SetCertificateIPs(f.Config.Addons.ACMEServer.IngressIP),
+		)
+		cert.Namespace = f.Namespace.Name
+
+		_, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Waiting for the Certificate to be issued...")
+		err = f.Helper().WaitCertificateIssued(f.Namespace.Name, certificateName, time.Minute*5)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Validating the issued Certificate...")
+		err = f.Helper().ValidateCertificate(f.Namespace.Name, certificateName)
+		Expect(err).NotTo(HaveOccurred())
+	})
+
+	It("should obtain a signed certificate with an IP and DNS names from the ACME server", func() {
+		certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name)
+
+		By("Creating a Certificate")
+		cert := gen.Certificate(certificateName,
+			gen.SetCertificateSecretName(certificateSecretName),
+			gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}),
+			gen.SetCertificateDNSNames(fmt.Sprintf("%s.%s", cmutil.RandStringRunes(2), acmeIngressDomain)),
+			gen.SetCertificateIPs(f.Config.Addons.ACMEServer.IngressIP),
+		)
+		cert.Namespace = f.Namespace.Name
+
+		_, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Waiting for the Certificate to be issued...")
+		err = f.Helper().WaitCertificateIssued(f.Namespace.Name, certificateName, time.Minute*5)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Validating the issued Certificate...")
+		err = f.Helper().ValidateCertificate(f.Namespace.Name, certificateName)
+		Expect(err).NotTo(HaveOccurred())
+	})
+
+	It("should allow updating an existing certificate with a new dns name", func() {
+		certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name)
+
+		By("Creating a Certificate")
+		cert := gen.Certificate(certificateName,
+			gen.SetCertificateSecretName(certificateSecretName),
+			gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}),
+			gen.SetCertificateDNSNames(fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)),
+		)
+		cert.Namespace = f.Namespace.Name
+
+		_, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Waiting for the Certificate to be issued...")
+		err = f.Helper().WaitCertificateIssued(f.Namespace.Name, certificateName, time.Minute*5)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Validating the issued Certificate...")
+		err = f.Helper().ValidateCertificate(f.Namespace.Name, certificateName)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Getting the latest version of the Certificate")
+		cert, err = certClient.Get(context.TODO(), certificateName, metav1.GetOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Adding an additional dnsName to the Certificate")
+		newDNSName := fmt.Sprintf("%s.%s", cmutil.RandStringRunes(5), acmeIngressDomain)
+		cert.Spec.DNSNames = append(cert.Spec.DNSNames, newDNSName)
+
+		By("Updating the Certificate in the apiserver")
+		cert, err = certClient.Update(context.TODO(), cert, metav1.UpdateOptions{})
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Waiting for the Certificate to be not ready")
+		_, err = h.WaitForCertificateNotReady(f.Namespace.Name, certificateName, time.Minute*5)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Waiting for the Certificate to be issued...")
+		err = f.Helper().WaitCertificateIssued(f.Namespace.Name, certificateName, time.Minute*5)
+		Expect(err).NotTo(HaveOccurred())
+
+		By("Validating the issued Certificate...")
+		err = f.Helper().ValidateCertificate(f.Namespace.Name, certificateName)
+		Expect(err).NotTo(HaveOccurred())
+	})
+
 })
diff --git a/test/unit/gen/order.go b/test/unit/gen/order.go
index b9e4947d7..b51297c2c 100644
--- a/test/unit/gen/order.go
+++ b/test/unit/gen/order.go
@@ -54,6 +54,12 @@ func SetOrderDNSNames(dnsNames ...string) OrderModifier {
 	}
 }
 
+func SetOrderIPAddresses(ips ...string) OrderModifier {
+	return func(order *cmacme.Order) {
+		order.Spec.IPAddresses = ips
+	}
+}
+
 func SetOrderURL(url string) OrderModifier {
 	return func(order *cmacme.Order) {
 		order.Status.URL = url