diff --git a/LICENSES b/LICENSES index ab19db9a5..2ec6cb357 100644 --- a/LICENSES +++ b/LICENSES @@ -417,6 +417,215 @@ ================================================================================ +================================================================================ += vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang licensed under: = + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + += vendor/github.com/akamai/AkamaiOPEN-edgegrid-golang/LICENSE e3fc50a88d0a364313df4b21ef20c29e +================================================================================ + + ================================================================================ = vendor/github.com/aws/aws-sdk-go licensed under: = @@ -11895,6 +12104,33 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/patrickmn/go-cache licensed under: = + +Copyright (c) 2012-2017 Patrick Mylund Nielsen and the go-cache contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + += vendor/github.com/patrickmn/go-cache/LICENSE ad59632c93a4b0296b360ba542decb5e +================================================================================ + + ================================================================================ = vendor/github.com/pavel-v-chernykh/keystore-go licensed under: = @@ -13104,6 +13340,35 @@ SOFTWARE. ================================================================================ +================================================================================ += vendor/github.com/sirupsen/logrus licensed under: = + +The MIT License (MIT) + +Copyright (c) 2014 Simon Eskildsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + += vendor/github.com/sirupsen/logrus/LICENSE 8dadfef729c08ec4e631c4f6fc5d43a0 +================================================================================ + + ================================================================================ = vendor/github.com/spf13/cobra licensed under: = diff --git a/go.mod b/go.mod index 024635695..cb9fa75d9 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/Azure/go-autorest/autorest/to v0.2.0 github.com/Azure/go-autorest/autorest/validation v0.1.0 // indirect github.com/Venafi/vcert/v4 v4.13.1 + github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0 github.com/aws/aws-sdk-go v1.34.30 github.com/cloudflare/cloudflare-go v0.13.2 github.com/cpu/goacmedns v0.0.3 diff --git a/go.sum b/go.sum index 73c330c7e..7d923dbbd 100644 --- a/go.sum +++ b/go.sum @@ -88,6 +88,8 @@ github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vaj github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0 h1:NNsy0ugDFZbu6S11NlsR4Kmko9hJ00gv9U2JHbwpf3g= +github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.0/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -454,6 +456,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -669,6 +673,8 @@ github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= +github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= @@ -708,6 +714,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible h1:Jd6xfriVlJ6hWPvYOE0Ni0QWcNTLRehfGPFxr3eSL80= github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible/go.mod h1:xlUlxe/2ItGlQyMTstqeDv9r3U4obH7xYd26TbDQutY= github.com/pavel-v-chernykh/keystore-go/v4 v4.1.0 h1:xKxUVGoB9VJU+lgQLPN0KURjw+XCVVSpHfQEeyxk3zo= @@ -863,6 +871,12 @@ github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI= @@ -1178,10 +1192,13 @@ gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= +gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI= diff --git a/hack/build/repos.bzl b/hack/build/repos.bzl index 1e039453d..580840ea9 100644 --- a/hack/build/repos.bzl +++ b/hack/build/repos.bzl @@ -59,6 +59,15 @@ def go_repositories(): version = "v1.0.1", ) + go_repository( + name = "com_github_akamai_AkamaiOPEN-edgegrid-golang", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/akamai/AkamaiOPEN-edgegrid-golang", + sum = "h1:NNsy0ugDFZbu6S11NlsR4Kmko9hJ00gv9U2JHbwpf3g=", + version = "v1.1.0", + ) + go_repository( name = "com_github_alecthomas_template", build_file_generation = "on", @@ -1311,6 +1320,15 @@ def go_repositories(): sum = "h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=", version = "v1.9.5", ) + go_repository( + name = "com_github_h2non_parth", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/h2non/parth", + sum = "h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=", + version = "v0.0.0-20190131123155-b4df798d6542", + ) + go_repository( name = "com_github_hailocab_go_hostpool", build_file_generation = "on", @@ -2121,6 +2139,15 @@ def go_repositories(): sum = "h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=", version = "v1.0.1", ) + go_repository( + name = "com_github_nbio_st", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/nbio/st", + sum = "h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=", + version = "v0.0.0-20140626010706-e9e8d9816f32", + ) + go_repository( name = "com_github_niemeyer_pretty", build_file_generation = "on", @@ -2865,6 +2892,31 @@ def go_repositories(): sum = "h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=", version = "v1.0.0", ) + go_repository( + name = "com_github_xeipuuv_gojsonpointer", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/xeipuuv/gojsonpointer", + sum = "h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=", + version = "v0.0.0-20180127040702-4e3ac2762d5f", + ) + go_repository( + name = "com_github_xeipuuv_gojsonreference", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/xeipuuv/gojsonreference", + sum = "h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=", + version = "v0.0.0-20180127040603-bd5ef7bd5415", + ) + go_repository( + name = "com_github_xeipuuv_gojsonschema", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "github.com/xeipuuv/gojsonschema", + sum = "h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=", + version = "v1.2.0", + ) + go_repository( name = "com_github_xiang90_probing", build_file_generation = "on", @@ -3043,6 +3095,15 @@ def go_repositories(): sum = "h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=", version = "v1.2.3", ) + go_repository( + name = "in_gopkg_h2non_gock_v1", + build_file_generation = "on", + build_file_proto_mode = "disable", + importpath = "gopkg.in/h2non/gock.v1", + sum = "h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=", + version = "v1.0.15", + ) + go_repository( name = "in_gopkg_inf_v0", build_file_generation = "on", @@ -3056,8 +3117,8 @@ def go_repositories(): build_file_generation = "on", build_file_proto_mode = "disable", importpath = "gopkg.in/ini.v1", - sum = "h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=", - version = "v1.51.0", + sum = "h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=", + version = "v1.51.1", ) go_repository( name = "in_gopkg_mgo_v2", diff --git a/pkg/issuer/acme/dns/akamai/BUILD.bazel b/pkg/issuer/acme/dns/akamai/BUILD.bazel index 092950672..2a42ce5aa 100644 --- a/pkg/issuer/acme/dns/akamai/BUILD.bazel +++ b/pkg/issuer/acme/dns/akamai/BUILD.bazel @@ -2,16 +2,14 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", - srcs = [ - "akamai.go", - "edgegridauth.go", - ], + srcs = ["akamai.go"], importpath = "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/akamai", visibility = ["//visibility:public"], deps = [ "//pkg/issuer/acme/dns/util:go_default_library", "//pkg/logs:go_default_library", - "//pkg/util:go_default_library", + "@com_github_akamai_AkamaiOPEN-edgegrid-golang//configdns-v2:go_default_library", + "@com_github_akamai_AkamaiOPEN-edgegrid-golang//edgegrid:go_default_library", "@com_github_go_logr_logr//:go_default_library", "@com_github_pkg_errors//:go_default_library", ], @@ -19,13 +17,11 @@ go_library( go_test( name = "go_default_test", - srcs = [ - "akamai_test.go", - "edgegridauth_test.go", - ], + srcs = ["akamai_test.go"], embed = [":go_default_library"], deps = [ "//pkg/issuer/acme/dns/util:go_default_library", + "@com_github_akamai_AkamaiOPEN-edgegrid-golang//configdns-v2:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", ], ) diff --git a/pkg/issuer/acme/dns/akamai/akamai.go b/pkg/issuer/acme/dns/akamai/akamai.go index b061e5fc1..d3bc44311 100644 --- a/pkg/issuer/acme/dns/akamai/akamai.go +++ b/pkg/issuer/acme/dns/akamai/akamai.go @@ -15,51 +15,76 @@ limitations under the License. */ // Package akamai implements a DNS provider for solving the DNS-01 -// challenge using Akamai FastDNS. -// See https://developer.akamai.com/api/luna/config-dns/overview.html +// challenge using Akamai Edge DNS. +// See https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html + package akamai import ( - "bytes" - "encoding/json" "fmt" - "io/ioutil" - "net/http" "strings" - "time" + + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" "github.com/go-logr/logr" "github.com/pkg/errors" "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/jetstack/cert-manager/pkg/logs" - pkgutil "github.com/jetstack/cert-manager/pkg/util" ) +// Interface defined to enable mocking and required functions +type OpenEdgegridDNSService interface { + GetRecord(zone string, name string, record_type string) (*dns.RecordBody, error) + RecordSave(rec *dns.RecordBody, zone string) error + RecordUpdate(rec *dns.RecordBody, zone string) error + RecordDelete(rec *dns.RecordBody, zone string) error +} + +type OpenDNSConfig struct { + config edgegrid.Config +} + // DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { - dns01Nameservers []string - // serviceConsumerDomain as issued by Akamai Luna Control Center. - // The ServiceConsumerDomain is the base URL. - serviceConsumerDomain string - - auth *EdgeGridAuth - - transport http.RoundTripper + dns01Nameservers []string + serviceConsumerDomain string + dnsclient OpenEdgegridDNSService + TTL int findHostedDomainByFqdn func(string, []string) (string, error) + isNotFound func(error) bool log logr.Logger } // NewDNSProvider returns a DNSProvider instance configured for Akamai. func NewDNSProvider(serviceConsumerDomain, clientToken, clientSecret, accessToken string, dns01Nameservers []string) (*DNSProvider, error) { - return &DNSProvider{ - dns01Nameservers, - serviceConsumerDomain, - NewEdgeGridAuth(clientToken, clientSecret, accessToken), - http.DefaultTransport, - findHostedDomainByFqdn, - logf.Log.WithName("akamai-dns"), - }, nil + + // required Aka OpenEdgegrid creds + non empty dnsservers list + if serviceConsumerDomain == "" || clientToken == "" || clientSecret == "" || accessToken == "" || len(dns01Nameservers) < 1 { + return nil, fmt.Errorf("edgedns: Provider creation failed. Missing required arguments.") + } + + dnsp := &DNSProvider{ + dns01Nameservers: dns01Nameservers, + serviceConsumerDomain: serviceConsumerDomain, + dnsclient: &OpenDNSConfig{}, + findHostedDomainByFqdn: findHostedDomainByFqdn, + isNotFound: isNotFound, + log: logf.Log.WithName("akamai-dns"), + TTL: 300, + } + dnsp.dnsclient.(*OpenDNSConfig).config = edgegrid.Config{ + Host: serviceConsumerDomain, + ClientToken: clientToken, + ClientSecret: clientSecret, + AccessToken: accessToken, + MaxBody: 131072, + } + + dns.Init(dnsp.dnsclient.(*OpenDNSConfig).config) + + return dnsp, nil } func findHostedDomainByFqdn(fqdn string, ns []string) (string, error) { @@ -71,218 +96,186 @@ func findHostedDomainByFqdn(fqdn string, ns []string) (string, error) { return util.UnFqdn(zone), nil } -// Present creates a TXT record to fulfil the dns-01 challenge +// Present creates/updates a TXT record to fulfill the dns-01 challenge. func (a *DNSProvider) Present(domain, fqdn, value string) error { - return a.setTxtRecord(fqdn, &dns01Record{value, 60}) -} -// CleanUp removes the TXT record matching the specified parameters -func (a *DNSProvider) CleanUp(domain, fqdn, value string) error { - return a.setTxtRecord(fqdn, nil) -} + logf.V(logf.DebugLevel).Infof("entering Present. domain: %s, fqdn: %s, value: %s", domain, fqdn, value) -type dns01Record struct { - value string - ttl int -} - -func (a *DNSProvider) setTxtRecord(fqdn string, dns01Record *dns01Record) error { hostedDomain, err := a.findHostedDomainByFqdn(fqdn, a.dns01Nameservers) if err != nil { - return errors.Wrapf(err, "failed to determine hosted domain for %q", fqdn) - } - - zoneData, err := a.loadZoneData(hostedDomain) - if err != nil { - return errors.Wrapf(err, "failed to load zone data for %q", hostedDomain) + return errors.Wrapf(err, "edgedns: failed to determine hosted domain for %q", fqdn) } + hostedDomain = util.UnFqdn(hostedDomain) + logf.V(logf.DebugLevel).Infof("hostedDomain: %s", hostedDomain) recordName, err := makeTxtRecordName(fqdn, hostedDomain) if err != nil { - return errors.Wrapf(err, "failed to create TXT record name") + return errors.Wrapf(err, "edgedns: failed to create TXT record name") + } + logf.V(logf.DebugLevel).Infof("recordName: %s", recordName) + + record, err := a.dnsclient.GetRecord(hostedDomain, recordName, "TXT") + if err != nil && !a.isNotFound(err) { + return errors.Wrapf(err, "edgedns: failed to retrieve TXT record") } - if updated, err := zoneData.setTxtRecord(recordName, dns01Record); !updated || err != nil { - if err != nil { - return errors.Wrapf(err, "failed to set TXT record in %q", hostedDomain) + if err == nil && record == nil { + return fmt.Errorf("edgedns: unknown error") + } + + if record != nil { + logf.V(logf.InfoLevel).Infof("edgedns: TXT record already exists. Updating target") + + if containsValue(record.Target, value) { + // have a record and have entry already + return nil } - return errors.Errorf("no %q TXT record found in %q", recordName, hostedDomain) + record.Target = append(record.Target, `"`+value+`"`) + record.TTL = a.TTL + + err = a.dnsclient.RecordUpdate(record, hostedDomain) + if err != nil { + return errors.Wrapf(err, "edgedns: failed to update TXT record") + } + + return nil } - newSerial, err := zoneData.incSoaSerial() + record = &dns.RecordBody{ + Name: recordName, + RecordType: "TXT", + TTL: a.TTL, + Target: []string{`"` + value + `"`}, + } + + err = a.dnsclient.RecordSave(record, hostedDomain) if err != nil { - return errors.Wrapf(err, "failed to increment SOA serial for %q", hostedDomain) + return errors.Wrapf(err, "edgedns: failed to create TXT record") } - if err := a.saveZoneData(hostedDomain, zoneData); err != nil { - return errors.Wrapf(err, "failed to save zone data for %q", hostedDomain) - } - - logf.V(logf.DebugLevel).Infof("Updated Akamai TXT record for %q on %q using SOA serial of %d", recordName, hostedDomain, newSerial) - return nil } +// CleanUp removes/updates the TXT record matching the specified parameters. +func (a *DNSProvider) CleanUp(domain, fqdn, value string) error { + + logf.V(logf.DebugLevel).Infof("entering CleanUp. domain: %s, fqdn: %s, value: %s", domain, fqdn, value) + + hostedDomain, err := a.findHostedDomainByFqdn(fqdn, a.dns01Nameservers) + if err != nil { + return errors.Wrapf(err, "edgedns: failed to determine hosted domain for %q", fqdn) + } + hostedDomain = util.UnFqdn(hostedDomain) + logf.V(logf.DebugLevel).Infof("hostedDomain: %s", hostedDomain) + + recordName, err := makeTxtRecordName(fqdn, hostedDomain) + if err != nil { + return errors.Wrapf(err, "edgedns: failed to create TXT record name") + } + logf.V(logf.DebugLevel).Infof("recordName: %s", recordName) + + existingRec, err := a.dnsclient.GetRecord(hostedDomain, recordName, "TXT") + if err != nil { + if a.isNotFound(err) { + return nil + } + return errors.Wrapf(err, "edgedns: failed to retrieve TXT record") + } + + if existingRec == nil { + return fmt.Errorf("edgedns: unknown failure") + } + + if len(existingRec.Target) == 0 { + return fmt.Errorf("edgedns: TXT record is invalid") + } + + if !containsValue(existingRec.Target, value) { + return nil + } + + var newRData []string + for _, val := range existingRec.Target { + tval := strings.Trim(val, `"`) + if tval == value { + continue + } + newRData = append(newRData, val) + } + + if len(newRData) > 0 { + existingRec.Target = newRData + logf.V(logf.DebugLevel).Infof("updating Akamai TXT record: %s, data: %s", existingRec.Name, newRData) + err = a.dnsclient.RecordUpdate(existingRec, hostedDomain) + if err != nil { + return errors.Wrapf(err, "edgedns: TXT record update failed") + } + + return nil + } + + logf.V(logf.DebugLevel).Infof("deleting Akamai TXT record %s", existingRec.Name) + err = a.dnsclient.RecordDelete(existingRec, hostedDomain) + if err != nil { + return errors.Wrapf(err, "edgedns: TXT record delete failed") + } + + return nil +} + +func containsValue(values []string, value string) bool { + for _, val := range values { + if strings.Trim(val, `"`) == value { + return true + } + } + + return false +} + +func isNotFound(err error) bool { + + if err == nil { + return false + } + + _, ok := err.(*dns.RecordError) + if ok { + return true + } + + return false +} + func makeTxtRecordName(fqdn, hostedDomain string) (string, error) { - if !strings.HasSuffix(fqdn, "."+hostedDomain+".") { + + recName := util.UnFqdn(fqdn) + if !strings.HasSuffix(recName, hostedDomain) { return "", errors.Errorf("fqdn %q is not part of %q", fqdn, hostedDomain) } - return fqdn[0 : len(fqdn)-len(hostedDomain)-2], nil + return recName, nil } -func (a *DNSProvider) urlForDomain(domain string) string { - return fmt.Sprintf("https://%s/config-dns/v1/zones/%s", a.serviceConsumerDomain, domain) +func (o OpenDNSConfig) GetRecord(zone string, name string, record_type string) (*dns.RecordBody, error) { + + dns.Config = o.config + + return dns.GetRecord(zone, name, record_type) } -func (a *DNSProvider) loadZoneData(domain string) (zoneData, error) { - url := a.urlForDomain(domain) - req, err := http.NewRequest(http.MethodGet, url, http.NoBody) - if err != nil { - return nil, errors.Wrap(err, "failed to create HTTP request") - } +func (o OpenDNSConfig) RecordSave(rec *dns.RecordBody, zone string) error { - responsePayload, err := a.makeRequest(req) - if err != nil { - return nil, err - } - - var zoneData map[string]interface{} - err = json.NewDecoder(bytes.NewReader(responsePayload)).Decode(&zoneData) - if err != nil { - return nil, errors.Wrap(err, "failed to decode Akamai OPEN API response") - } - - return zoneData, nil + return rec.Save(zone) } -func (a *DNSProvider) saveZoneData(domain string, data zoneData) error { - body, err := json.Marshal(data) - if err != nil { - return errors.Wrap(err, "failed to encode zone data") - } +func (o OpenDNSConfig) RecordUpdate(rec *dns.RecordBody, zone string) error { - url := a.urlForDomain(domain) - req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) - if err != nil { - return errors.Wrap(err, "failed to create HTTP request") - } - - req.Header.Set("Content-Type", "application/json") - - if _, err := a.makeRequest(req); err != nil { - return err - } - - return nil + return rec.Update(zone) } -func (a *DNSProvider) makeRequest(req *http.Request) ([]byte, error) { - req.Header.Set("User-Agent", pkgutil.CertManagerUserAgent) +func (o OpenDNSConfig) RecordDelete(rec *dns.RecordBody, zone string) error { - if err := a.auth.SignRequest(req); err != nil { - return nil, errors.Wrap(err, "failed to sign HTTP request") - } - - client := http.Client{ - Transport: a.transport, - Timeout: 30 * time.Second, - } - - resp, err := client.Do(req) - if err != nil { - return nil, errors.Wrap(err, "error querying Akamai OPEN API") - } - - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNoContent { - return nil, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("unexpected response from Akamai OPEN API: status_code: %d, status: %s", resp.StatusCode, resp.Status) - } - - responsePayload, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, errors.Wrap(err, "failed to read response payload") - } - - return responsePayload, nil -} - -type zoneData map[string]interface{} - -func (z zoneData) setTxtRecord(name string, dns01Record *dns01Record) (bool, error) { - zone, ok := z["zone"].(map[string]interface{}) - if !ok { - return false, errors.New("failed to retrieve zone from zone data") - } - - var txtRecords []interface{} - if txtNode, ok := zone["txt"]; ok { - if txtRecords, ok = txtNode.([]interface{}); !ok { - return false, errors.New("failed to retrieve TXT records from zone data") - } - } - - if dns01Record == nil { - if txtRecords = deleteRecord(txtRecords, name); txtRecords == nil { - return false, nil - } - } else { - txtRecords = updateRecord(txtRecords, name, map[string]interface{}{ - "name": name, - "ttl": dns01Record.ttl, - "active": true, - "target": dns01Record.value, - }) - } - - if len(txtRecords) < 1 { - delete(zone, "txt") - } else { - zone["txt"] = txtRecords - } - - return true, nil -} - -func (z zoneData) incSoaSerial() (uint64, error) { - soa, ok := z["zone"].(map[string]interface{})["soa"].(map[string]interface{}) - if !ok { - return 0, errors.New("failed to retrieve SOA record from zone data") - } - - serial, ok := soa["serial"].(float64) - if !ok { - return 0, errors.New("failed to retrieve SOA serial from zone data") - } - - newSerial := uint64(serial) + 1 - soa["serial"] = newSerial - return newSerial, nil -} - -func deleteRecord(records []interface{}, name string) []interface{} { - for pos := range records { - if recordName, ok := records[pos].(map[string]interface{})["name"]; ok && recordName == name { - return append(records[:pos], records[pos+1:]...) - } - } - - return nil -} - -func updateRecord(records []interface{}, name string, record map[string]interface{}) []interface{} { - for pos := range records { - if records[pos].(map[string]interface{})["name"] == name { - records[pos] = record - return records - } - } - - return append(records, record) + return rec.Delete(zone) } diff --git a/pkg/issuer/acme/dns/akamai/akamai_test.go b/pkg/issuer/acme/dns/akamai/akamai_test.go index bea79cff2..e121095ee 100644 --- a/pkg/issuer/acme/dns/akamai/akamai_test.go +++ b/pkg/issuer/acme/dns/akamai/akamai_test.go @@ -17,164 +17,395 @@ limitations under the License. package akamai import ( - "bytes" - "encoding/json" - "io/ioutil" - "net/http" - "strings" "testing" + "fmt" + "reflect" + + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util" "github.com/stretchr/testify/assert" ) -const sampleZoneData = `{ - "token": "a184671d5307a388180fbf7f11dbdf46", - "zone": { - "name": "example.com", - "soa": { - "contact": "hostmaster.akamai.com.", - "expire": 604800, - "minimum": 180, - "originserver": "use4.akamai.com.", - "refresh": 900, - "retry": 300, - "serial": 1271354824, - "ttl": 900 - }, - "ns": [ - { - "active": true, - "name": "", - "target": "use4.akam.net.", - "ttl": 3600 - }, - { - "active": true, - "name": "", - "target": "use3.akam.net.", - "ttl": 3600 - } - ] - } -}` +func testRecordBodyData() *dns.RecordBody { -const sampleZoneDataWithTxt = `{ - "token": "a184671d5307a388180fbf7f11dbdf46", - "zone": { - "name": "example.com", - "soa": { - "contact": "hostmaster.akamai.com.", - "expire": 604800, - "minimum": 180, - "originserver": "use4.akamai.com.", - "refresh": 900, - "retry": 300, - "serial": 1271354825, - "ttl": 900 - }, - "ns": [ - { - "active": true, - "name": "", - "target": "use4.akam.net.", - "ttl": 3600 - }, - { - "active": true, - "name": "", - "target": "use3.akam.net.", - "ttl": 3600 - } - ], - "txt": [ - { - "active": true, - "name" :"_acme-challenge.test", - "target": "dns01-key", - "ttl": 60 - } - ] - } -}` - -type httpResponder func(req *http.Request) (*http.Response, error) - -func (r httpResponder) RoundTrip(req *http.Request) (*http.Response, error) { - return r(req) + return &dns.RecordBody{ + Name: "_acme-challenge.test.example.com", + RecordType: "TXT", + Target: []string{`"` + "dns01-key" + `"`}, + TTL: 300, + } } -func TestPresent(t *testing.T) { +func testRecordBodyDataExist() *dns.RecordBody { + + return &dns.RecordBody{ + Name: "_acme-challenge.test.example.com", + RecordType: "TXT", + Target: []string{`"` + "dns01-key" + `"`, `"` + "dns01-key-stub" + `"`}, + TTL: 300, + } +} + +// OpenEdggrid DNS Stub +type StubOpenDNSConfig struct { + FuncOutput map[string]interface{} + FuncErrors map[string]error +} + +// +func findStubHostedDomainByFqdn(fqdn string, ns []string) (string, error) { + + return "test.example.com", nil + +} + +func stubIsNotFoundTrue(err error) bool { + + return true +} + +func stubIsNotFoundFalse(err error) bool { + + return false +} + +// TestNewDNSProvider performs sanity check on provider init +func TestNewDNSProvider(t *testing.T) { + + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + // samplee couple important fields + assert.Equal(t, akamai.serviceConsumerDomain, "akamai.example.com") + assert.Equal(t, fmt.Sprintf("%T", akamai.dnsclient), "*akamai.OpenDNSConfig") + +} + +// TestPresentBasicFlow tests basic flow, e.g. no record exists. +func TestPresentBasicFlow(t *testing.T) { akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) assert.NoError(t, err) - var response []byte - mockTransport(t, akamai, "example.com", sampleZoneData, &response) + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundTrue + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = nil + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordSave"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) - var expected, actual map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleZoneDataWithTxt), &expected)) - assert.NoError(t, json.Unmarshal(response, &actual)) - assert.EqualValues(t, expected, actual) } -func TestCleanUp(t *testing.T) { +// TestPresentExists tests flow with existing record. +func TestPresentExists(t *testing.T) { akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) assert.NoError(t, err) - var response []byte - mockTransport(t, akamai, "example.com", sampleZoneDataWithTxt, &response) + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordUpdate"] = testRecordBodyDataExist() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + +} + +// TestPresentValueExists tests flow with existing record. +func TestPresentValueExists(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + +} + +func TestPresentFailGetRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = nil + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["GetRecord"] = fmt.Errorf("Failed Get Test") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + +} + +func TestPresentFailSaveRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundTrue + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = nil + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save fail") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + +} + +func TestPresentFailUpdateRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordUpdate"] = testRecordBodyDataExist() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update failed") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + +} + +// TestCleanUpBasicFlow tests flow with existing record. +func TestCleanUpBasicFlow(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordDelete"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) - var expected, actual map[string]interface{} - assert.NoError(t, json.Unmarshal([]byte(sampleZoneData), &expected)) - expected["zone"].(map[string]interface{})["soa"].(map[string]interface{})["serial"] = 1271354826. - assert.NoError(t, json.Unmarshal(response, &actual)) - assert.EqualValues(t, expected, actual) } -func mockTransport(t *testing.T, akamai *DNSProvider, domain, data string, response *[]byte) { - akamai.transport = httpResponder(func(req *http.Request) (*http.Response, error) { - defer req.Body.Close() +// TestPresentExists tests flow with existing record. +func TestCleanUpExists(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) - if req.URL.String() != "https://akamai.example.com/config-dns/v1/zones/"+domain { - return &http.Response{ - StatusCode: http.StatusNotFound, - Body: http.NoBody, - }, nil - } + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordUpdate"] = testRecordBodyDataExist() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - if req.Method == http.MethodGet { - return &http.Response{ - StatusCode: http.StatusOK, - Body: ioutil.NopCloser(bytes.NewReader([]byte(data))), - }, nil - } + assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) - if req.Method == http.MethodPost { - if req.Header.Get("Content-Type") != "application/json" { - t.Fatalf("unsupported Content Type: %v", req.Header.Get("Content-Type")) - } +} - var err error - *response, err = ioutil.ReadAll(req.Body) - assert.NoError(t, err) +// TestCleanUpExistsNoValue tests flow with existing record. +func TestCleanUpExistsNoValue(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) - return &http.Response{ - StatusCode: http.StatusNoContent, - Body: http.NoBody, - }, nil - } + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - t.Fatalf("unexpected method: %v", req.Method) - return nil, nil - }) - akamai.findHostedDomainByFqdn = func(fqdn string, _ []string) (string, error) { - if !strings.HasSuffix(fqdn, domain+".") { - t.Fatalf("unexpected fqdn: %s", fqdn) - } - return domain, nil + assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + +} + +// TestCleanUpNoRecord tests flow with no existing record. +func TestCleanUpNoRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundTrue // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = nil + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01")) + +} + +func TestCleanUpFailGetRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = nil + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["GetRecord"] = fmt.Errorf("Failed Get Record") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + +} + +func TestCleanUpFailUpdateRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyDataExist() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordUpdate"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update failed") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") + + assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + +} + +func TestCleanUpFailDeleteRecord(t *testing.T) { + akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) + assert.NoError(t, err) + + akamai.findHostedDomainByFqdn = findStubHostedDomainByFqdn + akamai.isNotFound = stubIsNotFoundFalse // ignored for this flow ... + akamai.dnsclient = &StubOpenDNSConfig{FuncOutput: map[string]interface{}{}, FuncErrors: map[string]error{}} + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["GetRecord"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncOutput["RecordDelete"] = testRecordBodyData() + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") + akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete failed") + + assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + +} + +// Stub Get Record +func (o StubOpenDNSConfig) GetRecord(zone string, name string, record_type string) (*dns.RecordBody, error) { + + var rec *dns.RecordBody + + err, ok := o.FuncErrors["GetRecord"] + if ok { + return nil, err } + + exp, ok := o.FuncOutput["GetRecord"] + if ok { + if exp == nil { + return nil, fmt.Errorf("GetRecord: Unexpected nil") + } + rec = exp.(*dns.RecordBody) + // comare passed with expected + if name != rec.Name { + return nil, fmt.Errorf("GetRecord: expected/actual Name don't match") + } + if record_type != rec.RecordType { + return nil, fmt.Errorf("GetRecord: expected/actual Record Type don't match") + } + } + + return rec, nil + +} + +func (o StubOpenDNSConfig) RecordSave(rec *dns.RecordBody, zone string) error { + + exp, ok := o.FuncOutput["RecordSave"] + if ok { + // comare passed with expected + if rec.Name != exp.(*dns.RecordBody).Name { + return fmt.Errorf("RecordSave: expected/actual Name don't match") + } + if rec.RecordType != exp.(*dns.RecordBody).RecordType { + return fmt.Errorf("RecordSave: expected/actual Record Type don't match") + } + if !reflect.DeepEqual(rec.Target, exp.(*dns.RecordBody).Target) { + return fmt.Errorf("RecordSave: expected/actual Target don't match") + } + if rec.TTL != exp.(*dns.RecordBody).TTL { + return fmt.Errorf("RecordSave: expected/actual TTL don't match") + } + } + err, ok := o.FuncErrors["RecordSave"] + if ok { + return err + } + + return nil + +} + +func (o StubOpenDNSConfig) RecordUpdate(rec *dns.RecordBody, zone string) error { + + exp, ok := o.FuncOutput["RecordUpdate"] + if ok { + // comare passed with expected + if rec.Name != exp.(*dns.RecordBody).Name { + return fmt.Errorf("RecordUpdate: expected/actual Name don't match") + } + if rec.RecordType != exp.(*dns.RecordBody).RecordType { + return fmt.Errorf("RecordUpdate: expected/actual Record Type don't match") + } + if !reflect.DeepEqual(rec.Target, exp.(*dns.RecordBody).Target) { + return fmt.Errorf("RecordUpdate: expected/actual Target don't match") + } + if rec.TTL != exp.(*dns.RecordBody).TTL { + return fmt.Errorf("RecordUpdate: expected/actual TTL don't match") + } + } + err, ok := o.FuncErrors["RecordUpdate"] + if ok { + return err + } + + return nil +} + +func (o StubOpenDNSConfig) RecordDelete(rec *dns.RecordBody, zone string) error { + + exp, ok := o.FuncOutput["RecordDelete"] + if ok { + // comare passed with expected + if rec.Name != exp.(*dns.RecordBody).Name { + return fmt.Errorf("RecordDelete: expected/actual Name don't match") + } + if rec.RecordType != exp.(*dns.RecordBody).RecordType { + return fmt.Errorf("RecordDelete: expected/actual Record Type don't match") + } + if !reflect.DeepEqual(rec.Target, exp.(*dns.RecordBody).Target) { + return fmt.Errorf("RecordDelete: expected/actual Target don't match") + } + if rec.TTL != exp.(*dns.RecordBody).TTL { + return fmt.Errorf("RecordDelete: expected/actual TTL don't match") + } + } + err, ok := o.FuncErrors["RecordDelete"] + if ok { + return err + } + + return nil } diff --git a/pkg/issuer/acme/dns/akamai/edgegridauth.go b/pkg/issuer/acme/dns/akamai/edgegridauth.go deleted file mode 100644 index 9b0edc265..000000000 --- a/pkg/issuer/acme/dns/akamai/edgegridauth.go +++ /dev/null @@ -1,280 +0,0 @@ -/* -Copyright 2020 The cert-manager 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 akamai - -import ( - "bytes" - "crypto/hmac" - "crypto/rand" - "crypto/sha256" - "encoding/base64" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "sort" - "strings" - "time" - "unicode" -) - -// EdgeGridAuth holds all values required to perform Akamai API Client Authentication. -// See https://developer.akamai.com/introduction/Client_Auth.html. -type EdgeGridAuth struct { - ClientToken string - ClientSecret string - AccessToken string - HeadersToSign []string - MaxBody int - - now func() time.Time - createNonce func() (string, error) -} - -type signingData struct { - timestamp string - authHeader string - dataToSign string -} - -// edgeGridAuthTimeFormat is used for timestamps in request signatures. -const edgeGridAuthTimeFormat = "20060102T15:04:05-0700" // yyyyMMddTHH:mm:ss+0000 - -const NoMaxBody = -1 - -// NewEdgeGridAuth returns a new request signer for Akamai EdgeGrid -func NewEdgeGridAuth(clientToken, clientSecret, accessToken string, headersToSign ...string) *EdgeGridAuth { - return &EdgeGridAuth{ - ClientToken: clientToken, - ClientSecret: clientSecret, - AccessToken: accessToken, - HeadersToSign: headersToSign, - MaxBody: NoMaxBody, - - now: time.Now, - createNonce: createRandomNonce, - } -} - -// SignRequest calculates the signature for Akamai Open API and adds it as the Authorization header. -// The Authorization header starts with the signing algorithm moniker (name of the algorithm) used to sign the request. -// The moniker below identifies EdgeGrid V1, hash message authentication code, SHA–256 as the hash standard. -// This moniker is then followed by a space and an ordered list of name value pairs with each field separated by a semicolon. -func (e *EdgeGridAuth) SignRequest(req *http.Request) error { - signingData, err := e.signingData(req) - if err != nil { - return err - } - - req.Header.Set("Authorization", fmt.Sprintf( - "%ssignature=%s", - signingData.authHeader, - e.calculateRequestSignature(signingData))) - - return nil -} - -func (e *EdgeGridAuth) calculateRequestSignature(signingData *signingData) string { - return computeSignature( - signingData.dataToSign, - e.signingKey(signingData.timestamp)) -} - -func (e *EdgeGridAuth) signingData(req *http.Request) (*signingData, error) { - nonce, err := e.createNonce() - if err != nil { - return nil, err - } - - timestamp := e.now().UTC().Format(edgeGridAuthTimeFormat) - authHeader := fmt.Sprintf("EG1-HMAC-SHA256 client_token=%s;access_token=%s;timestamp=%s;nonce=%s;", - e.ClientToken, - e.AccessToken, - timestamp, - nonce) - - return &signingData{ - timestamp: timestamp, - authHeader: authHeader, - dataToSign: e.dataToSign(req, authHeader), - }, nil -} - -// dataToSign includes the information from the HTTP request that is relevant to ensuring that the request is authentic. -// This data set comprised of the request data combined with the authorization header value (excluding the signature field, -// but including the ; right before the signature field). -func (e *EdgeGridAuth) dataToSign(req *http.Request, authHeader string) string { - var buffer bytes.Buffer - - buffer.WriteString(req.Method) - buffer.WriteRune('\t') - buffer.WriteString(req.URL.Scheme) - buffer.WriteRune('\t') - buffer.WriteString(req.URL.Host) - buffer.WriteRune('\t') - buffer.WriteString(relativeURL(req.URL)) - buffer.WriteRune('\t') - buffer.WriteString(e.canonicalizedHeaders(req)) - buffer.WriteRune('\t') - buffer.WriteString(e.computeBodyHash(req)) - buffer.WriteRune('\t') - buffer.WriteString(authHeader) - - return buffer.String() -} - -// signingKey is derived from the client secret. -// The signing key is computed as the base64 encoding of the SHA–256 HMAC of the timestamp string -// (the field value included in the HTTP authorization header described above) with the client secret as the key. -func (e *EdgeGridAuth) signingKey(timestamp string) string { - return computeSignature(timestamp, e.ClientSecret) -} - -// relativeURL is the part of the URL that starts from the root path and includes the query string, with the handling of following special cases: -// If the path is null or empty, set it to / (forward-slash). -// If the path does not start with /, add / to the beginning. -func relativeURL(url *url.URL) string { - relativeURL := url.Path - if relativeURL == "" { - return "/" - } - - if relativeURL[0] != '/' { - relativeURL = "/" + relativeURL - } - - if url.RawQuery != "" { - relativeURL += "?" - relativeURL += url.RawQuery - } - - return relativeURL -} - -// computeBodyHash returns the base64-encoded SHA–256 hash of the POST body. -// For any other request methods, this field is empty. But the tac separator (\t) must be included. -// The size of the POST body must be less than or equal to the value specified by the service. -// Any request that does not meet this criteria SHOULD be rejected during the signing process, -// as the request will be rejected by EdgeGrid. -func (e *EdgeGridAuth) computeBodyHash(req *http.Request) string { - if req.Body != nil { - bodyBytes, _ := ioutil.ReadAll(req.Body) - req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) - - if req.Method == http.MethodPost && len(bodyBytes) > 0 { - dataToHash := bodyBytes - if e.MaxBody != NoMaxBody && len(dataToHash) > e.MaxBody { - dataToHash = dataToHash[0:e.MaxBody] - } - sha256Sum := sha256.Sum256(dataToHash) - return base64.StdEncoding.EncodeToString(sha256Sum[:]) - } - } - - return "" -} - -// canonicalizedHeaders returns the request headers as a canonicalized string. -// -// The protocol does not support multiple request headers with the same header name. -// Such requests SHOULD be rejected during the signing process. Otherwise, EdgeGrid -// will not produce the intended results by rejecting such requests or removing all -// (but one) duplicated headers. -// -// Header names are case-insensitive per rfc2616. -// -// For each entry in the list of headers designated by the service provider to include -// in the signature in the specified order, the canonicalization of the request header -// is done as follows: -// -// Get the first header value for the name. -// Trim the leading and trailing white spaces. -// Replace all repeated white spaces with a single space. -// Concatenate the name:value pairs with the tab (\t) separator (name field is all in lower case). -// Terminate the headers with another tab (\t) separator. -// -// NOTE: The canonicalized data is used for creating the signature only, as this step -// might alter the header value. If a header in the list is not present in the request, -// or the header value is empty, nothing for that header, neither the name nor the tab -// separator, may be included. -func (e *EdgeGridAuth) canonicalizedHeaders(req *http.Request) string { - if len(e.HeadersToSign) < 1 { - return "" - } - - var headerNamesToSign []string - for headerName := range req.Header { - for _, sign := range e.HeadersToSign { - if strings.EqualFold(sign, headerName) { - headerNamesToSign = append(headerNamesToSign, headerName) - break - } - } - } - - if len(headerNamesToSign) < 1 { - return "" - } - - sort.Strings(headerNamesToSign) - - var buffer bytes.Buffer - for _, headerName := range headerNamesToSign { - for _, c := range headerName { - buffer.WriteRune(unicode.ToLower(c)) - } - - buffer.WriteRune(':') - - white := false - empty := true - for _, c := range req.Header.Get(headerName) { - if unicode.IsSpace(c) { - white = true - } else { - if white && !empty { - buffer.WriteRune(' ') - } - buffer.WriteRune(unicode.ToLower(c)) - empty = false - white = false - } - } - - buffer.WriteRune('\t') - } - - return buffer.String() -} - -// calculateSignature is the base64-encoding of the SHA–256 HMAC of the data to sign with the signing key. -func computeSignature(message string, secret string) string { - key := []byte(secret) - h := hmac.New(sha256.New, key) - h.Write([]byte(message)) - return base64.StdEncoding.EncodeToString(h.Sum(nil)) -} - -func createRandomNonce() (string, error) { - bytes := make([]byte, 18) - _, err := rand.Read(bytes) - if err != nil { - return "", err - } - - return base64.URLEncoding.EncodeToString(bytes), nil -} diff --git a/pkg/issuer/acme/dns/akamai/edgegridauth_test.go b/pkg/issuer/acme/dns/akamai/edgegridauth_test.go deleted file mode 100644 index 7f49e3209..000000000 --- a/pkg/issuer/acme/dns/akamai/edgegridauth_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -Copyright 2020 The cert-manager 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 akamai - -import ( - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestDataToSign(t *testing.T) { - req, err := http.NewRequest( - http.MethodGet, - "https://akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net/diagnostic-tools/v1/locations", - http.NoBody) - assert.NoError(t, err) - - auth := NewEdgeGridAuth("ClientToken", "ClientSecret", "AccessToken") - auth.now = func() time.Time { - return time.Unix(1396461906, 0) // 20140402T18:05:06Z - } - auth.createNonce = func() (string, error) { - return "185f94eb-537c-4c01-b8cc-2fa5a06aee7f", nil - } - - data, err := auth.signingData(req) - assert.NoError(t, err) - - expected := "GET" + - "\thttps" + - "\takaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net" + - "\t/diagnostic-tools/v1/locations" + - "\t" + // headers - "\t" + // content hash - "\tEG1-HMAC-SHA256 " + - "client_token=ClientToken;" + - "access_token=AccessToken;" + - "timestamp=20140402T18:05:06+0000;" + - "nonce=185f94eb-537c-4c01-b8cc-2fa5a06aee7f;" - - assert.EqualValues(t, expected, data.dataToSign) -} - -func TestDataToSignWithHeaders(t *testing.T) { - req, err := http.NewRequest( - http.MethodGet, - "http://akaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna-dev.akamaiapis.net/sample-api/v1/property/?fields=x&format=json&cpcode=1234", - http.NoBody) - assert.NoError(t, err) - - req.Header.Set("x-a", "va") - req.Header.Set("x-c", "\" xc \"") - req.Header.Set("x-b", " w b") - - auth := NewEdgeGridAuth( - "ClientToken", "ClientSecret", "AccessToken", - "x-c", "x-b", "x-a") - auth.now = func() time.Time { - return time.Unix(1376917283, 0) // 20130819T13:01:23Z - } - auth.createNonce = func() (string, error) { - return "ac392096-8aa1-44fd-8c3b-f797d35a6736", nil - } - - data, err := auth.signingData(req) - assert.NoError(t, err) - - expected := "GET" + - "\thttp" + - "\takaa-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna-dev.akamaiapis.net" + - "\t/sample-api/v1/property/?fields=x&format=json&cpcode=1234" + - "\tx-a:va\tx-b:w b\tx-c:\" xc \"\t" + - "\t" + // content hash - "\tEG1-HMAC-SHA256 " + - "client_token=ClientToken;" + - "access_token=AccessToken;" + - "timestamp=20130819T13:01:23+0000;" + - "nonce=ac392096-8aa1-44fd-8c3b-f797d35a6736;" - - assert.EqualValues(t, expected, data.dataToSign) -}