Repository: boto/botocore
Pull request #2601 (2022-01-26 14:44:54)

Fix IPv6 endpoints with non-default port
This commit adds brackets to IPv6 addresses in auth.py. Otherwise the signature is not calculated properly.

drake127: E.g.: `http://[::1]:8080` doesn't currently work.
nateprewitt: Hi @drake127, thanks for the PR! This looks reasonable to me, would you mind adding a test to ensure the behavior continues working as expected?
drake127: I am willing to do it if you give me some hints how to add tests in your project. I tested it against Scality docker endpoint but I suspect I cannot do it here. Do you have some endpoint in your test infra (that verifies V4 signatures)?
codecov-commenter: # [Codecov](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) Report
> Merging [#2601](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) (4da625f) into [develop](https://codecov.io/gh/boto/botocore/commit/d9d5ade65eeaf01178f6a3f74865ace07164f0f3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) (d9d5ade) will **decrease** coverage by `0.00%`.
> The diff coverage is `75.00%`.

> :exclamation: Current head 4da625f differs from pull request most recent head 5690112. Consider uploading reports for the commit 5690112 to get more accurate results
[![Impacted file tree graph](https://codecov.io/gh/boto/botocore/pull/2601/graphs/tree.svg?width=650&height=150&src=pr&token=QOLXw6XSza&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)

```diff
@@             Coverage Diff             @@
##           develop    #2601      +/-   ##
===========================================
- Coverage    95.43%   95.43%   -0.01%     
===========================================
  Files           59       59              
  Lines        11842    11844       +2     
===========================================
+ Hits         11302    11303       +1     
- Misses         540      541       +1     
```


| [Impacted Files](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) | Coverage Δ | |
|---|---|---|
| [botocore/auth.py](https://codecov.io/gh/boto/botocore/pull/2601/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvYXV0aC5weQ==) | `97.70% <75.00%> (-0.20%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto). Last update [d9d5ade...5690112](https://codecov.io/gh/boto/botocore/pull/2601?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto).


Patch:
diff --git a/botocore/auth.py b/botocore/auth.py
index 1f760fa24..47d9fdfb8 100644
--- a/botocore/auth.py
+++ b/botocore/auth.py
@@ -27,7 +27,7 @@ from botocore.compat import (
     six, unquote, urlsplit, urlunsplit, HAS_CRT
 )
 from botocore.exceptions import NoCredentialsError
-from botocore.utils import normalize_url_path, percent_encode_sequence
+from botocore.utils import is_valid_ipv6_endpoint_url, normalize_url_path, percent_encode_sequence
 
 # Imports for backwards compatibility
 from botocore.compat import MD5_AVAILABLE # noqa
@@ -59,6 +59,8 @@ def _host_from_url(url):
     # 3) excludes userinfo
     url_parts = urlsplit(url)
     host = url_parts.hostname  # urlsplit's hostname is always lowercase
+    if is_valid_ipv6_endpoint_url(url):
+        host = '[%s]' % (host)
     default_ports = {
         'http': 80,
         'https': 443


Test patch:
diff --git a/tests/unit/test_auth_sigv4.py b/tests/unit/test_auth_sigv4.py
index 7c253ee9b..5eff95368 100644
--- a/tests/unit/test_auth_sigv4.py
+++ b/tests/unit/test_auth_sigv4.py
@@ -31,3 +31,24 @@ class TestSigV4Auth(unittest.TestCase):
         request = AWSRequest(method='GET', url=endpoint)
         headers_to_sign = self.sigv4.headers_to_sign(request)
         self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_without_port(self):
+        endpoint = 'http://[::1]'
+        expected_host = '[::1]'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_with_default_port(self):
+        endpoint = 'http://[::1]:80'
+        expected_host = '[::1]'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_with_explicit_port(self):
+        endpoint = 'http://[::1]:6789'
+        expected_host = '[::1]:6789'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))