mirror of
https://github.com/caddyserver/caddy.git
synced 2026-05-25 16:22:36 -04:00
Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 66863aad3b | |||
| c42bfaf31e | |||
| e2f913bb7f | |||
| 65a09524c3 | |||
| c6d6a775a1 | |||
| 4accf737a6 | |||
| ff19bddac5 | |||
| 584eba94a4 | |||
| 904f149e5b | |||
| 8b80a3201f | |||
| 68529e2f9e | |||
| 399eff415c | |||
| c054a818a1 | |||
| af5c148ed1 | |||
| 514eef33fe | |||
| 3860b235d0 | |||
| 6f73a358f4 | |||
| 6a14e2c2a8 | |||
| 2bc30bb780 | |||
| 28d870c193 | |||
| fb9d874fa9 | |||
| 6cea1f239d | |||
| 2ae8c11927 | |||
| e9b1d7dcb4 | |||
| bd9d796e6e | |||
| 246a31aacd | |||
| 0665a86eb7 | |||
| 3fdaf50785 | |||
| 19cc2bd3c3 | |||
| 705de11bef | |||
| 8a0fff58aa | |||
| 6f0f159ba5 | |||
| 6eafd4e82f | |||
| eda54c22a6 | |||
| 2c71fb116b | |||
| 724613a1be | |||
| 735c86658d | |||
| a2dae1d43f | |||
| efc0cc5e85 | |||
| 0bf2565c37 | |||
| 7bfe5b6c95 | |||
| 2a5599e2ad | |||
| c35820012b | |||
| 2d0f8831f8 | |||
| d7dbf85525 | |||
| 77f233a484 | |||
| ddd690de4c | |||
| 6004d3f779 | |||
| caca55e582 | |||
| c9049bdc24 | |||
| 21c00a3cd2 | |||
| 61b7002d26 | |||
| b1480eb52f | |||
| 5bc4777be9 | |||
| 3af15c0725 | |||
| 6db3615547 | |||
| 32cafbb630 | |||
| 003403ecbc | |||
| 5b48f784ae | |||
| d84a5d8427 | |||
| 7da32f493a | |||
| cb0d9838cb | |||
| d81a69ef16 | |||
| 99dcc10f31 | |||
| fa4cdde7d8 | |||
| d55c3b31eb | |||
| 6d03fb48f9 | |||
| b3bff13f7d | |||
| 7211101c52 | |||
| 90dba172cb | |||
| 4b10ae5ce6 | |||
| 1dfb11486e | |||
| 11a132d48b | |||
| 9dafa63933 | |||
| 21c1da101c | |||
| 7a99835dab | |||
| 7b0962ba4d | |||
| 2d1f7b9da8 | |||
| a285fe4129 | |||
| 97e61c16a3 | |||
| 83551edf3e | |||
| e18c373064 | |||
| 9a7756c6e4 | |||
| fdf2a77feb | |||
| a496308f6e | |||
| d5d7fb5954 | |||
| 996af0915d | |||
| 6c051cd27d | |||
| 9415feca7c | |||
| 881b826fb5 | |||
| 538ddb8587 | |||
| 69b5643130 | |||
| e5bbed1046 | |||
| 294910c68c | |||
| 8c5d00b2bc | |||
| aa20878887 | |||
| c1e5c09294 | |||
| ffc125d6f5 | |||
| 22055c5e0f | |||
| dfe802aed3 | |||
| 7a365af5df | |||
| 0cbf467b3f | |||
| bb67e19d7b | |||
| 1dc4ec2d77 | |||
| 452d4726f7 | |||
| 2a8a198568 | |||
| cc8fb488d3 | |||
| fae064262d | |||
| 9ee01dceac | |||
| 812278acd8 | |||
| c47ddbeffb | |||
| 483e31b978 | |||
| 41a682ddde | |||
| 7243454a96 | |||
| 3fb2c394d1 | |||
| 21de227fe9 | |||
| 62c9f2cf3e | |||
| bde3823b76 | |||
| 4df56c77e3 | |||
| cee5589b98 | |||
| 90c7b4b0a1 | |||
| aef560c7fc | |||
| 44536a7594 | |||
| ea7e4b4024 | |||
| ef6e53bb5f | |||
| 35e1d92d58 | |||
| dc9f4f13fc | |||
| 4c55d26f11 | |||
| d534162556 | |||
| 5bde8d705b | |||
| 7960b4259d | |||
| 2c91688f39 | |||
| 513e0240fd | |||
| bf8c3c25c1 | |||
| c8da8ca673 | |||
| 43fba378d6 | |||
| cd9317e5df | |||
| 8dbc5f70a5 | |||
| 07c6076ea0 | |||
| 28ab0bfb13 | |||
| 1c17e6c6bb | |||
| b814c0af9c | |||
| 9e5d9e2530 | |||
| 9408dacc27 | |||
| 12cfc19487 | |||
| afecd90a6c | |||
| 2f59467ac3 | |||
| 184e8e9f71 | |||
| 1e8c9764df | |||
| 41c7bd27b4 | |||
| 96d6d277a4 | |||
| 26e559662d | |||
| 52305618df | |||
| e051e119d1 | |||
| 8e42661060 | |||
| 86a4f2c9f4 | |||
| a507a5bbc7 | |||
| d0770dbbb3 | |||
| a77bd1d887 | |||
| bca610fbde | |||
| 1fa8c185a8 | |||
| a1796c2f14 | |||
| f931c26f68 | |||
| 10db57027d | |||
| c11d0e47a3 | |||
| 9770ce7c9f | |||
| 5ae1a5617c | |||
| 83c85c53f5 | |||
| 768383a610 | |||
| 570d84f7d3 | |||
| a6761153cb | |||
| 02845bc9fd | |||
| 97ed9e111d | |||
| 100d19e3af | |||
| ebf07f853b | |||
| 1b061815b2 | |||
| 026937fab5 | |||
| 295604d6df | |||
| bacf50a59e | |||
| da8686c4b9 | |||
| e3a8f72f1c | |||
| bae4f15fad | |||
| 0798459e44 | |||
| f980170909 | |||
| 6963a72a63 | |||
| 76bbb473a5 | |||
| 3c70950fa1 | |||
| 7c171542ed | |||
| 9a572635f5 | |||
| f5ccb904a3 | |||
| 829e36d535 | |||
| 2609a72893 | |||
| ec456811bb | |||
| 68cebb28d0 | |||
| a3bdc22234 | |||
| d3383ced2a | |||
| c024ae096d | |||
| 3bee569a8a | |||
| 999ab22b8c | |||
| 9991fdc495 | |||
| f29023bf8f | |||
| 85f5f47f31 | |||
| 6e4132eb89 | |||
| d89ad2fd5b | |||
| d33926b63f | |||
| c5f9227a48 | |||
| 88d391c1f5 | |||
| b4a7d6267f | |||
| e5dc76b054 | |||
| 7dfd69cdc5 | |||
| 28fdf64dc5 | |||
| 0fe98038b6 | |||
| 6e4c688ea7 | |||
| 5110643201 | |||
| 4d9b63d909 | |||
| e30deedcc1 | |||
| fbd9515d35 | |||
| 95f6bd7e5c | |||
| b1ce9d4db7 | |||
| 61679b74f5 | |||
| 2c1b663156 | |||
| 8b2dbc52ec | |||
| 657f0cab17 | |||
| 7be747fbe9 | |||
| 5b355cbed0 | |||
| a3cfe437b1 | |||
| 437d5095a6 | |||
| 145aebbba5 | |||
| 6a32daa225 | |||
| 81cdebf648 | |||
| 84c729e96a | |||
| 346c33b4d5 | |||
| 78717ce5b0 | |||
| 3d6fc1e1b7 | |||
| c7ac7de38a | |||
| 05164c895a | |||
| 1e8af27329 | |||
| b6482e53c1 | |||
| 20f6795413 | |||
| 84f16852ab | |||
| 1456f15f9a | |||
| fdfe2ae53b | |||
| 1c190b001b | |||
| 3634c4593f | |||
| 7ca15861dd | |||
| 8ff330c555 | |||
| 626f19a264 | |||
| 6ca5828221 | |||
| 6fe04a30b1 | |||
| 19b45546a7 | |||
| d322de6b42 | |||
| ce3ca541d8 | |||
| 581f1defcb | |||
| 0d2a3511dc | |||
| 73643ea736 | |||
| 809e72792c | |||
| 9fb0b1e838 | |||
| 244b839f98 | |||
| 904d9cab39 | |||
| ac65f690ae | |||
| 37aa516a6e | |||
| 105acfa086 | |||
| deba26d225 | |||
| 178ba024fe | |||
| e207240f9a | |||
| 397e04ebd9 | |||
| d2c15bea1b | |||
| 8da9eaee34 | |||
| ea3688e1c0 | |||
| c87f82f0ce | |||
| 5c55e5d53f | |||
| 7ee3ab7baa | |||
| ba08833b2a | |||
| 9eecd698da | |||
| 0fa1a3b630 | |||
| 673d3d00f2 | |||
| 2acb208e32 | |||
| e02117cb8a | |||
| 95b2863df2 | |||
| 341d4fb805 | |||
| 745cb0e9e6 | |||
| 9af05719bc | |||
| d08cbefff8 | |||
| 2eede58b3a | |||
| 235357abc8 | |||
| 4b4e16edaf | |||
| ee64719d93 | |||
| 2491336c11 | |||
| 1698838685 | |||
| 4c43bf8cc8 | |||
| 348cb798e2 | |||
| e211491407 | |||
| 6e2fabb2a4 | |||
| 8cc60e6896 | |||
| bea8dedfb2 | |||
| f2ce81cc8b | |||
| 2cab475ba5 | |||
| c32f383a01 | |||
| 37093befd5 | |||
| d692d503a3 | |||
| 3c1def2430 | |||
| b583007c49 | |||
| 6b60a301c0 | |||
| d6632e2145 | |||
| 903776238e | |||
| f741ab3463 | |||
| 76ac28a624 | |||
| 61b427fa47 | |||
| 42a6628935 | |||
| 6a4d638c1e | |||
| aa6c5fde07 | |||
| 31c6ac097e | |||
| 406df22a16 | |||
| afb2ca27c1 | |||
| ce45353e61 | |||
| 89124aa570 | |||
| ab2fc9d066 | |||
| fc7340e11a | |||
| 3f48a2eb45 | |||
| f192ae5ea5 | |||
| b62f8e0582 | |||
| ae86f6dd91 | |||
| b550ea433b | |||
| e42514ad4a | |||
| f596fd77bb | |||
| 0433f9d075 | |||
| c67c8e60cc | |||
| 8f8ecd2e2a | |||
| 115b877e1a | |||
| 2ce3deb540 | |||
| acf4dde1dd |
@@ -0,0 +1,184 @@
|
||||
Contributing to Caddy
|
||||
=====================
|
||||
|
||||
Welcome! Thank you for choosing to be a part of our community. Caddy wouldn't be great without your involvement!
|
||||
|
||||
For starters, we invite you to join [the Caddy forum](https://caddy.community) where you can hang out with other Caddy users and developers.
|
||||
|
||||
## Common Tasks
|
||||
|
||||
- [Contributing code](#contributing-code)
|
||||
- [Writing a Caddy module](#writing-a-caddy-module)
|
||||
- [Asking or answering questions for help using Caddy](#getting-help-using-caddy)
|
||||
- [Reporting a bug](#reporting-bugs)
|
||||
- [Suggesting an enhancement or a new feature](#suggesting-features)
|
||||
- [Improving documentation](#improving-documentation)
|
||||
|
||||
Other menu items:
|
||||
|
||||
- [Values](#values)
|
||||
- [Coordinated Disclosure](#coordinated-disclosure)
|
||||
- [Thank You](#thank-you)
|
||||
|
||||
|
||||
### Contributing code
|
||||
|
||||
You can have a huge impact on the project by helping with its code. To contribute code to Caddy, open a [pull request](https://github.com/caddyserver/caddy/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.** You can get familiar with Caddy's code base by using [code search at Sourcegraph](https://sourcegraph.com/github.com/caddyserver/caddy/-/search).
|
||||
|
||||
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions—even if it seems small or insignificant. Please don't take it personally. :blue_heart: If your change is on the right track, we can guide you to make it mergable.
|
||||
|
||||
Here are some of the expectations we have of contributors:
|
||||
|
||||
- **Open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that any changes are in-line with the project's goals and the best interests of its users. We can also discuss the best possible implementation. If there's already an issue about it, comment on the existing issue to claim it.
|
||||
|
||||
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we want to avoid.](https://twitter.com/iamdevloper/status/397664295875805184)
|
||||
|
||||
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
|
||||
|
||||
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
|
||||
|
||||
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
|
||||
|
||||
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
|
||||
|
||||
- **Own your contributions.** Caddy is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
|
||||
|
||||
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
|
||||
|
||||
We often grant [collaborator status](#collaborator-instructions) to contributors who author one or more significant, high-quality PRs that are merged into the code base!
|
||||
|
||||
|
||||
#### HOW TO MAKE A PULL REQUEST TO CADDY
|
||||
|
||||
Contributing to Go projects on GitHub is fun and easy. We recommend the following workflow:
|
||||
|
||||
1. [Fork this repo](https://github.com/caddyserver/caddy). This makes a copy of the code you can write to.
|
||||
|
||||
2. If you don't already have this repo (caddyserver/caddy.git) repo on your computer, get it with `go get github.com/caddyserver/caddy/v2`.
|
||||
|
||||
3. Tell git that it can push the caddyserver/caddy.git repo to your fork by adding a remote: `git remote add myfork https://github.com/<your-username>/caddy.git`
|
||||
|
||||
4. Make your changes in the caddyserver/caddy.git repo on your computer.
|
||||
|
||||
5. Push your changes to your fork: `git push myfork`
|
||||
|
||||
6. [Create a pull request](https://github.com/caddyserver/caddy/pull/new/master) to merge your changes into caddyserver/caddy @ master. (Click "compare across forks" and change the head fork.)
|
||||
|
||||
This workflow is nice because you don't have to change import paths. You can get fancier by using different branches if you want.
|
||||
|
||||
|
||||
### Writing a Caddy module
|
||||
|
||||
Caddy can do more with modules! Anyone can write one. Caddy modules are Go libraries that get compiled into Caddy, extending its feature set. They can add directives to the Caddyfile, add new configuration adapters, and even implement new server types (e.g. HTTP, DNS).
|
||||
|
||||
[Learn how to write a module here](https://caddyserver.com/docs/extending-caddy). You should also share and discuss your module idea [on the forums](https://caddy.community) to have people test it out. We don't use the Caddy issue tracker for third-party modules.
|
||||
|
||||
|
||||
### Getting help using Caddy
|
||||
|
||||
If you have a question about using Caddy, [ask on our forum](https://caddy.community)! There will be more people there who can help you than just the Caddy developers who follow our issue tracker. Issues are not the place for usage questions.
|
||||
|
||||
Many people on the forums could benefit from your experience and expertise, too. Once you've been helped, consider giving back by answering other people's questions and participating in other discussions.
|
||||
|
||||
|
||||
### Reporting bugs
|
||||
|
||||
Like every software, Caddy has its flaws. If you find one, [search the issues](https://github.com/caddyserver/caddy/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/caddyserver/caddy/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Caddy and its standard modules.)
|
||||
|
||||
**You can help stop bugs in their tracks!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
|
||||
|
||||
Please follow the issue template so we have all the needed information. Unredacted—yes, actual values matter. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The burden is on you to convince us that it is actually a bug in Caddy. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
|
||||
|
||||
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||
|
||||
Please be kind. :smile: Remember that Caddy comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
|
||||
|
||||
#### Bug reporting expectations
|
||||
|
||||
Maintainers---or more generally, developers---need three things to act on bugs:
|
||||
|
||||
1. To agree or be convinced that it's a bug (reporter's responsibility).
|
||||
- A bug is undesired or surprising behavior which violates documentation or the spec.
|
||||
|
||||
2. To be able to understand what is happening (mostly reporter's responsibility).
|
||||
- If the reporter can provide satisfactory instructions such that a developer can reproduce the bug, the developer will likely be able to understand the bug, write a test case, and implement a fix.
|
||||
- Otherwise, the burden is on the reporter to test possible solutions. This is discouraged because it loosens the feedback loop, slows down debugging efforts, obscures the true nature of the problem from the developers, and is unlikely to result in new test cases.
|
||||
|
||||
3. A solution, or ideas toward a solution (mostly maintainer's responsibility).
|
||||
- Sometimes the best solution is a documentation change.
|
||||
- Usually the developers have the best domain knowledge for inventing a solution, but reporters may have ideas or preferences for how they would like the software to work.
|
||||
- Security, correctness, and project goals/vision all take priority over a user's preferences.
|
||||
- It's simply good business to yield a solution that satisfies the users, and it's even better business to leave them impressed.
|
||||
|
||||
Thus, at the very least, the reporter is expected to:
|
||||
|
||||
1. Convince the reader that it's a bug (if it's not obvious).
|
||||
2. Reduce the problem down to the minimum specific steps required to reproduce it.
|
||||
|
||||
The maintainer is usually able to do the rest; but of course the reporter may invest additional effort to speed up the process.
|
||||
|
||||
|
||||
|
||||
### Suggesting features
|
||||
|
||||
First, [search to see if your feature has already been requested](https://github.com/caddyserver/caddy/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and, without clarification, will have to be closed.
|
||||
|
||||
While we really do value your requests and implement many of them, not all features are a good fit for Caddy. Most of those [make good modules](#writing-a-caddy-module), which can be made by anyone! But if a feature is not in the best interest of the Caddy project or its users in general, we may politely decline to implement it into Caddy core.
|
||||
|
||||
|
||||
### Improving documentation
|
||||
|
||||
Caddy's documentation is available at [https://caddyserver.com/docs](https://caddyserver.com/docs) and its source is in the [website repo](https://github.com/caddyserver/website). If you would like to make a fix to the docs, please submit an issue there describing the change to make.
|
||||
|
||||
Note that third-party module documentation is not hosted by the Caddy website, other than basic usage examples. They are managed by the individual module authors, and you will have to contact them to change their documentation.
|
||||
|
||||
|
||||
|
||||
## Collaborator Instructions
|
||||
|
||||
Collaborators have push rights to the repository. We grant this permission after one or more successful, high-quality PRs are merged! We thank them for their help.The expectations we have of collaborators are:
|
||||
|
||||
- **Help review pull requests.** Be meticulous, but also kind. We love our contributors, but we critique the contribution to make it better. Multiple, thorough reviews make for the best contributions! Here are some questions to consider:
|
||||
- Can the change be made more elegant?
|
||||
- Is this a maintenance burden?
|
||||
- What assumptions does the code make?
|
||||
- Is it well-tested?
|
||||
- Is the change a good fit for the project?
|
||||
- Does it actually fix the problem or is it creating a special case instead?
|
||||
- Does the change incur any new dependencies? (Avoid these!)
|
||||
|
||||
- **Answer issues.** If every collaborator helped out with issues, we could count the number of open issues on two hands. This means getting involved in the discussion, investigating the code, and yes, debugging it. It's fun. Really! :smile: Please, please help with open issues. Granted, some issues need to be done before others. And of course some are larger than others: you don't have to do it all yourself. Work with other collaborators as a team!
|
||||
|
||||
- **Do not merge pull requests until they have been approved by one or two other collaborators.** If a project owner approves the PR, it can be merged (as long as the conversation has finished too).
|
||||
|
||||
- **Prefer squashed commits over a messy merge.** If there are many little commits, please [squash the commits](https://stackoverflow.com/a/11732910/1048862) so we don't clutter the commit history.
|
||||
|
||||
- **Don't accept new dependencies lightly.** Dependencies can make the world crash and burn, but they are sometimes necessary. Choose carefully. Extremely small dependencies (a few lines of code) can be inlined. The rest may not be needed. For those that are, Caddy uses [go modules](https://github.com/golang/go/wiki/Modules). All external dependencies must be installed as modules, and _Caddy must not export any types defined by those dependencies_. Check this diligently!
|
||||
|
||||
- **Be extra careful in some areas of the code.** There are some critical areas in the Caddy code base that we review extra meticulously: the `caddyhttp` and `caddytls` packages especially.
|
||||
|
||||
- **Make sure tests test the actual thing.** Double-check that the tests fail without the change, and pass with it. It's important that they assert what they're purported to assert.
|
||||
|
||||
- **Recommended reading**
|
||||
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
|
||||
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
|
||||
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
|
||||
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
|
||||
|
||||
|
||||
|
||||
## Values
|
||||
|
||||
- A person is always more important than code. People don't like being handled "efficiently". But we can still process issues and pull requests efficiently while being kind, patient, and considerate.
|
||||
|
||||
- The ends justify the means, if the means are good. A good tree won't produce bad fruit. But if we cut corners or are hasty in our process, the end result will not be good.
|
||||
|
||||
|
||||
## Security Policy
|
||||
|
||||
If you think you've found a security vulnerability, please refer to our [Security Policy](https://github.com/caddyserver/caddy/security/policy) document.
|
||||
|
||||
|
||||
## Thank you
|
||||
|
||||
Thanks for your help! Caddy would not be what it is today without your contributions.
|
||||
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [mholt] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -0,0 +1,27 @@
|
||||
# Security Policy
|
||||
|
||||
The Caddy project would like to make sure that it stays on top of all practically-exploitable vulnerabilities.
|
||||
|
||||
Some security problems are more the result of interplay between different components of the Web, rather than a vulnerability in the web server itself. Please report only vulnerabilities in the web server itself, as we cannot coerce the rest of the Web to be fixed (for example, we do not consider IP spoofing or BGP hijacks a vulnerability in the Caddy web server).
|
||||
|
||||
Please note that we consider publicly-registered domain names to be public information. This necessary in order to maintain the integrity of certificate transparency, public DNS, and other public trust systems.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | :white_check_mark: |
|
||||
| 1.x | :white_check_mark: (deprecating soon) |
|
||||
| < 1.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please email Matt Holt (the author) directly: matt [at] lightcodelabs [dot com].
|
||||
|
||||
We'll need enough information to verify the bug and make a patch. It will speed things up if you suggest a working patch, such as a code diff, and explain why and how it works. Reports that are not actionable, do not contain enough information, are too pushy/demanding, or are not able to convince us that it is a viable and practical attack on the web server itself may be deferred to a later time or possibly ignored, resources permitting. Priority will be given to credible, responsible reports that are constructive, specific, and actionable. Thank you for understanding.
|
||||
|
||||
Please also understand that due to our nature as an open source project, we do not have a budget to award security bounties. We can only thank you.
|
||||
|
||||
If your report is valid and a patch is released, we will not reveal your identity by default. If you wish to be credited, please give us the name to use.
|
||||
|
||||
Thanks for responsibly helping Caddy—and thousands of websites—be more secure!
|
||||
@@ -0,0 +1,175 @@
|
||||
# Used as inspiration: https://github.com/mvdan/github-actions-golang
|
||||
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
# Default is true, cancels jobs for other platforms in the matrix if one fails
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
|
||||
# Set some variables per OS, usable via ${{ matrix.VAR }}
|
||||
# CADDY_BIN_PATH: the path to the compiled Caddy binary, for artifact publishing
|
||||
# SUCCESS: the typical value for $? per OS (Windows/pwsh returns 'True')
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
CADDY_BIN_PATH: ./cmd/caddy/caddy
|
||||
SUCCESS: 0
|
||||
|
||||
- os: macos-latest
|
||||
CADDY_BIN_PATH: ./cmd/caddy/caddy
|
||||
SUCCESS: 0
|
||||
|
||||
- os: windows-latest
|
||||
CADDY_BIN_PATH: ./cmd/caddy/caddy.exe
|
||||
SUCCESS: 'True'
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# These tools would be useful if we later decide to reinvestigate
|
||||
# publishing test/coverage reports to some tool for easier consumption
|
||||
# - name: Install test and coverage analysis tools
|
||||
# run: |
|
||||
# go get github.com/axw/gocov/gocov
|
||||
# go get github.com/AlekSi/gocov-xml
|
||||
# go get -u github.com/jstemmer/go-junit-report
|
||||
# echo "::add-path::$(go env GOPATH)/bin"
|
||||
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
# Calculate the short SHA1 hash of the git commit
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
key: ${{ runner.os }}-go-ci-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-ci
|
||||
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
go get -v -t -d ./...
|
||||
# mkdir test-results
|
||||
|
||||
- name: Build Caddy
|
||||
working-directory: ./cmd/caddy
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
go build -trimpath -ldflags="-w -s" -v
|
||||
|
||||
- name: Publish Build Artifact
|
||||
uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: caddy_v2_${{ runner.os }}_${{ steps.vars.outputs.short_sha }}
|
||||
path: ${{ matrix.CADDY_BIN_PATH }}
|
||||
|
||||
# Commented bits below were useful to allow the job to continue
|
||||
# even if the tests fail, so we can publish the report separately
|
||||
# For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status
|
||||
- name: Run tests
|
||||
# id: step_test
|
||||
# continue-on-error: true
|
||||
run: |
|
||||
# (go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||
go test -v -coverprofile="cover-profile.out" -short -race ./...
|
||||
# echo "::set-output name=status::$?"
|
||||
|
||||
# Relevant step if we reinvestigate publishing test/coverage reports
|
||||
# - name: Prepare coverage reports
|
||||
# run: |
|
||||
# mkdir coverage
|
||||
# gocov convert cover-profile.out > coverage/coverage.json
|
||||
# # Because Windows doesn't work with input redirection like *nix, but output redirection works.
|
||||
# (cat ./coverage/coverage.json | gocov-xml) > coverage/coverage.xml
|
||||
|
||||
# To return the correct result even though we set 'continue-on-error: true'
|
||||
# - name: Coerce correct build result
|
||||
# if: matrix.os != 'windows-latest' && steps.step_test.outputs.status != ${{ matrix.SUCCESS }}
|
||||
# run: |
|
||||
# echo "step_test ${{ steps.step_test.outputs.status }}\n"
|
||||
# exit 1
|
||||
|
||||
s390x-test:
|
||||
name: test (s390x on IBM Z)
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
continue-on-error: true # August 2020: s390x VM is down due to weather and power issues
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Run Tests
|
||||
run: |
|
||||
mkdir -p ~/.ssh && echo -e "${SSH_KEY//_/\\n}" > ~/.ssh/id_ecdsa && chmod og-rwx ~/.ssh/id_ecdsa
|
||||
|
||||
# short sha is enough?
|
||||
short_sha=$(git rev-parse --short HEAD)
|
||||
|
||||
# The environment is fresh, so there's no point in keeping accepting and adding the key.
|
||||
rsync -arz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress --delete --exclude '.git' . caddy-ci@ci-s390x.caddyserver.com:/var/tmp/"$short_sha"
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -t caddy-ci@ci-s390x.caddyserver.com "cd /var/tmp/$short_sha; CGO_ENABLED=0 /usr/local/go/bin/go test -v ./..."
|
||||
test_result=$?
|
||||
|
||||
# There's no need leaving the files around
|
||||
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null caddy-ci@ci-s390x.caddyserver.com "rm -rf /var/tmp/'$short_sha'"
|
||||
|
||||
echo "Test exit code: $test_result"
|
||||
exit $test_result
|
||||
env:
|
||||
SSH_KEY: ${{ secrets.S390X_SSH_KEY }}
|
||||
|
||||
# From https://github.com/reviewdog/action-golangci-lint
|
||||
golangci-lint:
|
||||
name: runner / golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: reviewdog/action-golangci-lint@v1
|
||||
# uses: docker://reviewdog/action-golangci-lint:v1 # pre-build docker image
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
||||
goreleaser-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: check
|
||||
env:
|
||||
TAG: ${{ steps.vars.outputs.version_tag }}
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Cross-Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
cross-build-test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
goos: ['android', 'linux', 'solaris', 'illumos', 'dragonfly', 'freebsd', 'openbsd', 'plan9', 'windows', 'darwin', 'netbsd']
|
||||
go-version: [ 1.14.x ]
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
key: cross-build-go-${{ matrix.goos }}-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
cross-build-go-${{ matrix.goos }}
|
||||
- name: Checkout code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
- name: Run Build
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: ${{ matrix.goos }}
|
||||
shell: bash
|
||||
continue-on-error: true
|
||||
working-directory: ./cmd/caddy
|
||||
run: |
|
||||
GOOS=$GOOS go build -trimpath -o caddy-"$GOOS"-amd64 2> /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "::warning ::$GOOS Build Failed"
|
||||
exit 0
|
||||
fi
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Fuzzing
|
||||
|
||||
on:
|
||||
# Daily midnight fuzzing
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
fuzzing:
|
||||
name: Fuzzing
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download go-fuzz tools and the Fuzzit CLI, move Fuzzit CLI to GOBIN
|
||||
# If we decide we need to prevent this from running on forks, we can use this line:
|
||||
# if: github.repository == 'caddyserver/caddy'
|
||||
run: |
|
||||
|
||||
go get -v github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
wget -q -O fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.77/fuzzit_Linux_x86_64
|
||||
chmod a+x fuzzit
|
||||
mv fuzzit $(go env GOPATH)/bin
|
||||
echo "::add-path::$(go env GOPATH)/bin"
|
||||
|
||||
- name: Generate fuzzers & submit them to Fuzzit
|
||||
continue-on-error: true
|
||||
env:
|
||||
FUZZIT_API_KEY: ${{ secrets.FUZZIT_API_KEY }}
|
||||
SYSTEM_PULLREQUEST_SOURCEBRANCH: ${{ github.ref }}
|
||||
BUILD_SOURCEVERSION: ${{ github.sha }}
|
||||
run: |
|
||||
# debug
|
||||
echo "PR Source Branch: $SYSTEM_PULLREQUEST_SOURCEBRANCH"
|
||||
echo "Source version: $BUILD_SOURCEVERSION"
|
||||
|
||||
declare -A fuzzers_funcs=(\
|
||||
["./caddyconfig/httpcaddyfile/addresses_fuzz.go"]="FuzzParseAddress" \
|
||||
["./listeners_fuzz.go"]="FuzzParseNetworkAddress" \
|
||||
["./replacer_fuzz.go"]="FuzzReplacer" \
|
||||
)
|
||||
|
||||
declare -A fuzzers_targets=(\
|
||||
["./caddyconfig/httpcaddyfile/addresses_fuzz.go"]="parse-address" \
|
||||
["./listeners_fuzz.go"]="parse-network-address" \
|
||||
["./replacer_fuzz.go"]="replacer" \
|
||||
)
|
||||
|
||||
fuzz_type="fuzzing"
|
||||
|
||||
for f in $(find . -name \*_fuzz.go); do
|
||||
FUZZER_DIRECTORY=$(dirname "$f")
|
||||
|
||||
echo "go-fuzz-build func ${fuzzers_funcs[$f]} residing in $f"
|
||||
|
||||
go-fuzz-build -func "${fuzzers_funcs[$f]}" -o "$FUZZER_DIRECTORY/${fuzzers_targets[$f]}.zip" "$FUZZER_DIRECTORY"
|
||||
|
||||
fuzzit create job --engine go-fuzz caddyserver/"${fuzzers_targets[$f]}" "$FUZZER_DIRECTORY"/"${fuzzers_targets[$f]}.zip" --api-key "${FUZZIT_API_KEY}" --type "${fuzz_type}" --branch "${SYSTEM_PULLREQUEST_SOURCEBRANCH}" --revision "${BUILD_SOURCEVERSION}"
|
||||
|
||||
echo "Completed $f"
|
||||
done
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go-version: [ 1.14.x ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# So GoReleaser can generate the changelog properly
|
||||
- name: Unshallowify the repo clone
|
||||
run: git fetch --prune --unshallow
|
||||
|
||||
# https://github.community/t5/GitHub-Actions/How-to-get-just-the-tag-name/m-p/32167/highlight/true#M1027
|
||||
- name: Print Go version and environment
|
||||
id: vars
|
||||
run: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
echo "::set-output name=version_tag::${GITHUB_REF/refs\/tags\//}"
|
||||
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
|
||||
echo "::set-output name=go_cache::$(go env GOCACHE)"
|
||||
|
||||
# Parse semver
|
||||
TAG=${GITHUB_REF/refs\/tags\//}
|
||||
SEMVER_RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z\.-]*\)'
|
||||
TAG_MAJOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\1#"`
|
||||
TAG_MINOR=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\2#"`
|
||||
TAG_PATCH=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\3#"`
|
||||
TAG_SPECIAL=`echo ${TAG#v} | sed -e "s#$SEMVER_RE#\4#"`
|
||||
echo "::set-output name=tag_major::${TAG_MAJOR}"
|
||||
echo "::set-output name=tag_minor::${TAG_MINOR}"
|
||||
echo "::set-output name=tag_patch::${TAG_PATCH}"
|
||||
echo "::set-output name=tag_special::${TAG_SPECIAL}"
|
||||
|
||||
- name: Cache the build cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.vars.outputs.go_cache }}
|
||||
key: ${{ runner.os }}-go-release-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-release
|
||||
|
||||
# GoReleaser will take care of publishing those artifacts into the release
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
with:
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.vars.outputs.version_tag }}
|
||||
|
||||
# Only publish on non-special tags (e.g. non-beta)
|
||||
- name: Publish .deb to Gemfury
|
||||
if: ${{ steps.vars.outputs.tag_special == '' }}
|
||||
env:
|
||||
GEMFURY_PUSH_TOKEN: ${{ secrets.GEMFURY_PUSH_TOKEN }}
|
||||
run: |
|
||||
for filename in dist/*.deb; do
|
||||
curl -F package=@"$filename" https://${GEMFURY_PUSH_TOKEN}:@push.fury.io/caddy/
|
||||
done
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Release Published
|
||||
|
||||
# Event payload: https://developer.github.com/webhooks/event-payloads/#release
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release Published
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
|
||||
# See https://github.com/peter-evans/repository-dispatch
|
||||
- name: Trigger event on caddyserver/dist
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: caddyserver/dist
|
||||
event-type: release-tagged
|
||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||
|
||||
- name: Trigger event on caddyserver/caddy-docker
|
||||
uses: peter-evans/repository-dispatch@v1
|
||||
with:
|
||||
token: ${{ secrets.REPO_DISPATCH_TOKEN }}
|
||||
repository: caddyserver/caddy-docker
|
||||
event-type: release-tagged
|
||||
client-payload: '{"tag": "${{ github.event.release.tag_name }}"}'
|
||||
|
||||
@@ -16,3 +16,8 @@ cmd/caddy/caddy.exe
|
||||
|
||||
# go modules
|
||||
vendor
|
||||
|
||||
# goreleaser artifacts
|
||||
dist
|
||||
caddy-build
|
||||
caddy-dist
|
||||
|
||||
@@ -8,6 +8,8 @@ linters-settings:
|
||||
linters:
|
||||
enable:
|
||||
- bodyclose
|
||||
- prealloc
|
||||
- unconvert
|
||||
- errcheck
|
||||
- gofmt
|
||||
- goimports
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
before:
|
||||
hooks:
|
||||
# The build is done in this particular way to build Caddy in a designated directory named in .gitignore.
|
||||
# This is so we can run goreleaser on tag without Git complaining of being dirty. The main.go in cmd/caddy directory
|
||||
# cannot be built within that directory due to changes necessary for the build causing Git to be dirty, which
|
||||
# subsequently causes gorleaser to refuse running.
|
||||
- mkdir -p caddy-build
|
||||
- cp cmd/caddy/main.go caddy-build/main.go
|
||||
- cp ./go.mod caddy-build/go.mod
|
||||
- sed -i.bkp 's|github.com/caddyserver/caddy/v2|caddy|g' ./caddy-build/go.mod
|
||||
# GoReleaser doesn't seem to offer {{.Tag}} at this stage, so we have to embed it into the env
|
||||
# so we run: TAG=$(git describe --abbrev=0) goreleaser release --rm-dist --skip-publish --skip-validate
|
||||
- go mod edit -require=github.com/caddyserver/caddy/v2@{{.Env.TAG}} ./caddy-build/go.mod
|
||||
- git clone --depth 1 https://github.com/caddyserver/dist caddy-dist
|
||||
- go mod download
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
- GO111MODULE=on
|
||||
main: main.go
|
||||
dir: ./caddy-build
|
||||
binary: caddy
|
||||
goos:
|
||||
- darwin
|
||||
- linux
|
||||
- windows
|
||||
- freebsd
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- s390x
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
ignore:
|
||||
- goos: darwin
|
||||
goarch: arm
|
||||
- goos: darwin
|
||||
goarch: ppc64le
|
||||
- goos: darwin
|
||||
goarch: s390x
|
||||
- goos: windows
|
||||
goarch: ppc64le
|
||||
- goos: windows
|
||||
goarch: s390x
|
||||
- goos: freebsd
|
||||
goarch: ppc64le
|
||||
- goos: freebsd
|
||||
goarch: s390x
|
||||
- goos: freebsd
|
||||
goarch: arm
|
||||
goarm: 5
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
|
||||
archives:
|
||||
- format_overrides:
|
||||
- goos: windows
|
||||
format: zip
|
||||
replacements:
|
||||
darwin: mac
|
||||
checksum:
|
||||
algorithm: sha512
|
||||
|
||||
nfpms:
|
||||
- id: default
|
||||
package_name: caddy
|
||||
|
||||
vendor: Light Code Labs
|
||||
homepage: https://caddyserver.com
|
||||
maintainer: Matthew Holt <mholt@users.noreply.github.com>
|
||||
description: |
|
||||
Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
|
||||
license: Apache 2.0
|
||||
|
||||
formats:
|
||||
- deb
|
||||
# - rpm
|
||||
|
||||
bindir: /usr/bin
|
||||
files:
|
||||
./caddy-dist/init/caddy.service: /lib/systemd/system/caddy.service
|
||||
./caddy-dist/init/caddy-api.service: /lib/systemd/system/caddy-api.service
|
||||
./caddy-dist/welcome/index.html: /usr/share/caddy/index.html
|
||||
./caddy-dist/scripts/completions/bash-completion: /etc/bash_completion.d/caddy
|
||||
config_files:
|
||||
./caddy-dist/config/Caddyfile: /etc/caddy/Caddyfile
|
||||
|
||||
scripts:
|
||||
postinstall: ./caddy-dist/scripts/postinstall.sh
|
||||
preremove: ./caddy-dist/scripts/preremove.sh
|
||||
postremove: ./caddy-dist/scripts/postremove.sh
|
||||
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: caddyserver
|
||||
name: caddy
|
||||
draft: true
|
||||
prerelease: auto
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^chore:'
|
||||
- '^ci:'
|
||||
- '^docs?:'
|
||||
- '^readme:'
|
||||
- '^tests?:'
|
||||
- '^\w+\s+' # a hack to remove commit messages without colons thus don't correspond to a package
|
||||
@@ -1,41 +1,31 @@
|
||||
Caddy 2
|
||||
=======
|
||||
|
||||
This is the development branch for Caddy 2.
|
||||
|
||||
**Caddy 2 is production-ready, but there may be breaking changes before the stable 2.0 release.** Please test it and deploy it as much as you are able, and submit your feedback!
|
||||
|
||||
**Caddy 2 is the web server of the Go community.** We are looking for maintainers to represent the community! Please become involved with issues, PRs, [our forum](https://caddy.community), sharing on social media, etc.
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<a href="https://caddyserver.com"><img src="https://user-images.githubusercontent.com/1128849/36338535-05fb646a-136f-11e8-987b-e6901e717d5a.png" alt="Caddy" width="450"></a>
|
||||
</p>
|
||||
<h3 align="center">Every site on HTTPS</h3>
|
||||
<p align="center">Caddy is an extensible server platform that uses TLS by default.</p>
|
||||
<p align="center">
|
||||
<a href="https://dev.azure.com/mholt-dev/Caddy/_build/latest?definitionId=5&branchName=v2"><img src="https://dev.azure.com/mholt-dev/Caddy/_apis/build/status/Multiplatform%20Tests?branchName=v2"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-blue.svg"></a>
|
||||
<a href="https://app.fuzzit.dev/orgs/caddyserver-gh/dashboard"><img src="https://app.fuzzit.dev/badge?org_id=caddyserver-gh"></a>
|
||||
<a href="https://github.com/caddyserver/caddy/actions?query=workflow%3ACross-Platform"><img src="https://github.com/caddyserver/caddy/workflows/Cross-Platform/badge.svg"></a>
|
||||
<a href="https://pkg.go.dev/github.com/caddyserver/caddy/v2"><img src="https://img.shields.io/badge/godoc-reference-%23007d9c.svg"></a>
|
||||
<br>
|
||||
<a href="https://twitter.com/caddyserver" title="@caddyserver on Twitter"><img src="https://img.shields.io/badge/twitter-@caddyserver-55acee.svg" alt="@caddyserver on Twitter"></a>
|
||||
<a href="https://caddy.community" title="Caddy Forum"><img src="https://img.shields.io/badge/community-forum-ff69b4.svg" alt="Caddy Forum"></a>
|
||||
<a href="https://sourcegraph.com/github.com/caddyserver/caddy?badge" title="Caddy on Sourcegraph"><img src="https://sourcegraph.com/github.com/caddyserver/caddy/-/badge.svg" alt="Caddy on Sourcegraph"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://github.com/caddyserver/caddy/releases">Download</a> ·
|
||||
<a href="https://github.com/caddyserver/caddy/releases">Releases</a> ·
|
||||
<a href="https://caddyserver.com/docs/">Documentation</a> ·
|
||||
<a href="https://caddy.community">Community</a>
|
||||
<a href="https://caddy.community">Get Help</a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
### Menu
|
||||
|
||||
- [Features](#features)
|
||||
- [Build from source](#build-from-source)
|
||||
- [Building with plugins](#building-with-plugins-and/or-version-information)
|
||||
- [Getting started](#getting-started)
|
||||
- [For development](#for-development)
|
||||
- [With version information and/or plugins](#with-version-information-andor-plugins)
|
||||
- [Quick start](#quick-start)
|
||||
- [Overview](#overview)
|
||||
- [Full documentation](#full-documentation)
|
||||
- [Getting help](#getting-help)
|
||||
@@ -47,46 +37,60 @@ This is the development branch for Caddy 2.
|
||||
<a href="https://github.com/caddyserver/certmagic"><img src="https://user-images.githubusercontent.com/1128849/49704830-49d37200-fbd5-11e8-8385-767e0cd033c3.png" alt="CertMagic" width="250"></a>
|
||||
</p>
|
||||
|
||||
## Build from source
|
||||
|
||||
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions below for building with plugins (you do not have to add any plugins)._
|
||||
## Features
|
||||
|
||||
- **Easy configuration** with the [Caddyfile](https://caddyserver.com/docs/caddyfile)
|
||||
- **Powerful configuration** with its [native JSON config](https://caddyserver.com/docs/json/)
|
||||
- **Dynamic configuration** with the [JSON API](https://caddyserver.com/docs/api)
|
||||
- [**Config adapters**](https://caddyserver.com/docs/config-adapters) if you don't like JSON
|
||||
- **Automatic HTTPS** by default
|
||||
- [Let's Encrypt](https://letsencrypt.org) for public sites
|
||||
- Fully-managed local CA for internal names & IPs
|
||||
- Can coordinate with other Caddy instances in a cluster
|
||||
- **Stays up when other servers go down** due to TLS/OCSP/certificate-related issues
|
||||
- **HTTP/1.1, HTTP/2, and experimental HTTP/3** support
|
||||
- **Highly extensible** [modular architecture](https://caddyserver.com/docs/architecture) lets Caddy do anything without bloat
|
||||
- **Runs anywhere** with **no external dependencies** (not even libc)
|
||||
- Written in Go, a language with higher **memory safety guarantees** than other servers
|
||||
- Actually **fun to use**
|
||||
- So, so much more to discover
|
||||
|
||||
|
||||
|
||||
## Build from source
|
||||
|
||||
Requirements:
|
||||
|
||||
- [Go 1.14 or newer](https://golang.org/dl/)
|
||||
- Do NOT disable [Go modules](https://github.com/golang/go/wiki/Modules) (`export GO111MODULE=auto`)
|
||||
|
||||
Download the `v2` source code:
|
||||
|
||||
```bash
|
||||
$ git clone -b v2 "https://github.com/caddyserver/caddy.git"
|
||||
```
|
||||
|
||||
Build:
|
||||
|
||||
### For development
|
||||
|
||||
```bash
|
||||
$ git clone "https://github.com/caddyserver/caddy.git"
|
||||
$ cd caddy/cmd/caddy/
|
||||
$ go build
|
||||
```
|
||||
|
||||
That will put a `caddy(.exe)` binary into the current directory.
|
||||
_**Note:** These steps [will not embed proper version information](https://github.com/golang/go/issues/29228). For that, please follow the instructions below._
|
||||
|
||||
If you encounter any Go-module-related errors, try clearing your Go module cache (`$GOPATH/pkg/mod`) or Go package cache (`$GOPATH/pkg`) and read [the Go wiki page about modules for help](https://github.com/golang/go/wiki/Modules). If you have issues with Go modules, please consult the Go community for help. But if there is an actual error in Caddy, please report it to us.
|
||||
### With version information and/or plugins
|
||||
|
||||
### Building with plugins and/or version information
|
||||
Using [our builder tool, `xcaddy`](https://github.com/caddyserver/xcaddy)...
|
||||
|
||||
Caddy is extensible with plugins. Plugins are added at compile-time, so all Caddy binaries are static (self-contained) and portable.
|
||||
```
|
||||
$ xcaddy build
|
||||
```
|
||||
|
||||
Instructions for doing this are also given in comments in [cmd/caddy/main.go](https://github.com/caddyserver/caddy/blob/v2/cmd/caddy/main.go) which you can copy and use as a template.
|
||||
...the following steps are automated:
|
||||
|
||||
1. Create a new folder: `mkdir caddy`
|
||||
2. Change into it: `cd caddy`
|
||||
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/v2/cmd/caddy/main.go) into the empty folder. Add imports for any plugins you want to include.
|
||||
4. Run: `go mod init caddy`
|
||||
5. Run: `go get github.com/caddyserver/caddy/v2@TAG` replacing `TAG` with the latest v2 tag. (Won't be necessary after stable 2.0 release.)
|
||||
6. Run: `go build`
|
||||
|
||||
Congrats, you now have a custom Caddy build with proper version information!
|
||||
3. Copy [Caddy's main.go](https://github.com/caddyserver/caddy/blob/master/cmd/caddy/main.go) into the empty folder. Add imports for any custom plugins you want to add.
|
||||
4. Initialize a Go module: `go mod init caddy`
|
||||
5. (Optional) Pin Caddy version: `go get github.com/caddyserver/caddy/v2@version` replacing `version` with a git tag or commit.
|
||||
6. (Optional) Add plugins by adding their import: `_ "import/path/here"`
|
||||
7. Compile: `go build`
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +101,7 @@ The [Caddy website](https://caddyserver.com/docs/) has documentation that includ
|
||||
|
||||
**We recommend that all users do our [Getting Started](https://caddyserver.com/docs/getting-started) guide to become familiar with using Caddy.**
|
||||
|
||||
If you've only got a few minutes, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂
|
||||
If you've only got a minute, [the website has several quick-start tutorials](https://caddyserver.com/docs/quick-starts) to choose from! However, after finishing a quick-start tutorial, please read more documentation to understand how the software works. 🙂
|
||||
|
||||
|
||||
|
||||
@@ -114,7 +118,7 @@ The primary way to configure Caddy is through [its API](https://caddyserver.com/
|
||||
|
||||
Caddy exposes an unprecedented level of control compared to any web server in existence. In Caddy, you are usually setting the actual values of the initialized types in memory that power everything from your HTTP handlers and TLS handshakes to your storage medium. Caddy is also ridiculously extensible, with a powerful plugin system that makes vast improvements over other web servers.
|
||||
|
||||
To wield the power of this design, you need to know how the config document is structured. Please see the [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
|
||||
To wield the power of this design, you need to know how the config document is structured. Please see [our documentation site](https://caddyserver.com/docs/) for details about [Caddy's config structure](https://caddyserver.com/docs/json/).
|
||||
|
||||
Nearly all of Caddy's configuration is contained in a single config document, rather than being scattered across CLI flags and env variables and a configuration file as with other web servers. This makes managing your server config more straightforward and reduces hidden variables/factors.
|
||||
|
||||
@@ -135,7 +139,7 @@ The docs are also open source. You can contribute to them here: https://github.c
|
||||
|
||||
- Individuals can exchange help for free on our community forum at https://caddy.community. Remember that people give help out of their spare time and good will. The best way to get help is to give it first!
|
||||
|
||||
Please use our [issue tracker](/caddyserver/caddy/issues) only for bug reports and feature requests, i.e. actionable development items (support questions will usually be referred to the forums).
|
||||
Please use our [issue tracker](https://github.com/caddyserver/caddy/issues) only for bug reports and feature requests, i.e. actionable development items (support questions will usually be referred to the forums).
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
@@ -33,7 +35,6 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -61,10 +62,11 @@ type AdminConfig struct {
|
||||
// default.
|
||||
EnforceOrigin bool `json:"enforce_origin,omitempty"`
|
||||
|
||||
// The list of allowed origins for API requests. Only used if
|
||||
// `enforce_origin` is true. If not set, the listener address
|
||||
// will be the default value. If set but empty, no origins will
|
||||
// be allowed.
|
||||
// The list of allowed origins/hosts for API requests. Only needed
|
||||
// if accessing the admin endpoint from a host different from the
|
||||
// socket's network interface or if `enforce_origin` is true. If not
|
||||
// set, the listener address will be the default value. If set but
|
||||
// empty, no origins will be allowed.
|
||||
Origins []string `json:"origins,omitempty"`
|
||||
|
||||
// Options related to configuration management.
|
||||
@@ -80,27 +82,28 @@ type ConfigSettings struct {
|
||||
|
||||
// listenAddr extracts a singular listen address from ac.Listen,
|
||||
// returning the network and the address of the listener.
|
||||
func (admin AdminConfig) listenAddr() (string, string, error) {
|
||||
func (admin AdminConfig) listenAddr() (NetworkAddress, error) {
|
||||
input := admin.Listen
|
||||
if input == "" {
|
||||
input = DefaultAdminListen
|
||||
}
|
||||
listenAddr, err := ParseNetworkAddress(input)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("parsing admin listener address: %v", err)
|
||||
return NetworkAddress{}, fmt.Errorf("parsing admin listener address: %v", err)
|
||||
}
|
||||
if listenAddr.PortRangeSize() != 1 {
|
||||
return "", "", fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
|
||||
return NetworkAddress{}, fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddr)
|
||||
}
|
||||
return listenAddr.Network, listenAddr.JoinHostPort(0), nil
|
||||
return listenAddr, nil
|
||||
}
|
||||
|
||||
// newAdminHandler reads admin's config and returns an http.Handler suitable
|
||||
// for use in an admin endpoint server, which will be listening on listenAddr.
|
||||
func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
|
||||
func (admin AdminConfig) newAdminHandler(addr NetworkAddress) adminHandler {
|
||||
muxWrap := adminHandler{
|
||||
enforceOrigin: admin.EnforceOrigin,
|
||||
allowedOrigins: admin.allowedOrigins(listenAddr),
|
||||
enforceHost: !addr.isWildcardInterface(),
|
||||
allowedOrigins: admin.allowedOrigins(addr),
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
|
||||
@@ -115,7 +118,6 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
|
||||
}
|
||||
|
||||
// register standard config control endpoints
|
||||
addRoute("/load", AdminHandlerFunc(handleLoad))
|
||||
addRoute("/"+rawConfigKey+"/", AdminHandlerFunc(handleConfig))
|
||||
addRoute("/id/", AdminHandlerFunc(handleConfigID))
|
||||
addRoute("/stop", AdminHandlerFunc(handleStop))
|
||||
@@ -143,16 +145,32 @@ func (admin AdminConfig) newAdminHandler(listenAddr string) adminHandler {
|
||||
// If admin.Origins is nil (null), the provided listen address
|
||||
// will be used as the default origin. If admin.Origins is
|
||||
// empty, no origins will be allowed, effectively bricking the
|
||||
// endpoint, but whatever.
|
||||
func (admin AdminConfig) allowedOrigins(listen string) []string {
|
||||
// endpoint for non-unix-socket endpoints, but whatever.
|
||||
func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []string {
|
||||
uniqueOrigins := make(map[string]struct{})
|
||||
for _, o := range admin.Origins {
|
||||
uniqueOrigins[o] = struct{}{}
|
||||
}
|
||||
if admin.Origins == nil {
|
||||
uniqueOrigins[listen] = struct{}{}
|
||||
if addr.isLoopback() {
|
||||
if addr.IsUnixNetwork() {
|
||||
// RFC 2616, Section 14.26:
|
||||
// "A client MUST include a Host header field in all HTTP/1.1 request
|
||||
// messages. If the requested URI does not include an Internet host
|
||||
// name for the service being requested, then the Host header field MUST
|
||||
// be given with an empty value."
|
||||
uniqueOrigins[""] = struct{}{}
|
||||
} else {
|
||||
uniqueOrigins[net.JoinHostPort("localhost", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("::1", addr.port())] = struct{}{}
|
||||
uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
|
||||
}
|
||||
}
|
||||
if !addr.IsUnixNetwork() {
|
||||
uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
|
||||
}
|
||||
}
|
||||
var allowed []string
|
||||
allowed := make([]string, 0, len(uniqueOrigins))
|
||||
for origin := range uniqueOrigins {
|
||||
allowed = append(allowed, origin)
|
||||
}
|
||||
@@ -198,14 +216,14 @@ func replaceAdmin(cfg *Config) error {
|
||||
}
|
||||
|
||||
// extract a singular listener address
|
||||
netw, addr, err := adminConfig.listenAddr()
|
||||
addr, err := adminConfig.listenAddr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
handler := adminConfig.newAdminHandler(addr)
|
||||
|
||||
ln, err := Listen(netw, addr)
|
||||
ln, err := Listen(addr.Network, addr.JoinHostPort(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -218,14 +236,22 @@ func replaceAdmin(cfg *Config) error {
|
||||
MaxHeaderBytes: 1024 * 64,
|
||||
}
|
||||
|
||||
go adminServer.Serve(ln)
|
||||
adminLogger := Log().Named("admin")
|
||||
go func() {
|
||||
if err := adminServer.Serve(ln); !errors.Is(err, http.ErrServerClosed) {
|
||||
adminLogger.Error("admin server shutdown for unknown reason", zap.Error(err))
|
||||
}
|
||||
}()
|
||||
|
||||
Log().Named("admin").Info(
|
||||
"admin endpoint started",
|
||||
zap.String("address", addr),
|
||||
adminLogger.Info("admin endpoint started",
|
||||
zap.String("address", addr.String()),
|
||||
zap.Bool("enforce_origin", adminConfig.EnforceOrigin),
|
||||
zap.Strings("origins", handler.allowedOrigins),
|
||||
)
|
||||
zap.Strings("origins", handler.allowedOrigins))
|
||||
|
||||
if !handler.enforceHost {
|
||||
adminLogger.Warn("admin endpoint on open interface; host checking disabled",
|
||||
zap.String("address", addr.String()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -257,6 +283,7 @@ type AdminRoute struct {
|
||||
|
||||
type adminHandler struct {
|
||||
enforceOrigin bool
|
||||
enforceHost bool
|
||||
allowedOrigins []string
|
||||
mux *http.ServeMux
|
||||
}
|
||||
@@ -266,6 +293,7 @@ type adminHandler struct {
|
||||
func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
Log().Named("admin.api").Info("received request",
|
||||
zap.String("method", r.Method),
|
||||
zap.String("host", r.Host),
|
||||
zap.String("uri", r.RequestURI),
|
||||
zap.String("remote_addr", r.RemoteAddr),
|
||||
zap.Reflect("headers", r.Header),
|
||||
@@ -277,14 +305,24 @@ func (h adminHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// be called more than once per request, for example if a request
|
||||
// is rewritten (i.e. internal redirect).
|
||||
func (h adminHandler) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if h.enforceOrigin {
|
||||
if strings.Contains(r.Header.Get("Upgrade"), "websocket") {
|
||||
// I've never been able demonstrate a vulnerability myself, but apparently
|
||||
// WebSocket connections originating from browsers aren't subject to CORS
|
||||
// restrictions, so we'll just be on the safe side
|
||||
h.handleError(w, r, fmt.Errorf("websocket connections aren't allowed"))
|
||||
return
|
||||
}
|
||||
|
||||
if h.enforceHost {
|
||||
// DNS rebinding mitigation
|
||||
err := h.checkHost(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if h.enforceOrigin {
|
||||
// cross-site mitigation
|
||||
origin, err := h.checkOrigin(r)
|
||||
if err != nil {
|
||||
@@ -407,86 +445,6 @@ func (h adminHandler) originAllowed(origin string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func handleLoad(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return APIError{
|
||||
Code: http.StatusMethodNotAllowed,
|
||||
Err: fmt.Errorf("method not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
_, err := io.Copy(buf, r.Body)
|
||||
if err != nil {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("reading request body: %v", err),
|
||||
}
|
||||
}
|
||||
body := buf.Bytes()
|
||||
|
||||
// if the config is formatted other than Caddy's native
|
||||
// JSON, we need to adapt it before loading it
|
||||
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
|
||||
ct, _, err := mime.ParseMediaType(ctHeader)
|
||||
if err != nil {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("invalid Content-Type: %v", err),
|
||||
}
|
||||
}
|
||||
if !strings.HasSuffix(ct, "/json") {
|
||||
slashIdx := strings.Index(ct, "/")
|
||||
if slashIdx < 0 {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("malformed Content-Type"),
|
||||
}
|
||||
}
|
||||
adapterName := ct[slashIdx+1:]
|
||||
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("unrecognized config adapter '%s'", adapterName),
|
||||
}
|
||||
}
|
||||
result, warnings, err := cfgAdapter.Adapt(body, nil)
|
||||
if err != nil {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("adapting config using %s adapter: %v", adapterName, err),
|
||||
}
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
respBody, err := json.Marshal(warnings)
|
||||
if err != nil {
|
||||
Log().Named("admin.api.load").Error(err.Error())
|
||||
}
|
||||
w.Write(respBody)
|
||||
}
|
||||
body = result
|
||||
}
|
||||
}
|
||||
|
||||
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
|
||||
|
||||
err = Load(body, forceReload)
|
||||
if err != nil {
|
||||
return APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("loading config: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
Log().Named("admin.api").Info("load complete")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleConfig(w http.ResponseWriter, r *http.Request) error {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
@@ -577,16 +535,19 @@ func handleStop(w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
Log().Named("admin.api").Error("unload error", zap.Error(err))
|
||||
}
|
||||
go func() {
|
||||
err := stopAdminServer(adminServer)
|
||||
var exitCode int
|
||||
if err != nil {
|
||||
exitCode = ExitCodeFailedQuit
|
||||
Log().Named("admin.api").Error("failed to stop admin server gracefully", zap.Error(err))
|
||||
}
|
||||
Log().Named("admin.api").Info("stopping now, bye!! 👋")
|
||||
os.Exit(exitCode)
|
||||
}()
|
||||
if adminServer != nil {
|
||||
// use goroutine so that we can finish responding to API request
|
||||
go func() {
|
||||
err := stopAdminServer(adminServer)
|
||||
var exitCode int
|
||||
if err != nil {
|
||||
exitCode = ExitCodeFailedQuit
|
||||
Log().Named("admin.api").Error("failed to stop admin server gracefully", zap.Error(err))
|
||||
}
|
||||
Log().Named("admin.api").Info("stopping now, bye!! 👋")
|
||||
os.Exit(exitCode)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -600,13 +561,6 @@ func handleUnload(w http.ResponseWriter, r *http.Request) error {
|
||||
Err: fmt.Errorf("method not allowed"),
|
||||
}
|
||||
}
|
||||
currentCfgMu.RLock()
|
||||
hasCfg := currentCfg != nil
|
||||
currentCfgMu.RUnlock()
|
||||
if !hasCfg {
|
||||
Log().Named("admin.api").Info("nothing to unload")
|
||||
return nil
|
||||
}
|
||||
Log().Named("admin.api").Info("unloading")
|
||||
if err := stopAndCleanup(); err != nil {
|
||||
Log().Named("admin.api").Error("error unloading", zap.Error(err))
|
||||
@@ -855,11 +809,26 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
// PIDFile writes a pidfile to the file at filename. It
|
||||
// will get deleted before the process gracefully exits.
|
||||
func PIDFile(filename string) error {
|
||||
pid := []byte(strconv.Itoa(os.Getpid()) + "\n")
|
||||
err := ioutil.WriteFile(filename, pid, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pidfile = filename
|
||||
return nil
|
||||
}
|
||||
|
||||
// idRegexp is used to match ID fields and their associated values
|
||||
// in the config. It also matches adjacent commas so that syntax
|
||||
// can be preserved no matter where in the object the field appears.
|
||||
// It supports string and most numeric values.
|
||||
var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `":\s?(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`)
|
||||
var idRegexp = regexp.MustCompile(`(?m),?\s*"` + idKey + `"\s*:\s*(-?[0-9]+(\.[0-9]+)?|(?U)".*")\s*,?`)
|
||||
|
||||
// pidfile is the name of the pidfile, if any.
|
||||
var pidfile string
|
||||
|
||||
const (
|
||||
rawConfigKey = "config"
|
||||
|
||||
@@ -1,263 +0,0 @@
|
||||
# Mutilated beyond recognition from the example at:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/languages/go
|
||||
|
||||
trigger:
|
||||
- v2
|
||||
|
||||
schedules:
|
||||
- cron: "0 0 * * *"
|
||||
displayName: Daily midnight fuzzing
|
||||
branches:
|
||||
include:
|
||||
- v2
|
||||
always: true
|
||||
|
||||
variables:
|
||||
GOROOT: $(gorootDir)/go
|
||||
GOPATH: $(system.defaultWorkingDirectory)/gopath
|
||||
GOBIN: $(GOPATH)/bin
|
||||
modulePath: '$(GOPATH)/src/github.com/$(build.repository.name)'
|
||||
|
||||
jobs:
|
||||
- job: crossPlatformTest
|
||||
displayName: "Cross-Platform Tests"
|
||||
strategy:
|
||||
matrix:
|
||||
linux:
|
||||
imageName: ubuntu-16.04
|
||||
gorootDir: /usr/local
|
||||
mac:
|
||||
imageName: macos-10.14
|
||||
gorootDir: /usr/local
|
||||
windows:
|
||||
imageName: windows-2019
|
||||
gorootDir: C:\
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
latestGo=$(curl "https://golang.org/VERSION?m=text")
|
||||
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
|
||||
echo "Latest Go version: $latestGo"
|
||||
displayName: "Get latest Go version"
|
||||
|
||||
- bash: |
|
||||
sudo rm -f $(which go)
|
||||
echo '##vso[task.prependpath]$(GOBIN)'
|
||||
echo '##vso[task.prependpath]$(GOROOT)/bin'
|
||||
mkdir -p '$(modulePath)'
|
||||
shopt -s extglob
|
||||
shopt -s dotglob
|
||||
mv !(gopath) '$(modulePath)'
|
||||
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
|
||||
|
||||
# Install Go (this varies by platform)
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Linux' )
|
||||
displayName: Install Go on Linux
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).darwin-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Darwin' )
|
||||
displayName: Install Go on macOS
|
||||
|
||||
# The low performance is partly due to PowerShell's attempt to update the progress bar. Disabling it speeds up the process.
|
||||
# Reference: https://github.com/PowerShell/PowerShell/issues/2138
|
||||
- powershell: |
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
Write-Host "Downloading Go..."
|
||||
(New-Object System.Net.WebClient).DownloadFile("https://dl.google.com/go/$(LATEST_GO).windows-amd64.zip", "$(LATEST_GO).windows-amd64.zip")
|
||||
Write-Host "Extracting Go... (I'm slow too)"
|
||||
7z x "$(LATEST_GO).windows-amd64.zip" -o"$(gorootDir)"
|
||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||
displayName: Install Go on Windows
|
||||
|
||||
- bash: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.6
|
||||
displayName: Install golangci-lint
|
||||
|
||||
- script: |
|
||||
go get github.com/axw/gocov/gocov
|
||||
go get github.com/AlekSi/gocov-xml
|
||||
go get -u github.com/jstemmer/go-junit-report
|
||||
displayName: Install test and coverage analysis tools
|
||||
|
||||
- bash: |
|
||||
printf "Using go at: $(which go)\n"
|
||||
printf "Go version: $(go version)\n"
|
||||
printf "\n\nGo environment:\n\n"
|
||||
go env
|
||||
printf "\n\nSystem environment:\n\n"
|
||||
env
|
||||
displayName: Print Go version and environment
|
||||
|
||||
- script: |
|
||||
go get -v -t -d ./...
|
||||
mkdir test-results
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Get dependencies
|
||||
|
||||
- bash: CGO_ENABLED=0 go build -trimpath -a -ldflags="-w -s" -v
|
||||
workingDirectory: '$(modulePath)/cmd/caddy'
|
||||
displayName: Build Caddy
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||
inputs:
|
||||
pathtoPublish: '$(modulePath)/cmd/caddy/caddy.exe'
|
||||
artifactName: caddy_v2.exe
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
||||
inputs:
|
||||
pathtoPublish: '$(modulePath)/cmd/caddy/caddy'
|
||||
artifactName: 'caddy_v2_$(Agent.OS)'
|
||||
|
||||
# its behavior is governed by .golangci.yml
|
||||
- script: |
|
||||
(golangci-lint run --out-format junit-xml) > test-results/lint-result.xml
|
||||
exit 0
|
||||
workingDirectory: '$(modulePath)'
|
||||
continueOnError: true
|
||||
displayName: Run lint check
|
||||
|
||||
- script: |
|
||||
(go test -v -coverprofile=cover-profile.out -race ./... 2>&1) > test-results/test-result.out
|
||||
workingDirectory: '$(modulePath)'
|
||||
continueOnError: true
|
||||
displayName: Run tests
|
||||
|
||||
- script: |
|
||||
set -e
|
||||
cmd/caddy/caddy start
|
||||
go test -v -count=1 ./caddytest/...
|
||||
cmd/caddy/caddy stop
|
||||
workingDirectory: '$(modulePath)'
|
||||
continueOnError: false
|
||||
displayName: Run Integration tests
|
||||
|
||||
- script: |
|
||||
mkdir coverage
|
||||
gocov convert cover-profile.out > coverage/coverage.json
|
||||
# Because Windows doesn't work with input redirection like *nix, but output redirection works.
|
||||
(cat ./coverage/coverage.json | gocov-xml) > coverage/coverage.xml
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Prepare coverage reports
|
||||
|
||||
- script: |
|
||||
(cat ./test-results/test-result.out | go-junit-report) > test-results/test-result.xml
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Prepare test report
|
||||
|
||||
- task: PublishCodeCoverageResults@1
|
||||
displayName: Publish test coverage report
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
summaryFileLocation: $(modulePath)/coverage/coverage.xml
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish unit test
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: $(modulePath)/test-results/test-result.xml
|
||||
testRunTitle: $(agent.OS) Unit Test
|
||||
mergeTestResults: false
|
||||
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish lint results
|
||||
inputs:
|
||||
testResultsFormat: 'JUnit'
|
||||
testResultsFiles: $(modulePath)/test-results/lint-result.xml
|
||||
testRunTitle: $(agent.OS) Lint
|
||||
mergeTestResults: false
|
||||
|
||||
- bash: |
|
||||
exit 1
|
||||
condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues')
|
||||
displayName: Coerce correct build result
|
||||
|
||||
- job: fuzzing
|
||||
displayName: 'Fuzzing'
|
||||
# Only run this job on schedules or PRs for non-forks.
|
||||
condition: or(eq(variables['System.PullRequest.IsFork'], 'False'), eq(variables['Build.Reason'], 'Schedule') )
|
||||
strategy:
|
||||
matrix:
|
||||
linux:
|
||||
imageName: ubuntu-16.04
|
||||
gorootDir: /usr/local
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
latestGo=$(curl "https://golang.org/VERSION?m=text")
|
||||
echo "##vso[task.setvariable variable=LATEST_GO]$latestGo"
|
||||
echo "Latest Go version: $latestGo"
|
||||
displayName: "Get latest Go version"
|
||||
|
||||
- bash: |
|
||||
sudo rm -f $(which go)
|
||||
echo '##vso[task.prependpath]$(GOBIN)'
|
||||
echo '##vso[task.prependpath]$(GOROOT)/bin'
|
||||
mkdir -p '$(modulePath)'
|
||||
shopt -s extglob
|
||||
shopt -s dotglob
|
||||
mv !(gopath) '$(modulePath)'
|
||||
displayName: Remove old Go, set GOBIN/GOROOT, and move project into GOPATH
|
||||
|
||||
- bash: |
|
||||
wget "https://dl.google.com/go/$(LATEST_GO).linux-amd64.tar.gz"
|
||||
sudo tar -C $(gorootDir) -xzf "$(LATEST_GO).linux-amd64.tar.gz"
|
||||
condition: eq( variables['Agent.OS'], 'Linux' )
|
||||
displayName: Install Go on Linux
|
||||
|
||||
- bash: |
|
||||
# Install Clang-7.0 because other versions seem to be missing the file libclang_rt.fuzzer-x86_64.a
|
||||
sudo add-apt-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main"
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt update && sudo apt install -y clang-7 lldb-7 lld-7
|
||||
|
||||
go get -v github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
wget -q -O fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.77/fuzzit_Linux_x86_64
|
||||
chmod a+x fuzzit
|
||||
mv fuzzit $(GOBIN)
|
||||
displayName: Download go-fuzz tools and the Fuzzit CLI, and move Fuzzit CLI to GOBIN
|
||||
condition: and(eq(variables['System.PullRequest.IsFork'], 'False') , eq( variables['Agent.OS'], 'Linux' ))
|
||||
|
||||
- bash: |
|
||||
declare -A fuzzers_funcs=(\
|
||||
["./caddyconfig/httpcaddyfile/addresses_fuzz.go"]="FuzzParseAddress" \
|
||||
["./caddyconfig/caddyfile/parse_fuzz.go"]="FuzzParseCaddyfile" \
|
||||
["./listeners_fuzz.go"]="FuzzParseNetworkAddress" \
|
||||
["./replacer_fuzz.go"]="FuzzReplacer" \
|
||||
)
|
||||
|
||||
declare -A fuzzers_targets=(\
|
||||
["./caddyconfig/httpcaddyfile/addresses_fuzz.go"]="parse-address" \
|
||||
["./caddyconfig/caddyfile/parse_fuzz.go"]="parse-caddyfile" \
|
||||
["./listeners_fuzz.go"]="parse-network-address" \
|
||||
["./replacer_fuzz.go"]="replacer" \
|
||||
)
|
||||
|
||||
fuzz_type="local-regression"
|
||||
if [[ $(Build.Reason) == "Schedule" ]]; then
|
||||
fuzz_type="fuzzing"
|
||||
fi
|
||||
echo "Fuzzing type: $fuzz_type"
|
||||
|
||||
for f in $(find . -name \*_fuzz.go); do
|
||||
FUZZER_DIRECTORY=$(dirname $f)
|
||||
echo "go-fuzz-build func ${fuzzers_funcs[$f]} residing in $f"
|
||||
go-fuzz-build -func "${fuzzers_funcs[$f]}" -libfuzzer -o "$FUZZER_DIRECTORY/${fuzzers_targets[$f]}.a" $FUZZER_DIRECTORY
|
||||
echo "Generating fuzzer binary of func ${fuzzers_funcs[$f]} which resides in $f"
|
||||
clang-7 -fsanitize=fuzzer "$FUZZER_DIRECTORY/${fuzzers_targets[$f]}.a" -o "$FUZZER_DIRECTORY/${fuzzers_targets[$f]}"
|
||||
fuzzit create job caddyserver/${fuzzers_targets[$f]} $FUZZER_DIRECTORY/${fuzzers_targets[$f]} --api-key ${FUZZIT_API_KEY} --type "${fuzz_type}" --branch "${SYSTEM_PULLREQUEST_SOURCEBRANCH}" --revision "${BUILD_SOURCEVERSION}"
|
||||
echo "Completed $f"
|
||||
done
|
||||
env:
|
||||
FUZZIT_API_KEY: $(FUZZIT_API_KEY)
|
||||
workingDirectory: '$(modulePath)'
|
||||
displayName: Generate fuzzers & submit them to Fuzzit
|
||||
@@ -372,7 +372,7 @@ func run(newCfg *Config, start bool) error {
|
||||
}
|
||||
|
||||
if newCfg.storage == nil {
|
||||
newCfg.storage = &certmagic.FileStorage{Path: AppDataDir()}
|
||||
newCfg.storage = DefaultStorage
|
||||
}
|
||||
certmagic.Default.Storage = newCfg.storage
|
||||
|
||||
@@ -470,6 +470,9 @@ func stopAndCleanup() error {
|
||||
return err
|
||||
}
|
||||
certmagic.CleanUpOwnLocks()
|
||||
if pidfile != "" {
|
||||
return os.Remove(pidfile)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -486,7 +489,7 @@ func Validate(cfg *Config) error {
|
||||
// Duration can be an integer or a string. An integer is
|
||||
// interpreted as nanoseconds. If a string, it is a Go
|
||||
// time.Duration value such as `300ms`, `1.5h`, or `2h45m`;
|
||||
// valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, and `h`.
|
||||
// valid units are `ns`, `us`/`µs`, `ms`, `s`, `m`, `h`, and `d`.
|
||||
type Duration time.Duration
|
||||
|
||||
// UnmarshalJSON satisfies json.Unmarshaler.
|
||||
@@ -497,7 +500,7 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
var dur time.Duration
|
||||
var err error
|
||||
if b[0] == byte('"') && b[len(b)-1] == byte('"') {
|
||||
dur, err = time.ParseDuration(strings.Trim(string(b), `"`))
|
||||
dur, err = ParseDuration(strings.Trim(string(b), `"`))
|
||||
} else {
|
||||
err = json.Unmarshal(b, &dur)
|
||||
}
|
||||
@@ -505,6 +508,34 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseDuration parses a duration string, adding
|
||||
// support for the "d" unit meaning number of days,
|
||||
// where a day is assumed to be 24h.
|
||||
func ParseDuration(s string) (time.Duration, error) {
|
||||
var inNumber bool
|
||||
var numStart int
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch == 'd' {
|
||||
daysStr := s[numStart:i]
|
||||
days, err := strconv.ParseFloat(daysStr, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
hours := days * 24.0
|
||||
hoursStr := strconv.FormatFloat(hours, 'f', -1, 64)
|
||||
s = s[:numStart] + hoursStr + "h" + s[i+1:]
|
||||
i--
|
||||
continue
|
||||
}
|
||||
if !inNumber {
|
||||
numStart = i
|
||||
}
|
||||
inNumber = (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+'
|
||||
}
|
||||
return time.ParseDuration(s)
|
||||
}
|
||||
|
||||
// GoModule returns the build info of this Caddy
|
||||
// build from debug.BuildInfo (requires Go modules).
|
||||
// If no version information is available, a non-nil
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseDuration(t *testing.T) {
|
||||
const day = 24 * time.Hour
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expect time.Duration
|
||||
}{
|
||||
{
|
||||
input: "3h",
|
||||
expect: 3 * time.Hour,
|
||||
},
|
||||
{
|
||||
input: "1d",
|
||||
expect: day,
|
||||
},
|
||||
{
|
||||
input: "1d30m",
|
||||
expect: day + 30*time.Minute,
|
||||
},
|
||||
{
|
||||
input: "1m2d",
|
||||
expect: time.Minute + day*2,
|
||||
},
|
||||
{
|
||||
input: "1m2d30s",
|
||||
expect: time.Minute + day*2 + 30*time.Second,
|
||||
},
|
||||
{
|
||||
input: "1d2d",
|
||||
expect: 3 * day,
|
||||
},
|
||||
{
|
||||
input: "1.5d",
|
||||
expect: time.Duration(1.5 * float64(day)),
|
||||
},
|
||||
{
|
||||
input: "4m1.25d",
|
||||
expect: 4*time.Minute + time.Duration(1.25*float64(day)),
|
||||
},
|
||||
{
|
||||
input: "-1.25d12h",
|
||||
expect: time.Duration(-1.25*float64(day)) - 12*time.Hour,
|
||||
},
|
||||
} {
|
||||
actual, err := ParseDuration(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("Test %d ('%s'): Got error: %v", i, tc.input, err)
|
||||
continue
|
||||
}
|
||||
if actual != tc.expect {
|
||||
t.Errorf("Test %d ('%s'): Expected=%s Actual=%s", i, tc.input, tc.expect, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,11 @@ func (a Adapter) Adapt(body []byte, options map[string]interface{}) ([]byte, []c
|
||||
// into JSON. Caddyfile-unmarshaled values
|
||||
// will not be used directly; they will be
|
||||
// encoded as JSON and then used from that.
|
||||
// Implementations must be able to support
|
||||
// multiple segments (instances of their
|
||||
// directive or batch of tokens); typically
|
||||
// this means wrapping all token logic in
|
||||
// a loop: `for d.Next() { ... }`.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalCaddyfile(d *Dispenser) error
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ package caddyfile
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -37,6 +39,16 @@ func NewDispenser(tokens []Token) *Dispenser {
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestDispenser parses input into tokens and creates a new
|
||||
// Dispenser for test purposes only; any errors are fatal.
|
||||
func NewTestDispenser(input string) *Dispenser {
|
||||
tokens, err := allTokens("Testfile", []byte(input))
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatalf("getting all tokens from input: %v", err)
|
||||
}
|
||||
return NewDispenser(tokens)
|
||||
}
|
||||
|
||||
// Next loads the next token. Returns true if a token
|
||||
// was loaded; false otherwise. If false, all tokens
|
||||
// have been consumed.
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
package caddyfile
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -27,7 +25,7 @@ func TestDispenser_Val_Next(t *testing.T) {
|
||||
dir1 arg1
|
||||
dir2 arg2 arg3
|
||||
dir3`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
if val := d.Val(); val != "" {
|
||||
t.Fatalf("Val(): Should return empty string when no token loaded; got '%s'", val)
|
||||
@@ -65,7 +63,7 @@ func TestDispenser_NextArg(t *testing.T) {
|
||||
input := `dir1 arg1
|
||||
dir2 arg2 arg3
|
||||
dir3`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
assertNext := func(shouldLoad bool, expectedVal string, expectedCursor int) {
|
||||
if d.Next() != shouldLoad {
|
||||
@@ -112,7 +110,7 @@ func TestDispenser_NextLine(t *testing.T) {
|
||||
input := `host:port
|
||||
dir1 arg1
|
||||
dir2 arg2 arg3`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
assertNextLine := func(shouldLoad bool, expectedVal string, expectedCursor int) {
|
||||
if d.NextLine() != shouldLoad {
|
||||
@@ -145,7 +143,7 @@ func TestDispenser_NextBlock(t *testing.T) {
|
||||
}
|
||||
foobar2 {
|
||||
}`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
assertNextBlock := func(shouldLoad bool, expectedCursor, expectedNesting int) {
|
||||
if loaded := d.NextBlock(0); loaded != shouldLoad {
|
||||
@@ -175,7 +173,7 @@ func TestDispenser_Args(t *testing.T) {
|
||||
dir2 arg4 arg5
|
||||
dir3 arg6 arg7
|
||||
dir4`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
d.Next() // dir1
|
||||
|
||||
@@ -242,7 +240,7 @@ func TestDispenser_RemainingArgs(t *testing.T) {
|
||||
dir2 arg4 arg5
|
||||
dir3 arg6 { arg7
|
||||
dir4`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
d.Next() // dir1
|
||||
|
||||
@@ -279,7 +277,7 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
|
||||
input := `dir1 {
|
||||
}
|
||||
dir2 arg1 arg2`
|
||||
d := newTestDispenser(input)
|
||||
d := NewTestDispenser(input)
|
||||
|
||||
d.cursor = 1 // {
|
||||
|
||||
@@ -306,11 +304,3 @@ func TestDispenser_ArgErr_Err(t *testing.T) {
|
||||
t.Errorf("Expected error message with custom message in it ('foobar'); got '%v'", err)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestDispenser(input string) *Dispenser {
|
||||
tokens, err := allTokens("Testfile", []byte(input))
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatalf("getting all tokens from input: %v", err)
|
||||
}
|
||||
return NewDispenser(tokens)
|
||||
}
|
||||
|
||||
@@ -20,121 +20,194 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Format formats a Caddyfile to conventional standards.
|
||||
func Format(body []byte) []byte {
|
||||
reader := bytes.NewReader(body)
|
||||
result := new(bytes.Buffer)
|
||||
// Format formats the input Caddyfile to a standard, nice-looking
|
||||
// appearance. It works by reading each rune of the input and taking
|
||||
// control over all the bracing and whitespace that is written; otherwise,
|
||||
// words, comments, placeholders, and escaped characters are all treated
|
||||
// literally and written as they appear in the input.
|
||||
func Format(input []byte) []byte {
|
||||
input = bytes.TrimSpace(input)
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
rdr := bytes.NewReader(input)
|
||||
|
||||
var (
|
||||
commented,
|
||||
quoted,
|
||||
escaped,
|
||||
environ,
|
||||
lineBegin bool
|
||||
last rune // the last character that was written to the result
|
||||
|
||||
firstIteration = true
|
||||
space = true // whether current/previous character was whitespace (beginning of input counts as space)
|
||||
beginningOfLine = true // whether we are at beginning of line
|
||||
|
||||
indentation = 0
|
||||
openBrace bool // whether current word/token is or started with open curly brace
|
||||
openBraceWritten bool // if openBrace, whether that brace was written or not
|
||||
openBraceSpace bool // whether there was a non-newline space before open brace
|
||||
|
||||
prev,
|
||||
curr,
|
||||
next rune
|
||||
newLines int // count of newlines consumed
|
||||
|
||||
err error
|
||||
comment bool // whether we're in a comment
|
||||
quoted bool // whether we're in a quoted segment
|
||||
escaped bool // whether current char is escaped
|
||||
|
||||
nesting int // indentation level
|
||||
)
|
||||
|
||||
for {
|
||||
prev = curr
|
||||
curr = next
|
||||
write := func(ch rune) {
|
||||
out.WriteRune(ch)
|
||||
last = ch
|
||||
}
|
||||
|
||||
if curr < 0 {
|
||||
break
|
||||
indent := func() {
|
||||
for tabs := nesting; tabs > 0; tabs-- {
|
||||
write('\t')
|
||||
}
|
||||
}
|
||||
|
||||
next, _, err = reader.ReadRune()
|
||||
nextLine := func() {
|
||||
write('\n')
|
||||
beginningOfLine = true
|
||||
}
|
||||
|
||||
for {
|
||||
ch, _, err := rdr.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
next = -1
|
||||
break
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if comment {
|
||||
if ch == '\n' {
|
||||
comment = false
|
||||
} else {
|
||||
panic(err)
|
||||
write(ch)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if firstIteration {
|
||||
firstIteration = false
|
||||
lineBegin = true
|
||||
if !escaped && ch == '\\' {
|
||||
if space {
|
||||
write(' ')
|
||||
space = false
|
||||
}
|
||||
write(ch)
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
|
||||
if escaped {
|
||||
write(ch)
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
|
||||
if quoted {
|
||||
if escaped {
|
||||
escaped = false
|
||||
} else {
|
||||
if curr == '\\' {
|
||||
escaped = true
|
||||
}
|
||||
if curr == '"' {
|
||||
quoted = false
|
||||
}
|
||||
}
|
||||
if curr == '\n' {
|
||||
if ch == '"' {
|
||||
quoted = false
|
||||
}
|
||||
} else if commented {
|
||||
if curr == '\n' {
|
||||
commented = false
|
||||
}
|
||||
} else {
|
||||
if curr == '"' {
|
||||
quoted = true
|
||||
}
|
||||
if curr == '#' {
|
||||
commented = true
|
||||
}
|
||||
if curr == '}' {
|
||||
if environ {
|
||||
environ = false
|
||||
} else if indentation > 0 {
|
||||
indentation--
|
||||
}
|
||||
}
|
||||
if curr == '{' {
|
||||
if unicode.IsSpace(next) {
|
||||
indentation++
|
||||
|
||||
if !unicode.IsSpace(prev) {
|
||||
result.WriteRune(' ')
|
||||
}
|
||||
} else {
|
||||
environ = true
|
||||
}
|
||||
}
|
||||
if lineBegin {
|
||||
if curr == ' ' || curr == '\t' {
|
||||
continue
|
||||
} else {
|
||||
lineBegin = false
|
||||
if indentation > 0 {
|
||||
for tabs := indentation; tabs > 0; tabs-- {
|
||||
result.WriteRune('\t')
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if prev == '{' &&
|
||||
(curr == ' ' || curr == '\t') &&
|
||||
(next != '\n' && next != '\r') {
|
||||
curr = '\n'
|
||||
}
|
||||
}
|
||||
write(ch)
|
||||
continue
|
||||
}
|
||||
|
||||
if curr == '\n' {
|
||||
lineBegin = true
|
||||
if space && ch == '"' {
|
||||
quoted = true
|
||||
}
|
||||
|
||||
result.WriteRune(curr)
|
||||
if unicode.IsSpace(ch) {
|
||||
space = true
|
||||
if ch == '\n' {
|
||||
newLines++
|
||||
}
|
||||
continue
|
||||
}
|
||||
spacePrior := space
|
||||
space = false
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// I find it helpful to think of the formatting loop in two
|
||||
// main sections; by the time we reach this point, we
|
||||
// know we are in a "regular" part of the file: we know
|
||||
// the character is not a space, not in a literal segment
|
||||
// like a comment or quoted, it's not escaped, etc.
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
if ch == '#' {
|
||||
comment = true
|
||||
}
|
||||
|
||||
if openBrace && spacePrior && !openBraceWritten {
|
||||
if nesting == 0 && last == '}' {
|
||||
nextLine()
|
||||
nextLine()
|
||||
}
|
||||
|
||||
openBrace = false
|
||||
if beginningOfLine {
|
||||
indent()
|
||||
} else if !openBraceSpace {
|
||||
write(' ')
|
||||
}
|
||||
write('{')
|
||||
openBraceWritten = true
|
||||
nextLine()
|
||||
newLines = 0
|
||||
nesting++
|
||||
}
|
||||
|
||||
switch {
|
||||
case ch == '{':
|
||||
openBrace = true
|
||||
openBraceWritten = false
|
||||
openBraceSpace = spacePrior && !beginningOfLine
|
||||
if openBraceSpace {
|
||||
write(' ')
|
||||
}
|
||||
continue
|
||||
|
||||
case ch == '}' && (spacePrior || !openBrace):
|
||||
if last != '\n' {
|
||||
nextLine()
|
||||
}
|
||||
if nesting > 0 {
|
||||
nesting--
|
||||
}
|
||||
indent()
|
||||
write('}')
|
||||
newLines = 0
|
||||
continue
|
||||
}
|
||||
|
||||
if newLines > 2 {
|
||||
newLines = 2
|
||||
}
|
||||
for i := 0; i < newLines; i++ {
|
||||
nextLine()
|
||||
}
|
||||
newLines = 0
|
||||
if beginningOfLine {
|
||||
indent()
|
||||
}
|
||||
if nesting == 0 && last == '}' && beginningOfLine {
|
||||
nextLine()
|
||||
nextLine()
|
||||
}
|
||||
|
||||
if !beginningOfLine && spacePrior {
|
||||
write(' ')
|
||||
}
|
||||
|
||||
if openBrace && !openBraceWritten {
|
||||
write('{')
|
||||
openBraceWritten = true
|
||||
}
|
||||
write(ch)
|
||||
|
||||
beginningOfLine = false
|
||||
}
|
||||
|
||||
return result.Bytes()
|
||||
// the Caddyfile does not need any leading or trailing spaces, but...
|
||||
trimmedResult := bytes.TrimSpace(out.Bytes())
|
||||
|
||||
// ...Caddyfiles should, however, end with a newline because
|
||||
// newlines are significant to the syntax of the file
|
||||
return append(trimmedResult, '\n')
|
||||
}
|
||||
|
||||
@@ -15,12 +15,28 @@
|
||||
package caddyfile
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatBasicIndentation(t *testing.T) {
|
||||
input := []byte(`
|
||||
a
|
||||
func TestFormatter(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
description string
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
description: "very simple",
|
||||
input: `abc def
|
||||
g hi jkl
|
||||
mn`,
|
||||
expect: `abc def
|
||||
g hi jkl
|
||||
mn`,
|
||||
},
|
||||
{
|
||||
description: "basic indentation, line breaks, and nesting",
|
||||
input: ` a
|
||||
b
|
||||
|
||||
c {
|
||||
@@ -30,6 +46,8 @@ b
|
||||
e { f
|
||||
}
|
||||
|
||||
|
||||
|
||||
g {
|
||||
h {
|
||||
i
|
||||
@@ -44,10 +62,20 @@ l
|
||||
m {
|
||||
n { o
|
||||
}
|
||||
p { q r
|
||||
s }
|
||||
}
|
||||
`)
|
||||
expected := []byte(`
|
||||
a
|
||||
|
||||
{
|
||||
{ t
|
||||
u
|
||||
|
||||
v
|
||||
|
||||
w
|
||||
}
|
||||
}`,
|
||||
expect: `a
|
||||
b
|
||||
|
||||
c {
|
||||
@@ -74,35 +102,58 @@ m {
|
||||
n {
|
||||
o
|
||||
}
|
||||
}
|
||||
`)
|
||||
testFormat(t, input, expected)
|
||||
p {
|
||||
q r
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatBasicSpacing(t *testing.T) {
|
||||
input := []byte(`
|
||||
a{
|
||||
{
|
||||
{
|
||||
t
|
||||
u
|
||||
|
||||
v
|
||||
|
||||
w
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "block spacing",
|
||||
input: `a{
|
||||
b
|
||||
}
|
||||
|
||||
c{ d
|
||||
}
|
||||
`)
|
||||
expected := []byte(`
|
||||
a {
|
||||
}`,
|
||||
expect: `a {
|
||||
b
|
||||
}
|
||||
|
||||
c {
|
||||
d
|
||||
}
|
||||
`)
|
||||
testFormat(t, input, expected)
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "advanced spacing",
|
||||
input: `abc {
|
||||
def
|
||||
}ghi{
|
||||
jkl mno
|
||||
pqr}`,
|
||||
expect: `abc {
|
||||
def
|
||||
}
|
||||
|
||||
func TestFormatEnvironmentVariable(t *testing.T) {
|
||||
input := []byte(`
|
||||
{$A}
|
||||
ghi {
|
||||
jkl mno
|
||||
pqr
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "env var placeholders",
|
||||
input: `{$A}
|
||||
|
||||
b {
|
||||
{$C}
|
||||
@@ -110,9 +161,11 @@ b {
|
||||
|
||||
d { {$E}
|
||||
}
|
||||
`)
|
||||
expected := []byte(`
|
||||
{$A}
|
||||
|
||||
{ {$F}
|
||||
}
|
||||
`,
|
||||
expect: `{$A}
|
||||
|
||||
b {
|
||||
{$C}
|
||||
@@ -121,48 +174,44 @@ b {
|
||||
d {
|
||||
{$E}
|
||||
}
|
||||
`)
|
||||
testFormat(t, input, expected)
|
||||
}
|
||||
|
||||
func TestFormatComments(t *testing.T) {
|
||||
input := []byte(`
|
||||
# a "\n"
|
||||
{
|
||||
{$F}
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "comments",
|
||||
input: `#a "\n"
|
||||
|
||||
# b {
|
||||
#b {
|
||||
c
|
||||
}
|
||||
|
||||
d {
|
||||
e # f
|
||||
e#f
|
||||
# g
|
||||
}
|
||||
|
||||
h { # i
|
||||
}
|
||||
`)
|
||||
expected := []byte(`
|
||||
# a "\n"
|
||||
}`,
|
||||
expect: `#a "\n"
|
||||
|
||||
# b {
|
||||
#b {
|
||||
c
|
||||
}
|
||||
|
||||
d {
|
||||
e # f
|
||||
e#f
|
||||
# g
|
||||
}
|
||||
|
||||
h {
|
||||
# i
|
||||
}
|
||||
`)
|
||||
testFormat(t, input, expected)
|
||||
}
|
||||
|
||||
func TestFormatQuotesAndEscapes(t *testing.T) {
|
||||
input := []byte(`
|
||||
"a \"b\" #c
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "quotes and escaping",
|
||||
input: `"a \"b\" "#c
|
||||
d
|
||||
|
||||
e {
|
||||
@@ -171,9 +220,16 @@ e {
|
||||
|
||||
g { "h"
|
||||
}
|
||||
`)
|
||||
expected := []byte(`
|
||||
"a \"b\" #c
|
||||
|
||||
i {
|
||||
"foo
|
||||
bar"
|
||||
}
|
||||
|
||||
j {
|
||||
"\"k\" l m"
|
||||
}`,
|
||||
expect: `"a \"b\" "#c
|
||||
d
|
||||
|
||||
e {
|
||||
@@ -183,13 +239,89 @@ e {
|
||||
g {
|
||||
"h"
|
||||
}
|
||||
`)
|
||||
testFormat(t, input, expected)
|
||||
|
||||
i {
|
||||
"foo
|
||||
bar"
|
||||
}
|
||||
|
||||
func testFormat(t *testing.T, input, expected []byte) {
|
||||
output := Format(input)
|
||||
if string(output) != string(expected) {
|
||||
t.Errorf("Expected:\n%s\ngot:\n%s", string(output), string(expected))
|
||||
j {
|
||||
"\"k\" l m"
|
||||
}`,
|
||||
},
|
||||
{
|
||||
description: "bad nesting (too many open)",
|
||||
input: `a
|
||||
{
|
||||
{
|
||||
}`,
|
||||
expect: `a {
|
||||
{
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
description: "bad nesting (too many close)",
|
||||
input: `a
|
||||
{
|
||||
{
|
||||
}}}`,
|
||||
expect: `a {
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
description: "json",
|
||||
input: `foo
|
||||
bar "{\"key\":34}"
|
||||
`,
|
||||
expect: `foo
|
||||
bar "{\"key\":34}"`,
|
||||
},
|
||||
{
|
||||
description: "escaping after spaces",
|
||||
input: `foo \"literal\"`,
|
||||
expect: `foo \"literal\"`,
|
||||
},
|
||||
{
|
||||
description: "simple placeholders as standalone tokens",
|
||||
input: `foo {bar}`,
|
||||
expect: `foo {bar}`,
|
||||
},
|
||||
{
|
||||
description: "simple placeholders within tokens",
|
||||
input: `foo{bar} foo{bar}baz`,
|
||||
expect: `foo{bar} foo{bar}baz`,
|
||||
},
|
||||
{
|
||||
description: "placeholders and malformed braces",
|
||||
input: `foo{bar} foo{ bar}baz`,
|
||||
expect: `foo{bar} foo {
|
||||
bar
|
||||
}
|
||||
|
||||
baz`,
|
||||
},
|
||||
{
|
||||
description: "hash within string is not a comment",
|
||||
input: `redir / /some/#/path`,
|
||||
expect: `redir / /some/#/path`,
|
||||
},
|
||||
} {
|
||||
// the formatter should output a trailing newline,
|
||||
// even if the tests aren't written to expect that
|
||||
if !strings.HasSuffix(tc.expect, "\n") {
|
||||
tc.expect += "\n"
|
||||
}
|
||||
|
||||
actual := Format([]byte(tc.input))
|
||||
|
||||
if string(actual) != tc.expect {
|
||||
t.Errorf("\n[TEST %d: %s]\n====== EXPECTED ======\n%s\n====== ACTUAL ======\n%s^^^^^^^^^^^^^^^^^^^^^",
|
||||
i, tc.description, string(tc.expect), string(actual))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package caddyfile
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode"
|
||||
)
|
||||
@@ -73,7 +74,7 @@ func (l *lexer) load(input io.Reader) error {
|
||||
// a token was loaded; false otherwise.
|
||||
func (l *lexer) next() bool {
|
||||
var val []rune
|
||||
var comment, quoted, escaped bool
|
||||
var comment, quoted, btQuoted, escaped bool
|
||||
|
||||
makeToken := func() bool {
|
||||
l.token.Text = string(val)
|
||||
@@ -92,13 +93,13 @@ func (l *lexer) next() bool {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if !escaped && ch == '\\' {
|
||||
if !escaped && !btQuoted && ch == '\\' {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
|
||||
if quoted {
|
||||
if escaped {
|
||||
if quoted || btQuoted {
|
||||
if quoted && escaped {
|
||||
// all is literal in quoted area,
|
||||
// so only escape quotes
|
||||
if ch != '"' {
|
||||
@@ -106,7 +107,10 @@ func (l *lexer) next() bool {
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
if ch == '"' {
|
||||
if quoted && ch == '"' {
|
||||
return makeToken()
|
||||
}
|
||||
if btQuoted && ch == '`' {
|
||||
return makeToken()
|
||||
}
|
||||
}
|
||||
@@ -138,7 +142,7 @@ func (l *lexer) next() bool {
|
||||
continue
|
||||
}
|
||||
|
||||
if ch == '#' {
|
||||
if ch == '#' && len(val) == 0 {
|
||||
comment = true
|
||||
}
|
||||
if comment {
|
||||
@@ -151,6 +155,10 @@ func (l *lexer) next() bool {
|
||||
quoted = true
|
||||
continue
|
||||
}
|
||||
if ch == '`' {
|
||||
btQuoted = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if escaped {
|
||||
@@ -161,3 +169,21 @@ func (l *lexer) next() bool {
|
||||
val = append(val, ch)
|
||||
}
|
||||
}
|
||||
|
||||
// Tokenize takes bytes as input and lexes it into
|
||||
// a list of tokens that can be parsed as a Caddyfile.
|
||||
// Also takes a filename to fill the token's File as
|
||||
// the source of the tokens, which is important to
|
||||
// determine relative paths for `import` directives.
|
||||
func Tokenize(input []byte, filename string) ([]Token, error) {
|
||||
l := lexer{}
|
||||
if err := l.load(bytes.NewReader(input)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
@@ -15,37 +15,35 @@
|
||||
package caddyfile
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type lexerTestCase struct {
|
||||
input string
|
||||
input []byte
|
||||
expected []Token
|
||||
}
|
||||
|
||||
func TestLexer(t *testing.T) {
|
||||
testCases := []lexerTestCase{
|
||||
{
|
||||
input: `host:123`,
|
||||
input: []byte(`host:123`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123
|
||||
input: []byte(`host:123
|
||||
|
||||
directive`,
|
||||
directive`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 3, Text: "directive"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
input: []byte(`host:123 {
|
||||
directive
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -54,7 +52,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 { directive }`,
|
||||
input: []byte(`host:123 { directive }`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -63,12 +61,12 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `host:123 {
|
||||
input: []byte(`host:123 {
|
||||
#comment
|
||||
directive
|
||||
# comment
|
||||
foobar # another comment
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
@@ -78,8 +76,28 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `a "quoted value" b
|
||||
foobar`,
|
||||
input: []byte(`host:123 {
|
||||
# hash inside string is not a comment
|
||||
redir / /some/#/path
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "host:123"},
|
||||
{Line: 1, Text: "{"},
|
||||
{Line: 3, Text: "redir"},
|
||||
{Line: 3, Text: "/"},
|
||||
{Line: 3, Text: "/some/#/path"},
|
||||
{Line: 4, Text: "}"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("# comment at beginning of file\n# comment at beginning of line\nhost:123"),
|
||||
expected: []Token{
|
||||
{Line: 3, Text: "host:123"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte(`a "quoted value" b
|
||||
foobar`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "a"},
|
||||
{Line: 1, Text: "quoted value"},
|
||||
@@ -88,7 +106,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted \"value\" inside" B`,
|
||||
input: []byte(`A "quoted \"value\" inside" B`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "A"},
|
||||
{Line: 1, Text: `quoted "value" inside`},
|
||||
@@ -96,7 +114,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "An escaped \"newline\\\ninside\" quotes",
|
||||
input: []byte("An escaped \"newline\\\ninside\" quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "An"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -105,7 +123,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "An escaped newline\\\noutside quotes",
|
||||
input: []byte("An escaped newline\\\noutside quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "An"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -115,7 +133,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "line1\\\nescaped\nline2\nline3",
|
||||
input: []byte("line1\\\nescaped\nline2\nline3"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "line1"},
|
||||
{Line: 1, Text: "escaped"},
|
||||
@@ -124,7 +142,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "line1\\\nescaped1\\\nescaped2\nline4\nline5",
|
||||
input: []byte("line1\\\nescaped1\\\nescaped2\nline4\nline5"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "line1"},
|
||||
{Line: 1, Text: "escaped1"},
|
||||
@@ -134,34 +152,34 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"unescapable\ in quotes"`,
|
||||
input: []byte(`"unescapable\ in quotes"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `unescapable\ in quotes`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"don't\escape"`,
|
||||
input: []byte(`"don't\escape"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `don't\escape`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"don't\\escape"`,
|
||||
input: []byte(`"don't\\escape"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `don't\\escape`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `un\escapable`,
|
||||
input: []byte(`un\escapable`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `un\escapable`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `A "quoted value with line
|
||||
input: []byte(`A "quoted value with line
|
||||
break inside" {
|
||||
foobar
|
||||
}`,
|
||||
}`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "A"},
|
||||
{Line: 1, Text: "quoted value with line\n\t\t\t\t\tbreak inside"},
|
||||
@@ -171,13 +189,13 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `"C:\php\php-cgi.exe"`,
|
||||
input: []byte(`"C:\php\php-cgi.exe"`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `C:\php\php-cgi.exe`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: `empty "" string`,
|
||||
input: []byte(`empty "" string`),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `empty`},
|
||||
{Line: 1, Text: ``},
|
||||
@@ -185,7 +203,7 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "skip those\r\nCR characters",
|
||||
input: []byte("skip those\r\nCR characters"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: "skip"},
|
||||
{Line: 1, Text: "those"},
|
||||
@@ -194,30 +212,54 @@ func TestLexer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "\xEF\xBB\xBF:8080", // test with leading byte order mark
|
||||
input: []byte("\xEF\xBB\xBF:8080"), // test with leading byte order mark
|
||||
expected: []Token{
|
||||
{Line: 1, Text: ":8080"},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("simple `backtick quoted` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `simple`},
|
||||
{Line: 1, Text: `backtick quoted`},
|
||||
{Line: 1, Text: `string`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("multiline `backtick\nquoted\n` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `multiline`},
|
||||
{Line: 1, Text: "backtick\nquoted\n"},
|
||||
{Line: 3, Text: `string`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("nested `\"quotes inside\" backticks` string"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `nested`},
|
||||
{Line: 1, Text: `"quotes inside" backticks`},
|
||||
{Line: 1, Text: `string`},
|
||||
},
|
||||
},
|
||||
{
|
||||
input: []byte("reverse-nested \"`backticks` inside\" quotes"),
|
||||
expected: []Token{
|
||||
{Line: 1, Text: `reverse-nested`},
|
||||
{Line: 1, Text: "`backticks` inside"},
|
||||
{Line: 1, Text: `quotes`},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
actual := tokenize(testCase.input)
|
||||
actual, err := Tokenize(testCase.input, "")
|
||||
if err != nil {
|
||||
t.Errorf("%v", err)
|
||||
}
|
||||
lexerCompare(t, i, testCase.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func tokenize(input string) (tokens []Token) {
|
||||
l := lexer{}
|
||||
if err := l.load(strings.NewReader(input)); err != nil {
|
||||
log.Printf("[ERROR] load failed: %v", err)
|
||||
}
|
||||
for l.next() {
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lexerCompare(t *testing.T, n int, expected, actual []Token) {
|
||||
if len(expected) != len(actual) {
|
||||
t.Errorf("Test case %d: expected %d token(s) but got %d", n, len(expected), len(actual))
|
||||
|
||||
@@ -20,7 +20,10 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
// Parse parses the input just enough to group tokens, in
|
||||
@@ -84,16 +87,10 @@ func allTokens(filename string, input []byte) ([]Token, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := new(lexer)
|
||||
err = l.load(bytes.NewReader(input))
|
||||
tokens, err := Tokenize(input, filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var tokens []Token
|
||||
for l.next() {
|
||||
l.token.File = filename
|
||||
tokens = append(tokens, l.token)
|
||||
}
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
@@ -292,11 +289,19 @@ func (p *parser) doImport() error {
|
||||
if importPattern == "" {
|
||||
return p.Err("Import requires a non-empty filepath")
|
||||
}
|
||||
if p.NextArg() {
|
||||
return p.Err("Import takes only one argument (glob pattern or file)")
|
||||
|
||||
// grab remaining args as placeholder replacements
|
||||
args := p.RemainingArgs()
|
||||
|
||||
// add args to the replacer
|
||||
repl := caddy.NewReplacer()
|
||||
for index, arg := range args {
|
||||
repl.Set("args."+strconv.Itoa(index), arg)
|
||||
}
|
||||
// splice out the import directive and its argument (2 tokens total)
|
||||
tokensBefore := p.tokens[:p.cursor-1]
|
||||
|
||||
// splice out the import directive and its arguments
|
||||
// (2 tokens, plus the length of args)
|
||||
tokensBefore := p.tokens[:p.cursor-1-len(args)]
|
||||
tokensAfter := p.tokens[p.cursor+1:]
|
||||
var importedTokens []Token
|
||||
|
||||
@@ -348,10 +353,20 @@ func (p *parser) doImport() error {
|
||||
}
|
||||
}
|
||||
|
||||
// copy the tokens so we don't overwrite p.definedSnippets
|
||||
tokensCopy := make([]Token, len(importedTokens))
|
||||
copy(tokensCopy, importedTokens)
|
||||
|
||||
// run the argument replacer on the tokens
|
||||
for index, token := range tokensCopy {
|
||||
token.Text = repl.ReplaceKnown(token.Text, "")
|
||||
tokensCopy[index] = token
|
||||
}
|
||||
|
||||
// splice the imported tokens in the place of the import statement
|
||||
// and rewind cursor so Next() will land on first imported token
|
||||
p.tokens = append(tokensBefore, append(importedTokens, tokensAfter...)...)
|
||||
p.cursor--
|
||||
p.tokens = append(tokensBefore, append(tokensCopy, tokensAfter...)...)
|
||||
p.cursor -= len(args) + 1
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -182,14 +182,17 @@ func TestParseOneAndImport(t *testing.T) {
|
||||
"host1",
|
||||
}, []int{1, 2}},
|
||||
|
||||
{`import testdata/import_test1.txt testdata/import_test2.txt`, true, []string{}, []int{}},
|
||||
|
||||
{`import testdata/not_found.txt`, true, []string{}, []int{}},
|
||||
|
||||
{`""`, false, []string{}, []int{}},
|
||||
|
||||
{``, false, []string{}, []int{}},
|
||||
|
||||
// import with args
|
||||
{`import testdata/import_args0.txt a`, false, []string{"a"}, []int{}},
|
||||
{`import testdata/import_args1.txt a b`, false, []string{"a", "b"}, []int{}},
|
||||
{`import testdata/import_args*.txt a b`, false, []string{"a"}, []int{2}},
|
||||
|
||||
// test cases found by fuzzing!
|
||||
{`import }{$"`, true, []string{}, []int{}},
|
||||
{`import /*/*.txt`, true, []string{}, []int{}},
|
||||
@@ -210,6 +213,7 @@ func TestParseOneAndImport(t *testing.T) {
|
||||
t.Errorf("Test %d: Expected no error, but got: %v", i, err)
|
||||
}
|
||||
|
||||
// t.Logf("%+v\n", result)
|
||||
if len(result.Keys) != len(test.keys) {
|
||||
t.Errorf("Test %d: Expected %d keys, got %d",
|
||||
i, len(test.keys), len(result.Keys))
|
||||
@@ -568,10 +572,6 @@ func TestSnippets(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, b := range blocks {
|
||||
t.Log(b.Keys)
|
||||
t.Log(b.Segments)
|
||||
}
|
||||
if len(blocks) != 1 {
|
||||
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
||||
}
|
||||
@@ -616,10 +616,6 @@ func TestImportedFilesIgnoreNonDirectiveImportTokens(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, b := range blocks {
|
||||
t.Log(b.Keys)
|
||||
t.Log(b.Segments)
|
||||
}
|
||||
auth := blocks[0].Segments[0]
|
||||
line := auth[0].Text + " " + auth[1].Text + " " + auth[2].Text + " " + auth[3].Text
|
||||
if line != "basicauth / import password" {
|
||||
@@ -651,10 +647,6 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, b := range blocks {
|
||||
t.Log(b.Keys)
|
||||
t.Log(b.Segments)
|
||||
}
|
||||
if len(blocks) != 1 {
|
||||
t.Fatalf("Expect exactly one server block. Got %d.", len(blocks))
|
||||
}
|
||||
@@ -670,5 +662,5 @@ func TestSnippetAcrossMultipleFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func testParser(input string) parser {
|
||||
return parser{Dispenser: newTestDispenser(input)}
|
||||
return parser{Dispenser: NewTestDispenser(input)}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{args.0}
|
||||
@@ -0,0 +1 @@
|
||||
{args.0} {args.1}
|
||||
@@ -17,6 +17,8 @@ package caddyconfig
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
// Adapter is a type which can adapt a configuration to Caddy JSON.
|
||||
@@ -49,14 +51,13 @@ func JSON(val interface{}, warnings *[]Warning) json.RawMessage {
|
||||
return b
|
||||
}
|
||||
|
||||
// JSONModuleObject is like JSON, except it marshals val into a JSON object
|
||||
// and then adds a key to that object named fieldName with the value fieldVal.
|
||||
// This is useful for JSON-encoding module values where the module name has to
|
||||
// be described within the object by a certain key; for example,
|
||||
// "responder": "file_server" for a file server HTTP responder. The val must
|
||||
// encode into a map[string]interface{} (i.e. it must be a struct or map),
|
||||
// and any errors are converted into warnings, so this can be conveniently
|
||||
// used when filling a struct. For correct code, there should be no errors.
|
||||
// JSONModuleObject is like JSON(), except it marshals val into a JSON object
|
||||
// with an added key named fieldName with the value fieldVal. This is useful
|
||||
// for encoding module values where the module name has to be described within
|
||||
// the object by a certain key; for example, `"handler": "file_server"` for a
|
||||
// file server HTTP handler (fieldName="handler" and fieldVal="file_server").
|
||||
// The val parameter must encode into a map[string]interface{} (i.e. it must be
|
||||
// a struct or map). Any errors are converted into warnings.
|
||||
func JSONModuleObject(val interface{}, fieldName, fieldVal string, warnings *[]Warning) json.RawMessage {
|
||||
// encode to a JSON object first
|
||||
enc, err := json.Marshal(val)
|
||||
@@ -99,13 +100,14 @@ func JSONIndent(val interface{}) ([]byte, error) {
|
||||
}
|
||||
|
||||
// RegisterAdapter registers a config adapter with the given name.
|
||||
// This should usually be done at init-time.
|
||||
func RegisterAdapter(name string, adapter Adapter) error {
|
||||
// This should usually be done at init-time. It panics if the
|
||||
// adapter cannot be registered successfully.
|
||||
func RegisterAdapter(name string, adapter Adapter) {
|
||||
if _, ok := configAdapters[name]; ok {
|
||||
return fmt.Errorf("%s: already registered", name)
|
||||
panic(fmt.Errorf("%s: already registered", name))
|
||||
}
|
||||
configAdapters[name] = adapter
|
||||
return nil
|
||||
caddy.RegisterModule(adapterModule{name, adapter})
|
||||
}
|
||||
|
||||
// GetAdapter returns the adapter with the given name,
|
||||
@@ -114,4 +116,21 @@ func GetAdapter(name string) Adapter {
|
||||
return configAdapters[name]
|
||||
}
|
||||
|
||||
// adapterModule is a wrapper type that can turn any config
|
||||
// adapter into a Caddy module, which has the benefit of being
|
||||
// counted with other modules, even though they do not
|
||||
// technically extend the Caddy configuration structure.
|
||||
// See caddyserver/caddy#3132.
|
||||
type adapterModule struct {
|
||||
name string
|
||||
Adapter
|
||||
}
|
||||
|
||||
func (am adapterModule) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: caddy.ModuleID("caddy.adapters." + am.name),
|
||||
New: func() caddy.Module { return am },
|
||||
}
|
||||
}
|
||||
|
||||
var configAdapters = make(map[string]Adapter)
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/certmagic"
|
||||
@@ -105,12 +107,22 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
||||
// server block are only the ones which use the address; but
|
||||
// the contents (tokens) are of course the same
|
||||
for addr, keys := range addrToKeys {
|
||||
// parse keys so that we only have to do it once
|
||||
parsedKeys := make([]Address, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
addr, err := ParseAddress(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing key '%s': %v", key, err)
|
||||
}
|
||||
parsedKeys = append(parsedKeys, addr.Normalize())
|
||||
}
|
||||
sbmap[addr] = append(sbmap[addr], serverBlock{
|
||||
block: caddyfile.ServerBlock{
|
||||
Keys: keys,
|
||||
Segments: sblock.block.Segments,
|
||||
},
|
||||
pile: sblock.pile,
|
||||
keys: parsedKeys,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -127,7 +139,7 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
|
||||
// association from multiple addresses to multiple server blocks; i.e. each element of
|
||||
// the returned slice) becomes a server definition in the output JSON.
|
||||
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
|
||||
var sbaddrs []sbAddrAssociation
|
||||
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
|
||||
for addr, sblocks := range addrToServerBlocks {
|
||||
// we start with knowing that at least this address
|
||||
// maps to these server blocks
|
||||
@@ -164,7 +176,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
|
||||
// figure out the HTTP and HTTPS ports; either
|
||||
// use defaults, or override with user config
|
||||
httpPort, httpsPort := strconv.Itoa(certmagic.HTTPPort), strconv.Itoa(certmagic.HTTPSPort)
|
||||
httpPort, httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPPort), strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
if hport, ok := options["http_port"]; ok {
|
||||
httpPort = strconv.Itoa(hport.(int))
|
||||
}
|
||||
@@ -188,7 +200,7 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
}
|
||||
|
||||
// the bind directive specifies hosts, but is optional
|
||||
var lnHosts []string
|
||||
lnHosts := make([]string, 0, len(sblock.pile))
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
|
||||
}
|
||||
@@ -199,11 +211,16 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str
|
||||
// use a map to prevent duplication
|
||||
listeners := make(map[string]struct{})
|
||||
for _, host := range lnHosts {
|
||||
listeners[net.JoinHostPort(host, lnPort)] = struct{}{}
|
||||
addr, err := caddy.ParseNetworkAddress(host)
|
||||
if err == nil && addr.IsUnixNetwork() {
|
||||
listeners[host] = struct{}{}
|
||||
} else {
|
||||
listeners[net.JoinHostPort(host, lnPort)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// now turn map into list
|
||||
var listenersList []string
|
||||
listenersList := make([]string, 0, len(listeners))
|
||||
for lnStr := range listeners {
|
||||
listenersList = append(listenersList, lnStr)
|
||||
}
|
||||
@@ -274,8 +291,6 @@ func ParseAddress(str string) (Address, error) {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// TODO: which of the methods on Address are even used?
|
||||
|
||||
// String returns a human-readable form of a. It will
|
||||
// be a cleaned-up and filled-out URL string.
|
||||
func (a Address) String() string {
|
||||
@@ -312,15 +327,15 @@ func (a Address) Normalize() Address {
|
||||
path := a.Path
|
||||
|
||||
// ensure host is normalized if it's an IP address
|
||||
host := a.Host
|
||||
host := strings.TrimSpace(a.Host)
|
||||
if ip := net.ParseIP(host); ip != nil {
|
||||
host = ip.String()
|
||||
}
|
||||
|
||||
return Address{
|
||||
Original: a.Original,
|
||||
Scheme: strings.ToLower(a.Scheme),
|
||||
Host: strings.ToLower(host),
|
||||
Scheme: lowerExceptPlaceholders(a.Scheme),
|
||||
Host: lowerExceptPlaceholders(host),
|
||||
Port: a.Port,
|
||||
Path: path,
|
||||
}
|
||||
@@ -347,3 +362,31 @@ func (a Address) Key() string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// lowerExceptPlaceholders lowercases s except within
|
||||
// placeholders (substrings in non-escaped '{ }' spans).
|
||||
// See https://github.com/caddyserver/caddy/issues/3264
|
||||
func lowerExceptPlaceholders(s string) string {
|
||||
var sb strings.Builder
|
||||
var escaped, inPlaceholder bool
|
||||
for _, ch := range s {
|
||||
if ch == '\\' && !escaped {
|
||||
escaped = true
|
||||
sb.WriteRune(ch)
|
||||
continue
|
||||
}
|
||||
if ch == '{' && !escaped {
|
||||
inPlaceholder = true
|
||||
}
|
||||
if ch == '}' && inPlaceholder && !escaped {
|
||||
inPlaceholder = false
|
||||
}
|
||||
if inPlaceholder {
|
||||
sb.WriteRune(ch)
|
||||
} else {
|
||||
sb.WriteRune(unicode.ToLower(ch))
|
||||
}
|
||||
escaped = false
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
// +build gofuzz
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
package httpcaddyfile
|
||||
|
||||
|
||||
@@ -108,6 +108,10 @@ func TestKeyNormalization(t *testing.T) {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
input: "example.com",
|
||||
expect: "example.com",
|
||||
},
|
||||
{
|
||||
input: "http://host:1234/path",
|
||||
expect: "http://host:1234/path",
|
||||
@@ -124,6 +128,22 @@ func TestKeyNormalization(t *testing.T) {
|
||||
input: "A:2015/Path",
|
||||
expect: "a:2015/Path",
|
||||
},
|
||||
{
|
||||
input: "sub.{env.MY_DOMAIN}",
|
||||
expect: "sub.{env.MY_DOMAIN}",
|
||||
},
|
||||
{
|
||||
input: "sub.ExAmPle",
|
||||
expect: "sub.example",
|
||||
},
|
||||
{
|
||||
input: "sub.\\{env.MY_DOMAIN\\}",
|
||||
expect: "sub.\\{env.my_domain\\}",
|
||||
},
|
||||
{
|
||||
input: "sub.{env.MY_DOMAIN}.com",
|
||||
expect: "sub.{env.MY_DOMAIN}.com",
|
||||
},
|
||||
{
|
||||
input: ":80",
|
||||
expect: ":80",
|
||||
@@ -156,7 +176,7 @@ func TestKeyNormalization(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
if actual := addr.Normalize().Key(); actual != tc.expect {
|
||||
t.Errorf("Test %d: Normalized key for address '%s' was '%s' but expected '%s'", i, tc.input, actual, tc.expect)
|
||||
t.Errorf("Test %d: Input '%s': Expected '%s' but got '%s'", i, tc.input, tc.expect, actual)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"html"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -27,13 +29,15 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDirective("bind", parseBind)
|
||||
RegisterDirective("root", parseRoot) // TODO: isn't this a handler directive?
|
||||
RegisterDirective("tls", parseTLS)
|
||||
RegisterHandlerDirective("root", parseRoot)
|
||||
RegisterHandlerDirective("redir", parseRedir)
|
||||
RegisterHandlerDirective("respond", parseRespond)
|
||||
RegisterHandlerDirective("route", parseRoute)
|
||||
@@ -54,63 +58,37 @@ func parseBind(h Helper) ([]ConfigValue, error) {
|
||||
return h.NewBindAddresses(lnHosts), nil
|
||||
}
|
||||
|
||||
// parseRoot parses the root directive. Syntax:
|
||||
//
|
||||
// root [<matcher>] <path>
|
||||
//
|
||||
func parseRoot(h Helper) ([]ConfigValue, error) {
|
||||
if !h.Next() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
matcherSet, ok, err := h.MatcherToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !ok {
|
||||
// no matcher token; oops
|
||||
h.Dispenser.Prev()
|
||||
}
|
||||
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
root := h.Val()
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
varsHandler := caddyhttp.VarsMiddleware{"root": root}
|
||||
route := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{
|
||||
caddyconfig.JSONModuleObject(varsHandler, "handler", "vars", nil),
|
||||
},
|
||||
}
|
||||
if matcherSet != nil {
|
||||
route.MatcherSetsRaw = []caddy.ModuleMap{matcherSet}
|
||||
}
|
||||
|
||||
return []ConfigValue{{Class: "route", Value: route}}, nil
|
||||
}
|
||||
|
||||
// parseTLS parses the tls directive. Syntax:
|
||||
//
|
||||
// tls [<email>|internal]|[<cert_file> <key_file>] {
|
||||
// protocols <min> [<max>]
|
||||
// ciphers <cipher_suites...>
|
||||
// curves <curves...>
|
||||
// client_auth {
|
||||
// mode [request|require|verify_if_given|require_and_verify]
|
||||
// trusted_ca_cert <base64_der>
|
||||
// trusted_ca_cert_file <filename>
|
||||
// trusted_leaf_cert <base64_der>
|
||||
// trusted_leaf_cert_file <filename>
|
||||
// }
|
||||
// alpn <values...>
|
||||
// load <paths...>
|
||||
// ca <acme_ca_endpoint>
|
||||
// ca_root <pem_file>
|
||||
// dns <provider_name>
|
||||
// on_demand
|
||||
// issuer <module_name> ...
|
||||
// }
|
||||
//
|
||||
func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
var cp *caddytls.ConnectionPolicy
|
||||
cp := new(caddytls.ConnectionPolicy)
|
||||
var fileLoader caddytls.FileLoader
|
||||
var folderLoader caddytls.FolderLoader
|
||||
var certSelector caddytls.CustomCertSelectionPolicy
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
var internalIssuer *caddytls.InternalIssuer
|
||||
var issuer certmagic.Issuer
|
||||
var onDemand bool
|
||||
|
||||
for h.Next() {
|
||||
// file certificate loader
|
||||
@@ -172,22 +150,17 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
// remember this for next time we see this cert file
|
||||
tlsCertTags[certFilename] = tag
|
||||
}
|
||||
certSelector := caddytls.CustomCertSelectionPolicy{Tag: tag}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
certSelector.AnyTag = append(certSelector.AnyTag, tag)
|
||||
|
||||
cp.CertSelection = caddyconfig.JSONModuleObject(certSelector, "policy", "custom", h.warnings)
|
||||
default:
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
var hasBlock bool
|
||||
for h.NextBlock(0) {
|
||||
for nesting := h.Nesting(); h.NextBlock(nesting); {
|
||||
hasBlock = true
|
||||
|
||||
switch h.Val() {
|
||||
// connection policy
|
||||
case "protocols":
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
@@ -197,55 +170,92 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
if _, ok := caddytls.SupportedProtocols[args[0]]; !ok {
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[0])
|
||||
}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
cp.ProtocolMin = args[0]
|
||||
}
|
||||
if len(args) > 1 {
|
||||
if _, ok := caddytls.SupportedProtocols[args[1]]; !ok {
|
||||
return nil, h.Errf("Wrong protocol name or protocol not supported: '%s'", args[1])
|
||||
}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
cp.ProtocolMax = args[1]
|
||||
}
|
||||
|
||||
case "ciphers":
|
||||
for h.NextArg() {
|
||||
if _, ok := caddytls.SupportedCipherSuites[h.Val()]; !ok {
|
||||
if !caddytls.CipherSuiteNameSupported(h.Val()) {
|
||||
return nil, h.Errf("Wrong cipher suite name or cipher suite not supported: '%s'", h.Val())
|
||||
}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
cp.CipherSuites = append(cp.CipherSuites, h.Val())
|
||||
}
|
||||
|
||||
case "curves":
|
||||
for h.NextArg() {
|
||||
if _, ok := caddytls.SupportedCurves[h.Val()]; !ok {
|
||||
return nil, h.Errf("Wrong curve name or curve not supported: '%s'", h.Val())
|
||||
}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
cp.Curves = append(cp.Curves, h.Val())
|
||||
}
|
||||
|
||||
case "client_auth":
|
||||
cp.ClientAuthentication = &caddytls.ClientAuthentication{}
|
||||
for nesting := h.Nesting(); h.NextBlock(nesting); {
|
||||
subdir := h.Val()
|
||||
switch subdir {
|
||||
case "mode":
|
||||
if !h.Args(&cp.ClientAuthentication.Mode) {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
case "trusted_ca_cert",
|
||||
"trusted_leaf_cert":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
if subdir == "trusted_ca_cert" {
|
||||
cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts, h.Val())
|
||||
} else {
|
||||
cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts, h.Val())
|
||||
}
|
||||
|
||||
case "trusted_ca_cert_file",
|
||||
"trusted_leaf_cert_file":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
filename := h.Val()
|
||||
certDataPEM, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block, _ := pem.Decode(certDataPEM)
|
||||
if block == nil || block.Type != "CERTIFICATE" {
|
||||
return nil, h.Errf("no CERTIFICATE pem block found in %s", h.Val())
|
||||
}
|
||||
if subdir == "trusted_ca_cert_file" {
|
||||
cp.ClientAuthentication.TrustedCACerts = append(cp.ClientAuthentication.TrustedCACerts,
|
||||
base64.StdEncoding.EncodeToString(block.Bytes))
|
||||
} else {
|
||||
cp.ClientAuthentication.TrustedLeafCerts = append(cp.ClientAuthentication.TrustedLeafCerts,
|
||||
base64.StdEncoding.EncodeToString(block.Bytes))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, h.Errf("unknown subdirective for client_auth: %s", subdir)
|
||||
}
|
||||
}
|
||||
|
||||
case "alpn":
|
||||
args := h.RemainingArgs()
|
||||
if len(args) == 0 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
cp.ALPN = args
|
||||
|
||||
// certificate folder loader
|
||||
case "load":
|
||||
folderLoader = append(folderLoader, h.RemainingArgs()...)
|
||||
|
||||
// automation policy
|
||||
case "ca":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 1 {
|
||||
@@ -256,23 +266,65 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
acmeIssuer.CA = arg[0]
|
||||
|
||||
// DNS provider for ACME DNS challenge
|
||||
case "dns":
|
||||
if !h.Next() {
|
||||
case "eab":
|
||||
arg := h.RemainingArgs()
|
||||
if len(arg) != 2 {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
acmeIssuer.ExternalAccount = &acme.EAB{
|
||||
KeyID: arg[0],
|
||||
MACKey: arg[1],
|
||||
}
|
||||
|
||||
case "issuer":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
modName := h.Val()
|
||||
mod, err := caddy.GetModule("tls.issuance." + modName)
|
||||
if err != nil {
|
||||
return nil, h.Errf("getting issuer module '%s': %v", modName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return nil, h.Errf("issuer module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
issuer, ok = unm.(certmagic.Issuer)
|
||||
if !ok {
|
||||
return nil, h.Errf("module %s is not a certmagic.Issuer", mod.ID)
|
||||
}
|
||||
|
||||
case "dns":
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
provName := h.Val()
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
acmeIssuer.Challenges.DNS = new(caddytls.DNSChallengeConfig)
|
||||
}
|
||||
dnsProvModule, err := caddy.GetModule("tls.dns." + provName)
|
||||
dnsProvModule, err := caddy.GetModule("dns.providers." + provName)
|
||||
if err != nil {
|
||||
return nil, h.Errf("getting DNS provider module named '%s': %v", provName, err)
|
||||
}
|
||||
acmeIssuer.Challenges.DNSRaw = caddyconfig.JSONModuleObject(dnsProvModule.New(), "provider", provName, h.warnings)
|
||||
dnsProvModuleInstance := dnsProvModule.New()
|
||||
if unm, ok := dnsProvModuleInstance.(caddyfile.Unmarshaler); ok {
|
||||
err = unm.UnmarshalCaddyfile(h.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
acmeIssuer.Challenges.DNS.ProviderRaw = caddyconfig.JSONModuleObject(dnsProvModuleInstance, "name", provName, h.warnings)
|
||||
|
||||
case "ca_root":
|
||||
arg := h.RemainingArgs()
|
||||
@@ -284,6 +336,12 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
}
|
||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, arg[0])
|
||||
|
||||
case "on_demand":
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
onDemand = true
|
||||
|
||||
default:
|
||||
return nil, h.Errf("unknown subdirective: %s", h.Val())
|
||||
}
|
||||
@@ -301,39 +359,40 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
// certificate loaders
|
||||
if len(fileLoader) > 0 {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.certificate_loader",
|
||||
Class: "tls.cert_loader",
|
||||
Value: fileLoader,
|
||||
})
|
||||
// ensure server uses HTTPS by setting non-nil conn policy
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
}
|
||||
if len(folderLoader) > 0 {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.certificate_loader",
|
||||
Class: "tls.cert_loader",
|
||||
Value: folderLoader,
|
||||
})
|
||||
// ensure server uses HTTPS by setting non-nil conn policy
|
||||
if cp == nil {
|
||||
cp = new(caddytls.ConnectionPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
// connection policy
|
||||
if cp != nil {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.connection_policy",
|
||||
Value: cp,
|
||||
})
|
||||
}
|
||||
|
||||
// automation policy
|
||||
// issuer
|
||||
if acmeIssuer != nil && internalIssuer != nil {
|
||||
// the logic to support this would be complex
|
||||
return nil, h.Err("cannot use both ACME and internal issuers in same server block")
|
||||
}
|
||||
if acmeIssuer != nil {
|
||||
if issuer != nil && (acmeIssuer != nil || internalIssuer != nil) {
|
||||
// similarly, the logic to support this would be complex
|
||||
return nil, h.Err("when defining an issuer, all its config must be in its block, rather than from separate tls subdirectives")
|
||||
}
|
||||
switch {
|
||||
case issuer != nil:
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: issuer,
|
||||
})
|
||||
|
||||
case internalIssuer != nil:
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: internalIssuer,
|
||||
})
|
||||
|
||||
case acmeIssuer != nil:
|
||||
// fill in global defaults, if configured
|
||||
if email := h.Option("email"); email != nil && acmeIssuer.Email == "" {
|
||||
acmeIssuer.Email = email.(string)
|
||||
@@ -344,21 +403,56 @@ func parseTLS(h Helper) ([]ConfigValue, error) {
|
||||
if caPemFile := h.Option("acme_ca_root"); caPemFile != nil {
|
||||
acmeIssuer.TrustedRootsPEMFiles = append(acmeIssuer.TrustedRootsPEMFiles, caPemFile.(string))
|
||||
}
|
||||
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: acmeIssuer,
|
||||
})
|
||||
} else if internalIssuer != nil {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.cert_issuer",
|
||||
Value: internalIssuer,
|
||||
Value: disambiguateACMEIssuer(acmeIssuer),
|
||||
})
|
||||
}
|
||||
|
||||
// on-demand TLS
|
||||
if onDemand {
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.on_demand",
|
||||
Value: true,
|
||||
})
|
||||
}
|
||||
|
||||
// custom certificate selection
|
||||
if len(certSelector.AnyTag) > 0 {
|
||||
cp.CertSelection = &certSelector
|
||||
}
|
||||
|
||||
// connection policy -- always add one, to ensure that TLS
|
||||
// is enabled, because this directive was used (this is
|
||||
// needed, for instance, when a site block has a key of
|
||||
// just ":5000" - i.e. no hostname, and only on-demand TLS
|
||||
// is enabled)
|
||||
configVals = append(configVals, ConfigValue{
|
||||
Class: "tls.connection_policy",
|
||||
Value: cp,
|
||||
})
|
||||
|
||||
return configVals, nil
|
||||
}
|
||||
|
||||
// parseRoot parses the root directive. Syntax:
|
||||
//
|
||||
// root [<matcher>] <path>
|
||||
//
|
||||
func parseRoot(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
var root string
|
||||
for h.Next() {
|
||||
if !h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
root = h.Val()
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
}
|
||||
return caddyhttp.VarsMiddleware{"root": root}, nil
|
||||
}
|
||||
|
||||
// parseRedir parses the redir directive. Syntax:
|
||||
//
|
||||
// redir [<matcher>] <to> [<code>]
|
||||
@@ -422,29 +516,23 @@ func parseRespond(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
sr := new(caddyhttp.Subroute)
|
||||
|
||||
for h.Next() {
|
||||
for nesting := h.Nesting(); h.NextBlock(nesting); {
|
||||
dir := h.Val()
|
||||
allResults, err := parseSegmentAsConfig(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dirFunc, ok := registeredDirectives[dir]
|
||||
if !ok {
|
||||
return nil, h.Errf("unrecognized directive: %s", dir)
|
||||
}
|
||||
|
||||
subHelper := h
|
||||
subHelper.Dispenser = h.NewFromNextSegment()
|
||||
|
||||
results, err := dirFunc(subHelper)
|
||||
if err != nil {
|
||||
return nil, h.Errf("parsing caddyfile tokens for '%s': %v", dir, err)
|
||||
}
|
||||
for _, result := range results {
|
||||
handler, ok := result.Value.(caddyhttp.Route)
|
||||
if !ok {
|
||||
return nil, h.Errf("%s directive returned something other than an HTTP route: %#v (only handler directives can be used in routes)", dir, result.Value)
|
||||
}
|
||||
sr.Routes = append(sr.Routes, handler)
|
||||
}
|
||||
for _, result := range allResults {
|
||||
switch handler := result.Value.(type) {
|
||||
case caddyhttp.Route:
|
||||
sr.Routes = append(sr.Routes, handler)
|
||||
case caddyhttp.Subroute:
|
||||
// directives which return a literal subroute instead of a route
|
||||
// means they intend to keep those handlers together without
|
||||
// them being reordered; we're doing that anyway since we're in
|
||||
// the route directive, so just append its handlers
|
||||
sr.Routes = append(sr.Routes, handler.Routes...)
|
||||
default:
|
||||
return nil, h.Errf("%s directive returned something other than an HTTP route or subroute: %#v (only handler directives can be used in routes)", result.directive, result.Value)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,11 +540,11 @@ func parseRoute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
}
|
||||
|
||||
func parseHandle(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
return parseSegmentAsSubroute(h)
|
||||
return ParseSegmentAsSubroute(h)
|
||||
}
|
||||
|
||||
func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
||||
subroute, err := parseSegmentAsSubroute(h)
|
||||
subroute, err := ParseSegmentAsSubroute(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -479,6 +567,11 @@ func parseHandleErrors(h Helper) ([]ConfigValue, error) {
|
||||
func parseLog(h Helper) ([]ConfigValue, error) {
|
||||
var configValues []ConfigValue
|
||||
for h.Next() {
|
||||
// log does not currently support any arguments
|
||||
if h.NextArg() {
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
cl := new(caddy.CustomLog)
|
||||
|
||||
for h.NextBlock(0) {
|
||||
@@ -564,8 +657,8 @@ func parseLog(h Helper) ([]ConfigValue, error) {
|
||||
if !ok {
|
||||
logCounter = 0
|
||||
}
|
||||
cl.Include = []string{"http.log.access"}
|
||||
val.name = fmt.Sprintf("log%d", logCounter)
|
||||
cl.Include = []string{"http.log.access." + val.name}
|
||||
val.log = cl
|
||||
logCounter++
|
||||
h.State["logCounter"] = logCounter
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/logging"
|
||||
)
|
||||
|
||||
func TestLogDirectiveSyntax(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expectWarn bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
input: `:8080 {
|
||||
log
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
log {
|
||||
output file foo.log
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `:8080 {
|
||||
log /foo {
|
||||
output file foo.log
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
ServerType: ServerType{},
|
||||
}
|
||||
|
||||
_, warnings, err := adapter.Adapt([]byte(tc.input), nil)
|
||||
|
||||
if len(warnings) > 0 != tc.expectWarn {
|
||||
t.Errorf("Test %d warning expectation failed Expected: %v, got %v", i, tc.expectWarn, warnings)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil != tc.expectError {
|
||||
t.Errorf("Test %d error expectation failed Expected: %v, got %s", i, tc.expectError, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ package httpcaddyfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
@@ -27,32 +29,44 @@ import (
|
||||
|
||||
// directiveOrder specifies the order
|
||||
// to apply directives in HTTP routes.
|
||||
//
|
||||
// The root directive goes first in case rewrites or
|
||||
// redirects depend on existence of files, i.e. the
|
||||
// file matcher, which must know the root first.
|
||||
//
|
||||
// The header directive goes second so that headers
|
||||
// can be manipulated before doing redirects.
|
||||
var directiveOrder = []string{
|
||||
"map",
|
||||
"root",
|
||||
|
||||
"header",
|
||||
|
||||
"redir",
|
||||
"rewrite",
|
||||
|
||||
"root",
|
||||
|
||||
"strip_prefix",
|
||||
"strip_suffix",
|
||||
"uri_replace",
|
||||
// URI manipulation
|
||||
"uri",
|
||||
"try_files",
|
||||
|
||||
// middleware handlers that typically wrap responses
|
||||
// middleware handlers; some wrap responses
|
||||
"basicauth",
|
||||
"header",
|
||||
"request_header",
|
||||
"encode",
|
||||
"templates",
|
||||
|
||||
// special routing & dispatching directives
|
||||
"handle",
|
||||
"handle_path",
|
||||
"route",
|
||||
"push",
|
||||
|
||||
// handlers that typically respond to requests
|
||||
"respond",
|
||||
"reverse_proxy",
|
||||
"php_fastcgi",
|
||||
"file_server",
|
||||
"acme_server",
|
||||
}
|
||||
|
||||
// directiveIsOrdered returns true if dir is
|
||||
@@ -110,6 +124,17 @@ func RegisterHandlerDirective(dir string, setupFunc UnmarshalHandlerFunc) {
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterGlobalOption registers a unique global option opt with
|
||||
// an associated unmarshaling (setup) function. When the global
|
||||
// option opt is encountered in a Caddyfile, setupFunc will be
|
||||
// called to unmarshal its tokens.
|
||||
func RegisterGlobalOption(opt string, setupFunc UnmarshalGlobalFunc) {
|
||||
if _, ok := registeredGlobalOptions[opt]; ok {
|
||||
panic("global option " + opt + " already registered")
|
||||
}
|
||||
registeredGlobalOptions[opt] = setupFunc
|
||||
}
|
||||
|
||||
// Helper is a type which helps setup a value from
|
||||
// Caddyfile tokens.
|
||||
type Helper struct {
|
||||
@@ -240,86 +265,22 @@ func (h Helper) NewBindAddresses(addrs []string) []ConfigValue {
|
||||
return []ConfigValue{{Class: "bind", Value: addrs}}
|
||||
}
|
||||
|
||||
// ConfigValue represents a value to be added to the final
|
||||
// configuration, or a value to be consulted when building
|
||||
// the final configuration.
|
||||
type ConfigValue struct {
|
||||
// The kind of value this is. As the config is
|
||||
// being built, the adapter will look in the
|
||||
// "pile" for values belonging to a certain
|
||||
// class when it is setting up a certain part
|
||||
// of the config. The associated value will be
|
||||
// type-asserted and placed accordingly.
|
||||
Class string
|
||||
|
||||
// The value to be used when building the config.
|
||||
// Generally its type is associated with the
|
||||
// name of the Class.
|
||||
Value interface{}
|
||||
|
||||
directive string
|
||||
}
|
||||
|
||||
func sortRoutes(routes []ConfigValue) {
|
||||
dirPositions := make(map[string]int)
|
||||
for i, dir := range directiveOrder {
|
||||
dirPositions[dir] = i
|
||||
}
|
||||
|
||||
// while we are sorting, we will need to decode a route's path matcher
|
||||
// in order to sub-sort by path length; we can amortize this operation
|
||||
// for efficiency by storing the decoded matchers in a slice
|
||||
decodedMatchers := make([]caddyhttp.MatchPath, len(routes))
|
||||
|
||||
sort.SliceStable(routes, func(i, j int) bool {
|
||||
iDir, jDir := routes[i].directive, routes[j].directive
|
||||
if iDir == jDir {
|
||||
// directives are the same; sub-sort by path matcher length
|
||||
// if there's only one matcher set and one path (common case)
|
||||
iRoute, ok := routes[i].Value.(caddyhttp.Route)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
jRoute, ok := routes[j].Value.(caddyhttp.Route)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// use already-decoded matcher, or decode if it's the first time seeing it
|
||||
iPM, jPM := decodedMatchers[i], decodedMatchers[j]
|
||||
if iPM == nil && len(iRoute.MatcherSetsRaw) == 1 {
|
||||
var pathMatcher caddyhttp.MatchPath
|
||||
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &pathMatcher)
|
||||
decodedMatchers[i] = pathMatcher
|
||||
iPM = pathMatcher
|
||||
}
|
||||
if jPM == nil && len(jRoute.MatcherSetsRaw) == 1 {
|
||||
var pathMatcher caddyhttp.MatchPath
|
||||
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &pathMatcher)
|
||||
decodedMatchers[j] = pathMatcher
|
||||
jPM = pathMatcher
|
||||
}
|
||||
|
||||
// sort by longer path (more specific) first; missing
|
||||
// path matchers are treated as zero-length paths
|
||||
var iPathLen, jPathLen int
|
||||
if iPM != nil {
|
||||
iPathLen = len(iPM[0])
|
||||
}
|
||||
if jPM != nil {
|
||||
jPathLen = len(jPM[0])
|
||||
}
|
||||
return iPathLen > jPathLen
|
||||
}
|
||||
|
||||
return dirPositions[iDir] < dirPositions[jDir]
|
||||
})
|
||||
}
|
||||
|
||||
// parseSegmentAsSubroute parses the segment such that its subdirectives
|
||||
// ParseSegmentAsSubroute parses the segment such that its subdirectives
|
||||
// are themselves treated as directives, from which a subroute is built
|
||||
// and returned.
|
||||
func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
func ParseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
allResults, err := parseSegmentAsConfig(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter)
|
||||
}
|
||||
|
||||
// parseSegmentAsConfig parses the segment such that its subdirectives
|
||||
// are themselves treated as directives, including named matcher definitions,
|
||||
// and the raw Config structs are returned.
|
||||
func parseSegmentAsConfig(h Helper) ([]ConfigValue, error) {
|
||||
var allResults []ConfigValue
|
||||
|
||||
for h.Next() {
|
||||
@@ -370,15 +331,138 @@ func parseSegmentAsSubroute(h Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return buildSubroute(allResults, h.groupCounter)
|
||||
return allResults, nil
|
||||
}
|
||||
|
||||
// serverBlock pairs a Caddyfile server block
|
||||
// with a "pile" of config values, keyed by class
|
||||
// name.
|
||||
// ConfigValue represents a value to be added to the final
|
||||
// configuration, or a value to be consulted when building
|
||||
// the final configuration.
|
||||
type ConfigValue struct {
|
||||
// The kind of value this is. As the config is
|
||||
// being built, the adapter will look in the
|
||||
// "pile" for values belonging to a certain
|
||||
// class when it is setting up a certain part
|
||||
// of the config. The associated value will be
|
||||
// type-asserted and placed accordingly.
|
||||
Class string
|
||||
|
||||
// The value to be used when building the config.
|
||||
// Generally its type is associated with the
|
||||
// name of the Class.
|
||||
Value interface{}
|
||||
|
||||
directive string
|
||||
}
|
||||
|
||||
func sortRoutes(routes []ConfigValue) {
|
||||
dirPositions := make(map[string]int)
|
||||
for i, dir := range directiveOrder {
|
||||
dirPositions[dir] = i
|
||||
}
|
||||
|
||||
sort.SliceStable(routes, func(i, j int) bool {
|
||||
// if the directives are different, just use the established directive order
|
||||
iDir, jDir := routes[i].directive, routes[j].directive
|
||||
if iDir != jDir {
|
||||
return dirPositions[iDir] < dirPositions[jDir]
|
||||
}
|
||||
|
||||
// directives are the same; sub-sort by path matcher length if there's
|
||||
// only one matcher set and one path (this is a very common case and
|
||||
// usually -- but not always -- helpful/expected, oh well; user can
|
||||
// always take manual control of order using handler or route blocks)
|
||||
iRoute, ok := routes[i].Value.(caddyhttp.Route)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
jRoute, ok := routes[j].Value.(caddyhttp.Route)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// decode the path matchers, if there is just one of them
|
||||
var iPM, jPM caddyhttp.MatchPath
|
||||
if len(iRoute.MatcherSetsRaw) == 1 {
|
||||
_ = json.Unmarshal(iRoute.MatcherSetsRaw[0]["path"], &iPM)
|
||||
}
|
||||
if len(jRoute.MatcherSetsRaw) == 1 {
|
||||
_ = json.Unmarshal(jRoute.MatcherSetsRaw[0]["path"], &jPM)
|
||||
}
|
||||
|
||||
// sort by longer path (more specific) first; missing path
|
||||
// matchers or multi-matchers are treated as zero-length paths
|
||||
var iPathLen, jPathLen int
|
||||
if len(iPM) > 0 {
|
||||
iPathLen = len(iPM[0])
|
||||
}
|
||||
if len(jPM) > 0 {
|
||||
jPathLen = len(jPM[0])
|
||||
}
|
||||
return iPathLen > jPathLen
|
||||
})
|
||||
}
|
||||
|
||||
// serverBlock pairs a Caddyfile server block with
|
||||
// a "pile" of config values, keyed by class name,
|
||||
// as well as its parsed keys for convenience.
|
||||
type serverBlock struct {
|
||||
block caddyfile.ServerBlock
|
||||
pile map[string][]ConfigValue // config values obtained from directives
|
||||
keys []Address
|
||||
}
|
||||
|
||||
// hostsFromKeys returns a list of all the non-empty hostnames found in
|
||||
// the keys of the server block sb. If logger mode is false, a key with
|
||||
// an empty hostname portion will return an empty slice, since that
|
||||
// server block is interpreted to effectively match all hosts. An empty
|
||||
// string is never added to the slice.
|
||||
//
|
||||
// If loggerMode is true, then the non-standard ports of keys will be
|
||||
// joined to the hostnames. This is to effectively match the Host
|
||||
// header of requests that come in for that key.
|
||||
//
|
||||
// The resulting slice is not sorted but will never have duplicates.
|
||||
func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
|
||||
// ensure each entry in our list is unique
|
||||
hostMap := make(map[string]struct{})
|
||||
for _, addr := range sb.keys {
|
||||
if addr.Host == "" {
|
||||
if !loggerMode {
|
||||
// server block contains a key like ":443", i.e. the host portion
|
||||
// is empty / catch-all, which means to match all hosts
|
||||
return []string{}
|
||||
}
|
||||
// never append an empty string
|
||||
continue
|
||||
}
|
||||
if loggerMode &&
|
||||
addr.Port != "" &&
|
||||
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPPort) &&
|
||||
addr.Port != strconv.Itoa(caddyhttp.DefaultHTTPSPort) {
|
||||
hostMap[net.JoinHostPort(addr.Host, addr.Port)] = struct{}{}
|
||||
} else {
|
||||
hostMap[addr.Host] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// convert map to slice
|
||||
sblockHosts := make([]string, 0, len(hostMap))
|
||||
for host := range hostMap {
|
||||
sblockHosts = append(sblockHosts, host)
|
||||
}
|
||||
|
||||
return sblockHosts
|
||||
}
|
||||
|
||||
// hasHostCatchAllKey returns true if sb has a key that
|
||||
// omits a host portion, i.e. it "catches all" hosts.
|
||||
func (sb serverBlock) hasHostCatchAllKey() bool {
|
||||
for _, addr := range sb.keys {
|
||||
if addr.Host == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type (
|
||||
@@ -397,6 +481,13 @@ type (
|
||||
// for you. These are passed to a call to
|
||||
// RegisterHandlerDirective.
|
||||
UnmarshalHandlerFunc func(h Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
|
||||
// UnmarshalGlobalFunc is a function which can unmarshal Caddyfile
|
||||
// tokens into a global option config value using a Helper type.
|
||||
// These are passed in a call to RegisterGlobalOption.
|
||||
UnmarshalGlobalFunc func(d *caddyfile.Dispenser) (interface{}, error)
|
||||
)
|
||||
|
||||
var registeredDirectives = make(map[string]UnmarshalFunc)
|
||||
|
||||
var registeredGlobalOptions = make(map[string]UnmarshalGlobalFunc)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHostsFromKeys(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
keys []Address
|
||||
expectNormalMode []string
|
||||
expectLoggerMode []string
|
||||
}{
|
||||
{
|
||||
[]Address{
|
||||
{Original: "foo", Host: "foo"},
|
||||
},
|
||||
[]string{"foo"},
|
||||
[]string{"foo"},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "foo", Host: "foo"},
|
||||
{Original: "bar", Host: "bar"},
|
||||
},
|
||||
[]string{"bar", "foo"},
|
||||
[]string{"bar", "foo"},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: ":2015", Port: "2015"},
|
||||
},
|
||||
[]string{}, []string{},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: ":443", Port: "443"},
|
||||
},
|
||||
[]string{}, []string{},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "foo", Host: "foo"},
|
||||
{Original: ":2015", Port: "2015"},
|
||||
},
|
||||
[]string{}, []string{"foo"},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "example.com:2015", Host: "example.com", Port: "2015"},
|
||||
},
|
||||
[]string{"example.com"},
|
||||
[]string{"example.com:2015"},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "example.com:80", Host: "example.com", Port: "80"},
|
||||
},
|
||||
[]string{"example.com"},
|
||||
[]string{"example.com"},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "https://:2015/foo", Scheme: "https", Port: "2015", Path: "/foo"},
|
||||
},
|
||||
[]string{},
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
[]Address{
|
||||
{Original: "https://example.com:2015/foo", Scheme: "https", Host: "example.com", Port: "2015", Path: "/foo"},
|
||||
},
|
||||
[]string{"example.com"},
|
||||
[]string{"example.com:2015"},
|
||||
},
|
||||
} {
|
||||
sb := serverBlock{keys: tc.keys}
|
||||
|
||||
// test in normal mode
|
||||
actual := sb.hostsFromKeys(false)
|
||||
sort.Strings(actual)
|
||||
if !reflect.DeepEqual(tc.expectNormalMode, actual) {
|
||||
t.Errorf("Test %d (loggerMode=false): Expected: %v Actual: %v", i, tc.expectNormalMode, actual)
|
||||
}
|
||||
|
||||
// test in logger mode
|
||||
actual = sb.hostsFromKeys(true)
|
||||
sort.Strings(actual)
|
||||
if !reflect.DeepEqual(tc.expectLoggerMode, actual) {
|
||||
t.Errorf("Test %d (loggerMode=true): Expected: %v Actual: %v", i, tc.expectLoggerMode, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
)
|
||||
|
||||
func TestServerType(t *testing.T) {
|
||||
func TestMatcherSyntax(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expectWarn bool
|
||||
@@ -15,7 +15,7 @@ func TestServerType(t *testing.T) {
|
||||
{
|
||||
input: `http://localhost
|
||||
@debug {
|
||||
query showdebug=1
|
||||
query showdebug=1
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
@@ -24,12 +24,48 @@ func TestServerType(t *testing.T) {
|
||||
{
|
||||
input: `http://localhost
|
||||
@debug {
|
||||
query bad format
|
||||
query bad format
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `http://localhost
|
||||
@debug {
|
||||
not {
|
||||
path /somepath*
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `http://localhost
|
||||
@debug {
|
||||
not path /somepath*
|
||||
}
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `http://localhost
|
||||
@debug not path /somepath*
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `@matcher {
|
||||
path /matcher-not-allowed/outside-of-site-block/*
|
||||
}
|
||||
http://localhost
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
@@ -128,6 +164,58 @@ func TestGlobalOptions(t *testing.T) {
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin 127.0.0.1:2020 {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin 192.168.1.1:2020 127.0.0.1:2020 {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
input: `
|
||||
{
|
||||
admin off {
|
||||
enforce_origin
|
||||
origins 192.168.1.1:2020 127.0.0.1:2020
|
||||
}
|
||||
}
|
||||
:80
|
||||
`,
|
||||
expectWarn: false,
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
|
||||
adapter := caddyfile.Adapter{
|
||||
|
||||
@@ -15,14 +15,41 @@
|
||||
package httpcaddyfile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) {
|
||||
func init() {
|
||||
RegisterGlobalOption("debug", parseOptTrue)
|
||||
RegisterGlobalOption("http_port", parseOptHTTPPort)
|
||||
RegisterGlobalOption("https_port", parseOptHTTPSPort)
|
||||
RegisterGlobalOption("default_sni", parseOptSingleString)
|
||||
RegisterGlobalOption("order", parseOptOrder)
|
||||
RegisterGlobalOption("experimental_http3", parseOptTrue)
|
||||
RegisterGlobalOption("storage", parseOptStorage)
|
||||
RegisterGlobalOption("acme_ca", parseOptSingleString)
|
||||
RegisterGlobalOption("acme_ca_root", parseOptSingleString)
|
||||
RegisterGlobalOption("acme_dns", parseOptSingleString)
|
||||
RegisterGlobalOption("acme_eab", parseOptACMEEAB)
|
||||
RegisterGlobalOption("cert_issuer", parseOptCertIssuer)
|
||||
RegisterGlobalOption("email", parseOptSingleString)
|
||||
RegisterGlobalOption("admin", parseOptAdmin)
|
||||
RegisterGlobalOption("on_demand_tls", parseOptOnDemand)
|
||||
RegisterGlobalOption("local_certs", parseOptTrue)
|
||||
RegisterGlobalOption("key_type", parseOptSingleString)
|
||||
RegisterGlobalOption("auto_https", parseOptAutoHTTPS)
|
||||
}
|
||||
|
||||
func parseOptTrue(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func parseOptHTTPPort(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
var httpPort int
|
||||
for d.Next() {
|
||||
var httpPortStr string
|
||||
@@ -38,7 +65,7 @@ func parseOptHTTPPort(d *caddyfile.Dispenser) (int, error) {
|
||||
return httpPort, nil
|
||||
}
|
||||
|
||||
func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) {
|
||||
func parseOptHTTPSPort(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
var httpsPort int
|
||||
for d.Next() {
|
||||
var httpsPortStr string
|
||||
@@ -54,11 +81,7 @@ func parseOptHTTPSPort(d *caddyfile.Dispenser) (int, error) {
|
||||
return httpsPort, nil
|
||||
}
|
||||
|
||||
func parseOptExperimentalHTTP3(d *caddyfile.Dispenser) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
|
||||
func parseOptOrder(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
newOrder := directiveOrder
|
||||
|
||||
for d.Next() {
|
||||
@@ -68,7 +91,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
|
||||
}
|
||||
dirName := d.Val()
|
||||
if _, ok := registeredDirectives[dirName]; !ok {
|
||||
return nil, fmt.Errorf("%s is not a registered directive", dirName)
|
||||
return nil, d.Errf("%s is not a registered directive", dirName)
|
||||
}
|
||||
|
||||
// get positional token
|
||||
@@ -104,7 +127,7 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
|
||||
case "before":
|
||||
case "after":
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown positional '%s'", pos)
|
||||
return nil, d.Errf("unknown positional '%s'", pos)
|
||||
}
|
||||
|
||||
// get name of other directive
|
||||
@@ -134,22 +157,21 @@ func parseOptOrder(d *caddyfile.Dispenser) ([]string, error) {
|
||||
return newOrder, nil
|
||||
}
|
||||
|
||||
func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
|
||||
if !d.Next() {
|
||||
func parseOptStorage(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
if !d.Next() { // consume option name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
args := d.RemainingArgs()
|
||||
if len(args) != 1 {
|
||||
if !d.Next() { // get storage module name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
modName := args[0]
|
||||
modName := d.Val()
|
||||
mod, err := caddy.GetModule("caddy.storage." + modName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting storage module '%s': %v", modName, err)
|
||||
return nil, d.Errf("getting storage module '%s': %v", modName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
return nil, d.Errf("storage module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
@@ -157,12 +179,67 @@ func parseOptStorage(d *caddyfile.Dispenser) (caddy.StorageConverter, error) {
|
||||
}
|
||||
storage, ok := unm.(caddy.StorageConverter)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module %s is not a StorageConverter", mod.ID)
|
||||
return nil, d.Errf("module %s is not a StorageConverter", mod.ID)
|
||||
}
|
||||
return storage, nil
|
||||
}
|
||||
|
||||
func parseOptSingleString(d *caddyfile.Dispenser) (string, error) {
|
||||
func parseOptACMEEAB(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
eab := new(acme.EAB)
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "key_id":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
eab.KeyID = d.Val()
|
||||
|
||||
case "mac_key":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
eab.MACKey = d.Val()
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
return eab, nil
|
||||
}
|
||||
|
||||
func parseOptCertIssuer(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
if !d.Next() { // consume option name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if !d.Next() { // get issuer module name
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
modName := d.Val()
|
||||
mod, err := caddy.GetModule("tls.issuance." + modName)
|
||||
if err != nil {
|
||||
return nil, d.Errf("getting issuer module '%s': %v", modName, err)
|
||||
}
|
||||
unm, ok := mod.New().(caddyfile.Unmarshaler)
|
||||
if !ok {
|
||||
return nil, d.Errf("issuer module '%s' is not a Caddyfile unmarshaler", mod.ID)
|
||||
}
|
||||
err = unm.UnmarshalCaddyfile(d.NewFromNextSegment())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iss, ok := unm.(certmagic.Issuer)
|
||||
if !ok {
|
||||
return nil, d.Errf("module %s is not a certmagic.Issuer", mod.ID)
|
||||
}
|
||||
return iss, nil
|
||||
}
|
||||
|
||||
func parseOptSingleString(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
d.Next() // consume parameter name
|
||||
if !d.Next() {
|
||||
return "", d.ArgErr()
|
||||
@@ -174,16 +251,113 @@ func parseOptSingleString(d *caddyfile.Dispenser) (string, error) {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func parseOptAdmin(d *caddyfile.Dispenser) (string, error) {
|
||||
if d.Next() {
|
||||
var listenAddress string
|
||||
if !d.AllArgs(&listenAddress) {
|
||||
return "", d.ArgErr()
|
||||
func parseOptAdmin(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
adminCfg := new(caddy.AdminConfig)
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
listenAddress := d.Val()
|
||||
if listenAddress == "off" {
|
||||
adminCfg.Disabled = true
|
||||
if d.Next() { // Do not accept any remaining options including block
|
||||
return nil, d.Err("No more option is allowed after turning off admin config")
|
||||
}
|
||||
} else {
|
||||
adminCfg.Listen = listenAddress
|
||||
if d.NextArg() { // At most 1 arg is allowed
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
}
|
||||
}
|
||||
if listenAddress == "" {
|
||||
listenAddress = caddy.DefaultAdminListen
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "enforce_origin":
|
||||
adminCfg.EnforceOrigin = true
|
||||
|
||||
case "origins":
|
||||
adminCfg.Origins = d.RemainingArgs()
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
return listenAddress, nil
|
||||
}
|
||||
return "", nil
|
||||
if adminCfg.Listen == "" && !adminCfg.Disabled {
|
||||
adminCfg.Listen = caddy.DefaultAdminListen
|
||||
}
|
||||
return adminCfg, nil
|
||||
}
|
||||
|
||||
func parseOptOnDemand(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
var ond *caddytls.OnDemandConfig
|
||||
for d.Next() {
|
||||
if d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||||
switch d.Val() {
|
||||
case "ask":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
if ond == nil {
|
||||
ond = new(caddytls.OnDemandConfig)
|
||||
}
|
||||
ond.Ask = d.Val()
|
||||
|
||||
case "interval":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
dur, err := caddy.ParseDuration(d.Val())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ond == nil {
|
||||
ond = new(caddytls.OnDemandConfig)
|
||||
}
|
||||
if ond.RateLimit == nil {
|
||||
ond.RateLimit = new(caddytls.RateLimit)
|
||||
}
|
||||
ond.RateLimit.Interval = caddy.Duration(dur)
|
||||
|
||||
case "burst":
|
||||
if !d.NextArg() {
|
||||
return nil, d.ArgErr()
|
||||
}
|
||||
burst, err := strconv.Atoi(d.Val())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ond == nil {
|
||||
ond = new(caddytls.OnDemandConfig)
|
||||
}
|
||||
if ond.RateLimit == nil {
|
||||
ond.RateLimit = new(caddytls.RateLimit)
|
||||
}
|
||||
ond.RateLimit.Burst = burst
|
||||
|
||||
default:
|
||||
return nil, d.Errf("unrecognized parameter '%s'", d.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
if ond == nil {
|
||||
return nil, d.Err("expected at least one config parameter for on_demand_tls")
|
||||
}
|
||||
return ond, nil
|
||||
}
|
||||
|
||||
func parseOptAutoHTTPS(d *caddyfile.Dispenser) (interface{}, error) {
|
||||
d.Next() // consume parameter name
|
||||
if !d.Next() {
|
||||
return "", d.ArgErr()
|
||||
}
|
||||
val := d.Val()
|
||||
if d.Next() {
|
||||
return "", d.ArgErr()
|
||||
}
|
||||
if val != "off" && val != "disable_redirects" {
|
||||
return "", d.Errf("auto_https must be either 'off' or 'disable_redirects'")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,491 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 httpcaddyfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/mholt/acmez/acme"
|
||||
)
|
||||
|
||||
func (st ServerType) buildTLSApp(
|
||||
pairings []sbAddrAssociation,
|
||||
options map[string]interface{},
|
||||
warnings []caddyconfig.Warning,
|
||||
) (*caddytls.TLS, []caddyconfig.Warning, error) {
|
||||
|
||||
tlsApp := &caddytls.TLS{CertificatesRaw: make(caddy.ModuleMap)}
|
||||
var certLoaders []caddytls.CertificateLoader
|
||||
|
||||
httpsPort := strconv.Itoa(caddyhttp.DefaultHTTPSPort)
|
||||
if hsp, ok := options["https_port"].(int); ok {
|
||||
httpsPort = strconv.Itoa(hsp)
|
||||
}
|
||||
|
||||
// count how many server blocks have a TLS-enabled key with
|
||||
// no host, and find all hosts that share a server block with
|
||||
// a hostless key, so that they don't get forgotten/omitted
|
||||
// by auto-HTTPS (since they won't appear in route matchers)
|
||||
var serverBlocksWithTLSHostlessKey int
|
||||
hostsSharedWithHostlessKey := make(map[string]struct{})
|
||||
for _, pair := range pairings {
|
||||
for _, sb := range pair.serverBlocks {
|
||||
for _, addr := range sb.keys {
|
||||
if addr.Host == "" {
|
||||
// this address has no hostname, but if it's explicitly set
|
||||
// to HTTPS, then we need to count it as being TLS-enabled
|
||||
if addr.Scheme == "https" || addr.Port == httpsPort {
|
||||
serverBlocksWithTLSHostlessKey++
|
||||
}
|
||||
// this server block has a hostless key, now
|
||||
// go through and add all the hosts to the set
|
||||
for _, otherAddr := range sb.keys {
|
||||
if otherAddr.Original == addr.Original {
|
||||
continue
|
||||
}
|
||||
if otherAddr.Host != "" {
|
||||
hostsSharedWithHostlessKey[otherAddr.Host] = struct{}{}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
catchAllAP, err := newBaseAutomationPolicy(options, warnings, false)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
|
||||
for _, p := range pairings {
|
||||
for _, sblock := range p.serverBlocks {
|
||||
// get values that populate an automation policy for this block
|
||||
var ap *caddytls.AutomationPolicy
|
||||
|
||||
sblockHosts := sblock.hostsFromKeys(false)
|
||||
if len(sblockHosts) == 0 {
|
||||
ap = catchAllAP
|
||||
}
|
||||
|
||||
// on-demand tls
|
||||
if _, ok := sblock.pile["tls.on_demand"]; ok {
|
||||
if ap == nil {
|
||||
var err error
|
||||
ap, err = newBaseAutomationPolicy(options, warnings, true)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
}
|
||||
ap.OnDemand = true
|
||||
}
|
||||
|
||||
// certificate issuers
|
||||
if issuerVals, ok := sblock.pile["tls.cert_issuer"]; ok {
|
||||
for _, issuerVal := range issuerVals {
|
||||
issuer := issuerVal.Value.(certmagic.Issuer)
|
||||
if ap == nil {
|
||||
var err error
|
||||
ap, err = newBaseAutomationPolicy(options, warnings, true)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
}
|
||||
if ap == catchAllAP && !reflect.DeepEqual(ap.Issuer, issuer) {
|
||||
return nil, warnings, fmt.Errorf("automation policy from site block is also default/catch-all policy because of key without hostname, and the two are in conflict: %#v != %#v", ap.Issuer, issuer)
|
||||
}
|
||||
ap.Issuer = issuer
|
||||
}
|
||||
}
|
||||
|
||||
// custom bind host
|
||||
for _, cfgVal := range sblock.pile["bind"] {
|
||||
// either an existing issuer is already configured (and thus, ap is not
|
||||
// nil), or we need to configure an issuer, so we need ap to be non-nil
|
||||
if ap == nil {
|
||||
ap, err = newBaseAutomationPolicy(options, warnings, true)
|
||||
if err != nil {
|
||||
return nil, warnings, err
|
||||
}
|
||||
}
|
||||
|
||||
// if an issuer was already configured and it is NOT an ACME
|
||||
// issuer, skip, since we intend to adjust only ACME issuers
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
if ap.Issuer != nil {
|
||||
// ensure we include any issuer that embeds/wraps an underlying ACME issuer
|
||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||
if acmeWrapper, ok := ap.Issuer.(acmeCapable); ok {
|
||||
acmeIssuer = acmeWrapper.GetACMEIssuer()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// proceed to configure the ACME issuer's bind host, without
|
||||
// overwriting any existing settings
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
if acmeIssuer.Challenges.BindHost == "" {
|
||||
// only binding to one host is supported
|
||||
var bindHost string
|
||||
if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
|
||||
bindHost = bindHosts[0]
|
||||
}
|
||||
acmeIssuer.Challenges.BindHost = bindHost
|
||||
}
|
||||
ap.Issuer = acmeIssuer // we'll encode it later
|
||||
}
|
||||
|
||||
if ap != nil {
|
||||
if ap.Issuer != nil {
|
||||
// encode issuer now that it's all set up
|
||||
issuerName := ap.Issuer.(caddy.Module).CaddyModule().ID.Name()
|
||||
ap.IssuerRaw = caddyconfig.JSONModuleObject(ap.Issuer, "module", issuerName, &warnings)
|
||||
}
|
||||
|
||||
// first make sure this block is allowed to create an automation policy;
|
||||
// doing so is forbidden if it has a key with no host (i.e. ":443")
|
||||
// and if there is a different server block that also has a key with no
|
||||
// host -- since a key with no host matches any host, we need its
|
||||
// associated automation policy to have an empty Subjects list, i.e. no
|
||||
// host filter, which is indistinguishable between the two server blocks
|
||||
// because automation is not done in the context of a particular server...
|
||||
// this is an example of a poor mapping from Caddyfile to JSON but that's
|
||||
// the least-leaky abstraction I could figure out
|
||||
if len(sblockHosts) == 0 {
|
||||
if serverBlocksWithTLSHostlessKey > 1 {
|
||||
// this server block and at least one other has a key with no host,
|
||||
// making the two indistinguishable; it is misleading to define such
|
||||
// a policy within one server block since it actually will apply to
|
||||
// others as well
|
||||
return nil, warnings, fmt.Errorf("cannot make a TLS automation policy from a server block that has a host-less address when there are other TLS-enabled server block addresses lacking a host")
|
||||
}
|
||||
if catchAllAP == nil {
|
||||
// this server block has a key with no hosts, but there is not yet
|
||||
// a catch-all automation policy (probably because no global options
|
||||
// were set), so this one becomes it
|
||||
catchAllAP = ap
|
||||
}
|
||||
}
|
||||
|
||||
// associate our new automation policy with this server block's hosts,
|
||||
// unless, of course, the server block has a key with no hosts, in which
|
||||
// case its automation policy becomes or blends with the default/global
|
||||
// automation policy because, of necessity, it applies to all hostnames
|
||||
// (i.e. it has no Subjects filter) -- in that case, we'll append it last
|
||||
if ap != catchAllAP {
|
||||
ap.Subjects = sblockHosts
|
||||
|
||||
// if a combination of public and internal names were given
|
||||
// for this same server block and no issuer was specified, we
|
||||
// need to separate them out in the automation policies so
|
||||
// that the internal names can use the internal issuer and
|
||||
// the other names can use the default/public/ACME issuer
|
||||
var ap2 *caddytls.AutomationPolicy
|
||||
if ap.Issuer == nil {
|
||||
var internal, external []string
|
||||
for _, s := range ap.Subjects {
|
||||
if certmagic.SubjectQualifiesForPublicCert(s) {
|
||||
external = append(external, s)
|
||||
} else {
|
||||
internal = append(internal, s)
|
||||
}
|
||||
}
|
||||
if len(external) > 0 && len(internal) > 0 {
|
||||
ap.Subjects = external
|
||||
apCopy := *ap
|
||||
ap2 = &apCopy
|
||||
ap2.Subjects = internal
|
||||
ap2.IssuerRaw = caddyconfig.JSONModuleObject(caddytls.InternalIssuer{}, "module", "internal", &warnings)
|
||||
}
|
||||
}
|
||||
if tlsApp.Automation == nil {
|
||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap)
|
||||
if ap2 != nil {
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, ap2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// certificate loaders
|
||||
if clVals, ok := sblock.pile["tls.cert_loader"]; ok {
|
||||
for _, clVal := range clVals {
|
||||
certLoaders = append(certLoaders, clVal.Value.(caddytls.CertificateLoader))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// group certificate loaders by module name, then add to config
|
||||
if len(certLoaders) > 0 {
|
||||
loadersByName := make(map[string]caddytls.CertificateLoader)
|
||||
for _, cl := range certLoaders {
|
||||
name := caddy.GetModuleName(cl)
|
||||
// ugh... technically, we may have multiple FileLoader and FolderLoader
|
||||
// modules (because the tls directive returns one per occurrence), but
|
||||
// the config structure expects only one instance of each kind of loader
|
||||
// module, so we have to combine them... instead of enumerating each
|
||||
// possible cert loader module in a type switch, we can use reflection,
|
||||
// which works on any cert loaders that are slice types
|
||||
if reflect.TypeOf(cl).Kind() == reflect.Slice {
|
||||
combined := reflect.ValueOf(loadersByName[name])
|
||||
if !combined.IsValid() {
|
||||
combined = reflect.New(reflect.TypeOf(cl)).Elem()
|
||||
}
|
||||
clVal := reflect.ValueOf(cl)
|
||||
for i := 0; i < clVal.Len(); i++ {
|
||||
combined = reflect.Append(combined, clVal.Index(i))
|
||||
}
|
||||
loadersByName[name] = combined.Interface().(caddytls.CertificateLoader)
|
||||
}
|
||||
}
|
||||
for certLoaderName, loaders := range loadersByName {
|
||||
tlsApp.CertificatesRaw[certLoaderName] = caddyconfig.JSON(loaders, &warnings)
|
||||
}
|
||||
}
|
||||
|
||||
// set any of the on-demand options, for if/when on-demand TLS is enabled
|
||||
if onDemand, ok := options["on_demand_tls"].(*caddytls.OnDemandConfig); ok {
|
||||
if tlsApp.Automation == nil {
|
||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
tlsApp.Automation.OnDemand = onDemand
|
||||
}
|
||||
|
||||
// if any hostnames appear on the same server block as a key with
|
||||
// no host, they will not be used with route matchers because the
|
||||
// hostless key matches all hosts, therefore, it wouldn't be
|
||||
// considered for auto-HTTPS, so we need to make sure those hosts
|
||||
// are manually considered for managed certificates; we also need
|
||||
// to make sure that any of these names which are internal-only
|
||||
// get internal certificates by default rather than ACME
|
||||
var al caddytls.AutomateLoader
|
||||
internalAP := &caddytls.AutomationPolicy{
|
||||
IssuerRaw: json.RawMessage(`{"module":"internal"}`),
|
||||
}
|
||||
for h := range hostsSharedWithHostlessKey {
|
||||
al = append(al, h)
|
||||
if !certmagic.SubjectQualifiesForPublicCert(h) {
|
||||
internalAP.Subjects = append(internalAP.Subjects, h)
|
||||
}
|
||||
}
|
||||
if len(al) > 0 {
|
||||
tlsApp.CertificatesRaw["automate"] = caddyconfig.JSON(al, &warnings)
|
||||
}
|
||||
if len(internalAP.Subjects) > 0 {
|
||||
if tlsApp.Automation == nil {
|
||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, internalAP)
|
||||
}
|
||||
|
||||
// if there is a global/catch-all automation policy, ensure it goes last
|
||||
if catchAllAP != nil {
|
||||
// first, encode its issuer, if there is one
|
||||
if catchAllAP.Issuer != nil {
|
||||
issuerName := catchAllAP.Issuer.(caddy.Module).CaddyModule().ID.Name()
|
||||
catchAllAP.IssuerRaw = caddyconfig.JSONModuleObject(catchAllAP.Issuer, "module", issuerName, &warnings)
|
||||
}
|
||||
|
||||
// then append it to the end of the policies list
|
||||
if tlsApp.Automation == nil {
|
||||
tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
tlsApp.Automation.Policies = append(tlsApp.Automation.Policies, catchAllAP)
|
||||
}
|
||||
|
||||
// do a little verification & cleanup
|
||||
if tlsApp.Automation != nil {
|
||||
// consolidate automation policies that are the exact same
|
||||
tlsApp.Automation.Policies = consolidateAutomationPolicies(tlsApp.Automation.Policies)
|
||||
|
||||
// ensure automation policies don't overlap subjects (this should be
|
||||
// an error at provision-time as well, but catch it in the adapt phase
|
||||
// for convenience)
|
||||
automationHostSet := make(map[string]struct{})
|
||||
for _, ap := range tlsApp.Automation.Policies {
|
||||
for _, s := range ap.Subjects {
|
||||
if _, ok := automationHostSet[s]; ok {
|
||||
return nil, warnings, fmt.Errorf("hostname appears in more than one automation policy, making certificate management ambiguous: %s", s)
|
||||
}
|
||||
automationHostSet[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tlsApp, warnings, nil
|
||||
}
|
||||
|
||||
// newBaseAutomationPolicy returns a new TLS automation policy that gets
|
||||
// its values from the global options map. It should be used as the base
|
||||
// for any other automation policies. A nil policy (and no error) will be
|
||||
// returned if there are no default/global options. However, if always is
|
||||
// true, a non-nil value will always be returned (unless there is an error).
|
||||
func newBaseAutomationPolicy(options map[string]interface{}, warnings []caddyconfig.Warning, always bool) (*caddytls.AutomationPolicy, error) {
|
||||
issuer, hasIssuer := options["cert_issuer"]
|
||||
|
||||
acmeCA, hasACMECA := options["acme_ca"]
|
||||
acmeCARoot, hasACMECARoot := options["acme_ca_root"]
|
||||
acmeDNS, hasACMEDNS := options["acme_dns"]
|
||||
acmeEAB, hasACMEEAB := options["acme_eab"]
|
||||
|
||||
email, hasEmail := options["email"]
|
||||
localCerts, hasLocalCerts := options["local_certs"]
|
||||
keyType, hasKeyType := options["key_type"]
|
||||
|
||||
hasGlobalAutomationOpts := hasIssuer || hasACMECA || hasACMECARoot || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts || hasKeyType
|
||||
|
||||
// if there are no global options related to automation policies
|
||||
// set, then we can just return right away
|
||||
if !hasGlobalAutomationOpts {
|
||||
if always {
|
||||
return new(caddytls.AutomationPolicy), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ap := new(caddytls.AutomationPolicy)
|
||||
if keyType != nil {
|
||||
ap.KeyType = keyType.(string)
|
||||
}
|
||||
|
||||
if hasIssuer {
|
||||
if hasACMECA || hasACMEDNS || hasACMEEAB || hasEmail || hasLocalCerts {
|
||||
return nil, fmt.Errorf("global options are ambiguous: cert_issuer is confusing when combined with acme_*, email, or local_certs options")
|
||||
}
|
||||
ap.Issuer = issuer.(certmagic.Issuer)
|
||||
} else if localCerts != nil {
|
||||
// internal issuer enabled trumps any ACME configurations; useful in testing
|
||||
ap.Issuer = new(caddytls.InternalIssuer) // we'll encode it later
|
||||
} else {
|
||||
if acmeCA == nil {
|
||||
acmeCA = ""
|
||||
}
|
||||
if email == nil {
|
||||
email = ""
|
||||
}
|
||||
mgr := &caddytls.ACMEIssuer{
|
||||
CA: acmeCA.(string),
|
||||
Email: email.(string),
|
||||
}
|
||||
if acmeDNS != nil {
|
||||
provName := acmeDNS.(string)
|
||||
dnsProvModule, err := caddy.GetModule("dns.providers." + provName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting DNS provider module named '%s': %v", provName, err)
|
||||
}
|
||||
mgr.Challenges = &caddytls.ChallengesConfig{
|
||||
DNS: &caddytls.DNSChallengeConfig{
|
||||
ProviderRaw: caddyconfig.JSONModuleObject(dnsProvModule.New(), "name", provName, &warnings),
|
||||
},
|
||||
}
|
||||
}
|
||||
if acmeCARoot != nil {
|
||||
mgr.TrustedRootsPEMFiles = []string{acmeCARoot.(string)}
|
||||
}
|
||||
if acmeEAB != nil {
|
||||
mgr.ExternalAccount = acmeEAB.(*acme.EAB)
|
||||
}
|
||||
ap.Issuer = disambiguateACMEIssuer(mgr) // we'll encode it later
|
||||
}
|
||||
|
||||
return ap, nil
|
||||
}
|
||||
|
||||
// disambiguateACMEIssuer returns an issuer based on the properties of acmeIssuer.
|
||||
// If acmeIssuer implicitly configures a certain kind of ACMEIssuer (for example,
|
||||
// ZeroSSL), the proper wrapper over acmeIssuer will be returned instead.
|
||||
func disambiguateACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) certmagic.Issuer {
|
||||
// as a special case, we integrate with ZeroSSL's ACME endpoint if it looks like an
|
||||
// implicit ZeroSSL configuration (this requires a wrapper type over ACMEIssuer
|
||||
// because of the EAB generation; if EAB is provided, we can use plain ACMEIssuer)
|
||||
if strings.Contains(acmeIssuer.CA, "acme.zerossl.com") && acmeIssuer.ExternalAccount == nil {
|
||||
return &caddytls.ZeroSSLIssuer{ACMEIssuer: acmeIssuer}
|
||||
}
|
||||
return acmeIssuer
|
||||
}
|
||||
|
||||
// consolidateAutomationPolicies combines automation policies that are the same,
|
||||
// for a cleaner overall output.
|
||||
func consolidateAutomationPolicies(aps []*caddytls.AutomationPolicy) []*caddytls.AutomationPolicy {
|
||||
for i := 0; i < len(aps); i++ {
|
||||
for j := 0; j < len(aps); j++ {
|
||||
if j == i {
|
||||
continue
|
||||
}
|
||||
|
||||
// if they're exactly equal in every way, just keep one of them
|
||||
if reflect.DeepEqual(aps[i], aps[j]) {
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
i--
|
||||
break
|
||||
}
|
||||
|
||||
// if the policy is the same, we can keep just one, but we have
|
||||
// to be careful which one we keep; if only one has any hostnames
|
||||
// defined, then we need to keep the one without any hostnames,
|
||||
// otherwise the one without any subjects (a catch-all) would be
|
||||
// eaten up by the one with subjects; and if both have subjects, we
|
||||
// need to combine their lists
|
||||
if bytes.Equal(aps[i].IssuerRaw, aps[j].IssuerRaw) &&
|
||||
bytes.Equal(aps[i].StorageRaw, aps[j].StorageRaw) &&
|
||||
aps[i].MustStaple == aps[j].MustStaple &&
|
||||
aps[i].KeyType == aps[j].KeyType &&
|
||||
aps[i].OnDemand == aps[j].OnDemand &&
|
||||
aps[i].RenewalWindowRatio == aps[j].RenewalWindowRatio {
|
||||
if len(aps[i].Subjects) == 0 && len(aps[j].Subjects) > 0 {
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
} else if len(aps[i].Subjects) > 0 && len(aps[j].Subjects) == 0 {
|
||||
aps = append(aps[:i], aps[i+1:]...)
|
||||
} else {
|
||||
// avoid repeated subjects
|
||||
for _, subj := range aps[j].Subjects {
|
||||
if !sliceContains(aps[i].Subjects, subj) {
|
||||
aps[i].Subjects = append(aps[i].Subjects, subj)
|
||||
}
|
||||
}
|
||||
aps = append(aps[:j], aps[j+1:]...)
|
||||
}
|
||||
i--
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ensure any catch-all policies go last
|
||||
sort.SliceStable(aps, func(i, j int) bool {
|
||||
return len(aps[i].Subjects) > len(aps[j].Subjects)
|
||||
})
|
||||
|
||||
return aps
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 json5adapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/ilibs/json5"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddyconfig.RegisterAdapter("json5", Adapter{})
|
||||
}
|
||||
|
||||
// Adapter adapts JSON5 to Caddy JSON.
|
||||
type Adapter struct{}
|
||||
|
||||
// Adapt converts the JSON5 config in body to Caddy JSON.
|
||||
func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) {
|
||||
var decoded interface{}
|
||||
err = json5.Unmarshal(body, &decoded)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
result, err = json.Marshal(decoded)
|
||||
return
|
||||
}
|
||||
|
||||
// Interface guard
|
||||
var _ caddyconfig.Adapter = (*Adapter)(nil)
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 jsoncadapter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/muhammadmuzzammil1998/jsonc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddyconfig.RegisterAdapter("jsonc", Adapter{})
|
||||
}
|
||||
|
||||
// Adapter adapts JSON-C to Caddy JSON.
|
||||
type Adapter struct{}
|
||||
|
||||
// Adapt converts the JSON-C config in body to Caddy JSON.
|
||||
func (a Adapter) Adapt(body []byte, options map[string]interface{}) (result []byte, warnings []caddyconfig.Warning, err error) {
|
||||
result = jsonc.ToJSON(body)
|
||||
|
||||
// any errors in the JSON will be
|
||||
// reported during config load, but
|
||||
// we can at least warn here that
|
||||
// it is not valid JSON
|
||||
if !json.Valid(result) {
|
||||
warnings = append(warnings, caddyconfig.Warning{
|
||||
Message: "Resulting JSON is invalid.",
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Interface guard
|
||||
var _ caddyconfig.Adapter = (*Adapter)(nil)
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddyconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(adminLoad{})
|
||||
}
|
||||
|
||||
// adminLoad is a module that provides the /load endpoint
|
||||
// for the Caddy admin API. The only reason it's not baked
|
||||
// into the caddy package directly is because of the import
|
||||
// of the caddyconfig package for its GetAdapter function.
|
||||
// If the caddy package depends on the caddyconfig package,
|
||||
// then the caddyconfig package will not be able to import
|
||||
// the caddy package, and it can more easily cause backward
|
||||
// edges in the dependency tree (i.e. import cycle).
|
||||
// Fortunately, the admin API has first-class support for
|
||||
// adding endpoints from modules.
|
||||
type adminLoad struct{}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (adminLoad) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "admin.api.load",
|
||||
New: func() caddy.Module { return new(adminLoad) },
|
||||
}
|
||||
}
|
||||
|
||||
// Routes returns a route for the /load endpoint.
|
||||
func (al adminLoad) Routes() []caddy.AdminRoute {
|
||||
return []caddy.AdminRoute{
|
||||
{
|
||||
Pattern: "/load",
|
||||
Handler: caddy.AdminHandlerFunc(al.handleLoad),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// handleLoad replaces the entire current configuration with
|
||||
// a new one provided in the response body. It supports config
|
||||
// adapters through the use of the Content-Type header. A
|
||||
// config that is identical to the currently-running config
|
||||
// will be a no-op unless Cache-Control: must-revalidate is set.
|
||||
func (adminLoad) handleLoad(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusMethodNotAllowed,
|
||||
Err: fmt.Errorf("method not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
buf := bufPool.Get().(*bytes.Buffer)
|
||||
buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
_, err := io.Copy(buf, r.Body)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("reading request body: %v", err),
|
||||
}
|
||||
}
|
||||
body := buf.Bytes()
|
||||
|
||||
// if the config is formatted other than Caddy's native
|
||||
// JSON, we need to adapt it before loading it
|
||||
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
|
||||
ct, _, err := mime.ParseMediaType(ctHeader)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("invalid Content-Type: %v", err),
|
||||
}
|
||||
}
|
||||
if !strings.HasSuffix(ct, "/json") {
|
||||
slashIdx := strings.Index(ct, "/")
|
||||
if slashIdx < 0 {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("malformed Content-Type"),
|
||||
}
|
||||
}
|
||||
adapterName := ct[slashIdx+1:]
|
||||
cfgAdapter := GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("unrecognized config adapter '%s'", adapterName),
|
||||
}
|
||||
}
|
||||
result, warnings, err := cfgAdapter.Adapt(body, nil)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("adapting config using %s adapter: %v", adapterName, err),
|
||||
}
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
respBody, err := json.Marshal(warnings)
|
||||
if err != nil {
|
||||
caddy.Log().Named("admin.api.load").Error(err.Error())
|
||||
}
|
||||
_, _ = w.Write(respBody)
|
||||
}
|
||||
body = result
|
||||
}
|
||||
}
|
||||
|
||||
forceReload := r.Header.Get("Cache-Control") == "must-revalidate"
|
||||
|
||||
err = caddy.Load(body, forceReload)
|
||||
if err != nil {
|
||||
return caddy.APIError{
|
||||
Code: http.StatusBadRequest,
|
||||
Err: fmt.Errorf("loading config: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
caddy.Log().Named("admin.api").Info("load complete")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID5zCCAs8CFG4+w/pqR5AZQ+aVB330uRRRKMF0MA0GCSqGSIb3DQEBCwUAMIGv
|
||||
MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZBgNVBAoMEkxvY2FsIERldmVs
|
||||
b3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxvcGVtZW50MRowGAYDVQQDDBFh
|
||||
LmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9jYWwgRGV2ZWxvcGVtZW50MSAw
|
||||
HgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2NhbDAeFw0yMDAzMTMxODUwMTda
|
||||
Fw0zMDAzMTExODUwMTdaMIGvMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxGzAZ
|
||||
BgNVBAoMEkxvY2FsIERldmVsb3BlbWVudDEbMBkGA1UEBwwSTG9jYWwgRGV2ZWxv
|
||||
cGVtZW50MRowGAYDVQQDDBFhLmNhZGR5LmxvY2FsaG9zdDEbMBkGA1UECwwSTG9j
|
||||
YWwgRGV2ZWxvcGVtZW50MSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBjYWRkeS5sb2Nh
|
||||
bDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMd9pC9wF7j0459FndPs
|
||||
Deud/rq41jEZFsVOVtjQgjS1A5ct6NfeMmSlq8i1F7uaTMPZjbOHzY6y6hzLc9+y
|
||||
/VWNgyUC543HjXnNTnp9Xug6tBBxOxvRMw5mv2nAyzjBGDePPgN84xKhOXG2Wj3u
|
||||
fOZ+VPVISefRNvjKfN87WLJ0B0HI9wplG5ASVdPQsWDY1cndrZgt2sxQ/3fjIno4
|
||||
VvrgRWC9Penizgps/a0ZcFZMD/6HJoX/mSZVa1LjopwbMTXvyHCpXkth21E+rBt6
|
||||
I9DMHerdioVQcX25CqPmAwePxPZSNGEQo/Qu32kzcmscmYxTtYBhDa+yLuHgGggI
|
||||
j7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAP/94KPtkpYtkWADnhtzDmgQ6Q1pH
|
||||
SubTUZdCwQtm6/LrvpT+uFNsOj4L3Mv3TVUnIQDmKd5VvR42W2MRBiTN2LQptgEn
|
||||
C7g9BB+UA9kjL3DPk1pJMjzxLHohh0uNLi7eh4mAj8eNvjz9Z4qMWPQoVS0y7/ZK
|
||||
cCBRKh2GkIqKm34ih6pX7xmMpPEQsFoTVPRHYJfYD1SZ8Iui+EN+7WqLuJWPsPXw
|
||||
JM1HuZKn7pZmJU2MZZBsrupHGUvNMbBg2mFJcxt4D1VvU+p+a67PSjpFQ6dJG2re
|
||||
pZoF+N1vMGAFkxe6UqhcC/bXDX+ILVQHJ+RNhzDO6DcWf8dRrC2LaJk3WA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAx32kL3AXuPTjn0Wd0+wN653+urjWMRkWxU5W2NCCNLUDly3o
|
||||
194yZKWryLUXu5pMw9mNs4fNjrLqHMtz37L9VY2DJQLnjceNec1Oen1e6Dq0EHE7
|
||||
G9EzDma/acDLOMEYN48+A3zjEqE5cbZaPe585n5U9UhJ59E2+Mp83ztYsnQHQcj3
|
||||
CmUbkBJV09CxYNjVyd2tmC3azFD/d+MiejhW+uBFYL096eLOCmz9rRlwVkwP/ocm
|
||||
hf+ZJlVrUuOinBsxNe/IcKleS2HbUT6sG3oj0Mwd6t2KhVBxfbkKo+YDB4/E9lI0
|
||||
YRCj9C7faTNyaxyZjFO1gGENr7Iu4eAaCAiPsQIDAQABAoIBAQDD/YFIBeWYlifn
|
||||
e9risQDAIrp3sk7lb9O6Rwv1+Wxi4hBEABvJsYhq74VFK/3EF4UhyWR5JIvkjYyK
|
||||
e6w887oGyoA05ZSe65XoO7fFidSrbbkoikZbPv3dQT7/ZCWEfdkQBNAVVyY0UGeC
|
||||
e3hPbjYRsb5AOSQ694X9idqC6uhqcOrBDjITFrctUoP4S6l9A6a+mLSUIwiICcuh
|
||||
mrNl+j0lzy7DMXRp/Z5Hyo5kuUlrC0dCLa1UHqtrrK7MR55AVEOihSNp1w+OC+vw
|
||||
f0VjE4JUtO7RQEQUmD1tDfLXwNfMFeWaobB2W0WMvRg0IqoitiqPxsPHRm56OxfM
|
||||
SRo/Q7QBAoGBAP8DapzBMuaIcJ7cE8Yl07ZGndWWf8buIKIItGF8rkEO3BXhrIke
|
||||
EmpOi+ELtpbMOG0APhORZyQ58f4ZOVrqZfneNKtDiEZV4mJZaYUESm1pU+2Y6+y5
|
||||
g4bpQSVKN0ow0xR+MH7qDYtSlsmBU7qAOz775L7BmMA1Bnu72aN/H1JBAoGBAMhD
|
||||
OzqCSakHOjUbEd22rPwqWmcIyVyo04gaSmcVVT2dHbqR4/t0gX5a9D9U2qwyO6xi
|
||||
/R+PXyMd32xIeVR2D/7SQ0x6dK68HXICLV8ofHZ5UQcHbxy5og4v/YxSZVTkN374
|
||||
cEsUeyB0s/UPOHLktFU5hpIlON72/Rp7b+pNIwFxAoGAczpq+Qu/YTWzlcSh1r4O
|
||||
7OT5uqI3eH7vFehTAV3iKxl4zxZa7NY+wfRd9kFhrr/2myIp6pOgBFl+hC+HoBIc
|
||||
JAyIxf5M3GNAWOpH6MfojYmzV7/qktu8l8BcJGplk0t+hVsDtMUze4nFAqZCXBpH
|
||||
Kw2M7bjyuZ78H/rgu6TcVUECgYEAo1M5ldE2U/VCApeuLX1TfWDpU8i1uK0zv3d5
|
||||
oLKkT1i5KzTak3SEO9HgC1qf8PoS8tfUio26UICHe99rnHehOfivzEq+qNdgyF+A
|
||||
M3BoeZMdgzcL5oh640k+Zte4LtDlddcWdhUhCepD7iPYrNNbQ3pkBwL2a9lRuOxc
|
||||
7OC2IPECgYBH8f3OrwXjDltIG1dDvuDPNljxLZbFEFbQyVzMePYNftgZknAyGEdh
|
||||
NW/LuWeTzstnmz/s6RE3jN5ZrrMa4sW77VA9+yU9QW2dkHqFyukQ4sfuNg6kDDNZ
|
||||
+lqZYMCLw0M5P9fIbmnIYwey7tXkHfmzoCpnYHGQDN6hL0Bh0zGwmg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQEL
|
||||
BQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkw
|
||||
ODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU
|
||||
7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl0
|
||||
3WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45t
|
||||
wOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNx
|
||||
tdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTU
|
||||
ApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAd
|
||||
BgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS
|
||||
2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5u
|
||||
NY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfK
|
||||
D66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEO
|
||||
fG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnk
|
||||
oNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZ
|
||||
ks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdle
|
||||
Ih6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||
-----END CERTIFICATE-----
|
||||
+301
-69
@@ -11,6 +11,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -18,6 +19,13 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aryann/difflib"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
// plug in Caddy modules here
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
)
|
||||
|
||||
// Defaults store any configuration required to make the tests run
|
||||
@@ -26,12 +34,18 @@ type Defaults struct {
|
||||
AdminPort int
|
||||
// Certificates we expect to be loaded before attempting to run the tests
|
||||
Certifcates []string
|
||||
// TestRequestTimeout is the time to wait for a http request to
|
||||
TestRequestTimeout time.Duration
|
||||
// LoadRequestTimeout is the time to wait for the config to be loaded against the caddy server
|
||||
LoadRequestTimeout time.Duration
|
||||
}
|
||||
|
||||
// Default testing values
|
||||
var Default = Defaults{
|
||||
AdminPort: 2019,
|
||||
Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||
AdminPort: 2019,
|
||||
Certifcates: []string{"/caddy.localhost.crt", "/caddy.localhost.key"},
|
||||
TestRequestTimeout: 5 * time.Second,
|
||||
LoadRequestTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -39,53 +53,93 @@ var (
|
||||
matchCert = regexp.MustCompile(`(/[\w\d\.]+\.crt)`)
|
||||
)
|
||||
|
||||
// Tester represents an instance of a test client.
|
||||
type Tester struct {
|
||||
Client *http.Client
|
||||
configLoaded bool
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
// NewTester will create a new testing client with an attached cookie jar
|
||||
func NewTester(t *testing.T) *Tester {
|
||||
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cookiejar: %s", err)
|
||||
}
|
||||
|
||||
return &Tester{
|
||||
Client: &http.Client{
|
||||
Transport: CreateTestingTransport(),
|
||||
Jar: jar,
|
||||
Timeout: Default.TestRequestTimeout,
|
||||
},
|
||||
configLoaded: false,
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
type configLoadError struct {
|
||||
Response string
|
||||
}
|
||||
|
||||
func (e configLoadError) Error() string { return e.Response }
|
||||
|
||||
func timeElapsed(start time.Time, name string) {
|
||||
elapsed := time.Since(start)
|
||||
log.Printf("%s took %s", name, elapsed)
|
||||
}
|
||||
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func InitServer(t *testing.T, rawConfig string, configType string) {
|
||||
if err := initServer(t, rawConfig, configType); errors.Is(err, &configLoadError{}) {
|
||||
t.Logf("failed to load config: %s", err)
|
||||
t.Fail()
|
||||
func (tc *Tester) InitServer(rawConfig string, configType string) {
|
||||
|
||||
if err := tc.initServer(rawConfig, configType); err != nil {
|
||||
tc.t.Logf("failed to load config: %s", err)
|
||||
tc.t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// InitServer this will configure the server with a configurion of a specific
|
||||
// type. The configType must be either "json" or the adapter type.
|
||||
func initServer(t *testing.T, rawConfig string, configType string) error {
|
||||
func (tc *Tester) initServer(rawConfig string, configType string) error {
|
||||
|
||||
err := validateTestPrerequisites()
|
||||
if err != nil {
|
||||
t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||
if testing.Short() {
|
||||
tc.t.SkipNow()
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
if t.Failed() {
|
||||
err := validateTestPrerequisites()
|
||||
if err != nil {
|
||||
tc.t.Skipf("skipping tests as failed integration prerequisites. %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
tc.t.Cleanup(func() {
|
||||
if tc.t.Failed() && tc.configLoaded {
|
||||
|
||||
res, err := http.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
||||
if err != nil {
|
||||
t.Log("unable to read the current config")
|
||||
tc.t.Log("unable to read the current config")
|
||||
return
|
||||
}
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
|
||||
var out bytes.Buffer
|
||||
json.Indent(&out, body, "", " ")
|
||||
t.Logf("----------- failed with config -----------\n%s", out.String())
|
||||
tc.t.Logf("----------- failed with config -----------\n%s", out.String())
|
||||
}
|
||||
})
|
||||
|
||||
rawConfig = prependCaddyFilePath(rawConfig)
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
Timeout: Default.LoadRequestTimeout,
|
||||
}
|
||||
start := time.Now()
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://localhost:%d/load", Default.AdminPort), strings.NewReader(rawConfig))
|
||||
if err != nil {
|
||||
t.Errorf("failed to create request. %s", err)
|
||||
tc.t.Errorf("failed to create request. %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,13 +151,15 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("unable to contact caddy server. %s", err)
|
||||
tc.t.Errorf("unable to contact caddy server. %s", err)
|
||||
return err
|
||||
}
|
||||
timeElapsed(start, "caddytest: config load time")
|
||||
|
||||
defer res.Body.Close()
|
||||
body, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unable to read response. %s", err)
|
||||
tc.t.Errorf("unable to read response. %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -111,6 +167,7 @@ func initServer(t *testing.T, rawConfig string, configType string) error {
|
||||
return configLoadError{Response: string(body)}
|
||||
}
|
||||
|
||||
tc.configLoaded = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,16 +193,39 @@ func validateTestPrerequisites() error {
|
||||
}
|
||||
}
|
||||
|
||||
if isCaddyAdminRunning() != nil {
|
||||
// start inprocess caddy server
|
||||
os.Args = []string{"caddy", "run"}
|
||||
go func() {
|
||||
caddycmd.Main()
|
||||
}()
|
||||
|
||||
// wait for caddy to start
|
||||
retries := 4
|
||||
for ; retries > 0 && isCaddyAdminRunning() != nil; retries-- {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// assert that caddy is running
|
||||
if err := isCaddyAdminRunning(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
arePrerequisitesValid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func isCaddyAdminRunning() error {
|
||||
// assert that caddy is running
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 2,
|
||||
Timeout: Default.LoadRequestTimeout,
|
||||
}
|
||||
_, err := client.Get(fmt.Sprintf("http://localhost:%d/config/", Default.AdminPort))
|
||||
if err != nil {
|
||||
return errors.New("caddy integration test caddy server not running. Expected to be listening on localhost:2019")
|
||||
}
|
||||
|
||||
arePrerequisitesValid = true
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -168,8 +248,8 @@ func prependCaddyFilePath(rawConfig string) string {
|
||||
return r
|
||||
}
|
||||
|
||||
// creates a testing transport that forces call dialing connections to happen locally
|
||||
func createTestingTransport() *http.Transport {
|
||||
// CreateTestingTransport creates a testing transport that forces call dialing connections to happen locally
|
||||
func CreateTestingTransport() *http.Transport {
|
||||
|
||||
dialer := net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
@@ -198,75 +278,227 @@ func createTestingTransport() *http.Transport {
|
||||
|
||||
// AssertLoadError will load a config and expect an error
|
||||
func AssertLoadError(t *testing.T, rawConfig string, configType string, expectedError string) {
|
||||
err := initServer(t, rawConfig, configType)
|
||||
|
||||
tc := NewTester(t)
|
||||
|
||||
err := tc.initServer(rawConfig, configType)
|
||||
if !strings.Contains(err.Error(), expectedError) {
|
||||
t.Errorf("expected error \"%s\" but got \"%s\"", expectedError, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// AssertGetResponse request a URI and assert the status code and the body contains a string
|
||||
func AssertGetResponse(t *testing.T, requestURI string, statusCode int, expectedBody string) (*http.Response, string) {
|
||||
resp, body := AssertGetResponseBody(t, requestURI, statusCode)
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
t.Errorf("expected response body \"%s\" but got \"%s\"", expectedBody, body)
|
||||
}
|
||||
return resp, string(body)
|
||||
}
|
||||
|
||||
// AssertGetResponseBody request a URI and assert the status code matches
|
||||
func AssertGetResponseBody(t *testing.T, requestURI string, expectedStatusCode int) (*http.Response, string) {
|
||||
|
||||
client := &http.Client{
|
||||
Transport: createTestingTransport(),
|
||||
}
|
||||
|
||||
resp, err := client.Get(requestURI)
|
||||
if err != nil {
|
||||
t.Errorf("failed to call server %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
t.Errorf("expected status code: %d but got %d", expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("unable to read the response body %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
return resp, string(body)
|
||||
}
|
||||
|
||||
// AssertRedirect makes a request and asserts the redirection happens
|
||||
func AssertRedirect(t *testing.T, requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
func (tc *Tester) AssertRedirect(requestURI string, expectedToLocation string, expectedStatusCode int) *http.Response {
|
||||
|
||||
redirectPolicyFunc := func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
CheckRedirect: redirectPolicyFunc,
|
||||
Transport: createTestingTransport(),
|
||||
}
|
||||
// using the existing client, we override the check redirect policy for this test
|
||||
old := tc.Client.CheckRedirect
|
||||
tc.Client.CheckRedirect = redirectPolicyFunc
|
||||
defer func() { tc.Client.CheckRedirect = old }()
|
||||
|
||||
resp, err := client.Get(requestURI)
|
||||
resp, err := tc.Client.Get(requestURI)
|
||||
if err != nil {
|
||||
t.Errorf("failed to call server %s", err)
|
||||
tc.t.Errorf("failed to call server %s", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
t.Errorf("expected status code: %d but got %d", expectedStatusCode, resp.StatusCode)
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", requestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
loc, err := resp.Location()
|
||||
if err != nil {
|
||||
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got error: %s", requestURI, expectedToLocation, err)
|
||||
}
|
||||
|
||||
if expectedToLocation != loc.String() {
|
||||
t.Errorf("expected location: \"%s\" but got \"%s\"", expectedToLocation, loc.String())
|
||||
tc.t.Errorf("requesting \"%s\" expected location: \"%s\" but got \"%s\"", requestURI, expectedToLocation, loc.String())
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// CompareAdapt adapts a config and then compares it against an expected result
|
||||
func CompareAdapt(t *testing.T, rawConfig string, adapterName string, expectedResponse string) bool {
|
||||
|
||||
cfgAdapter := caddyconfig.GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
t.Logf("unrecognized config adapter '%s'", adapterName)
|
||||
return false
|
||||
}
|
||||
|
||||
options := make(map[string]interface{})
|
||||
options["pretty"] = "true"
|
||||
|
||||
result, warnings, err := cfgAdapter.Adapt([]byte(rawConfig), options)
|
||||
if err != nil {
|
||||
t.Logf("adapting config using %s adapter: %v", adapterName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(warnings) > 0 {
|
||||
for _, w := range warnings {
|
||||
t.Logf("warning: directive: %s : %s", w.Directive, w.Message)
|
||||
}
|
||||
}
|
||||
|
||||
diff := difflib.Diff(
|
||||
strings.Split(expectedResponse, "\n"),
|
||||
strings.Split(string(result), "\n"))
|
||||
|
||||
// scan for failure
|
||||
failed := false
|
||||
for _, d := range diff {
|
||||
if d.Delta != difflib.Common {
|
||||
failed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
for _, d := range diff {
|
||||
switch d.Delta {
|
||||
case difflib.Common:
|
||||
fmt.Printf(" %s\n", d.Payload)
|
||||
case difflib.LeftOnly:
|
||||
fmt.Printf(" - %s\n", d.Payload)
|
||||
case difflib.RightOnly:
|
||||
fmt.Printf(" + %s\n", d.Payload)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// AssertAdapt adapts a config and then tests it against an expected result
|
||||
func AssertAdapt(t *testing.T, rawConfig string, adapterName string, expectedResponse string) {
|
||||
ok := CompareAdapt(t, rawConfig, adapterName, expectedResponse)
|
||||
if !ok {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// Generic request functions
|
||||
|
||||
func applyHeaders(t *testing.T, req *http.Request, requestHeaders []string) {
|
||||
requestContentType := ""
|
||||
for _, requestHeader := range requestHeaders {
|
||||
arr := strings.SplitAfterN(requestHeader, ":", 2)
|
||||
k := strings.TrimRight(arr[0], ":")
|
||||
v := strings.TrimSpace(arr[1])
|
||||
if k == "Content-Type" {
|
||||
requestContentType = v
|
||||
}
|
||||
t.Logf("Request header: %s => %s", k, v)
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
if requestContentType == "" {
|
||||
t.Logf("Content-Type header not provided")
|
||||
}
|
||||
}
|
||||
|
||||
// AssertResponseCode will execute the request and verify the status code, returns a response for additional assertions
|
||||
func (tc *Tester) AssertResponseCode(req *http.Request, expectedStatusCode int) *http.Response {
|
||||
|
||||
resp, err := tc.Client.Do(req)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("failed to call server %s", err)
|
||||
}
|
||||
|
||||
if expectedStatusCode != resp.StatusCode {
|
||||
tc.t.Errorf("requesting \"%s\" expected status code: %d but got %d", req.RequestURI, expectedStatusCode, resp.StatusCode)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// AssertResponse request a URI and assert the status code and the body contains a string
|
||||
func (tc *Tester) AssertResponse(req *http.Request, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
resp := tc.AssertResponseCode(req, expectedStatusCode)
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
tc.t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
|
||||
return resp, body
|
||||
}
|
||||
|
||||
// Verb specific test functions
|
||||
|
||||
// AssertGetResponse GET a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertGetResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("GET", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
}
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertDeleteResponse request a URI and expect a statusCode and body text
|
||||
func (tc *Tester) AssertDeleteResponse(requestURI string, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("DELETE", requestURI, nil)
|
||||
if err != nil {
|
||||
tc.t.Fatalf("unable to create request %s", err)
|
||||
}
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPostResponseBody POST to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPostResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("POST", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPutResponseBody PUT to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPutResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PUT", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
// AssertPatchResponseBody PATCH to a URI and assert the response code and body
|
||||
func (tc *Tester) AssertPatchResponseBody(requestURI string, requestHeaders []string, requestBody *bytes.Buffer, expectedStatusCode int, expectedBody string) (*http.Response, string) {
|
||||
|
||||
req, err := http.NewRequest("PATCH", requestURI, requestBody)
|
||||
if err != nil {
|
||||
tc.t.Errorf("failed to create request %s", err)
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
applyHeaders(tc.t, req, requestHeaders)
|
||||
|
||||
return tc.AssertResponse(req, expectedStatusCode, expectedBody)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
auto_https disable_redirects
|
||||
}
|
||||
|
||||
localhost
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"disable_redirects": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
localhost
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{}
|
||||
],
|
||||
"automatic_https": {
|
||||
"disable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
debug
|
||||
http_port 8080
|
||||
https_port 8443
|
||||
default_sni localhost
|
||||
order root first
|
||||
storage file_system {
|
||||
root /data
|
||||
}
|
||||
acme_ca https://example.com
|
||||
acme_ca_root /path/to/ca.crt
|
||||
|
||||
email test@example.com
|
||||
admin off
|
||||
on_demand_tls {
|
||||
ask https://example.com
|
||||
interval 30s
|
||||
burst 20
|
||||
}
|
||||
local_certs
|
||||
key_type ed25519
|
||||
}
|
||||
|
||||
:80
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"disabled": true
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"module": "file_system",
|
||||
"root": "/data"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 8080,
|
||||
"https_port": 8443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuer": {
|
||||
"module": "internal"
|
||||
},
|
||||
"key_type": "ed25519"
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
debug
|
||||
http_port 8080
|
||||
https_port 8443
|
||||
default_sni localhost
|
||||
order root first
|
||||
storage file_system {
|
||||
root /data
|
||||
}
|
||||
acme_ca https://example.com
|
||||
acme_eab {
|
||||
key_id 4K2scIVbBpNd-78scadB2g
|
||||
mac_key abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh
|
||||
}
|
||||
acme_ca_root /path/to/ca.crt
|
||||
email test@example.com
|
||||
admin off
|
||||
on_demand_tls {
|
||||
ask https://example.com
|
||||
interval 30s
|
||||
burst 20
|
||||
}
|
||||
|
||||
key_type ed25519
|
||||
}
|
||||
|
||||
:80
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"disabled": true
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"module": "file_system",
|
||||
"root": "/data"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 8080,
|
||||
"https_port": 8443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuer": {
|
||||
"ca": "https://example.com",
|
||||
"email": "test@example.com",
|
||||
"external_account": {
|
||||
"key_id": "4K2scIVbBpNd-78scadB2g",
|
||||
"mac_key": "abcdefghijklmnopqrstuvwx-abcdefghijklnopqrstuvwxyz12ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefgh"
|
||||
},
|
||||
"module": "acme",
|
||||
"trusted_roots_pem_files": [
|
||||
"/path/to/ca.crt"
|
||||
]
|
||||
},
|
||||
"key_type": "ed25519"
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
{
|
||||
debug
|
||||
http_port 8080
|
||||
https_port 8443
|
||||
default_sni localhost
|
||||
order root first
|
||||
storage file_system {
|
||||
root /data
|
||||
}
|
||||
acme_ca https://example.com
|
||||
acme_ca_root /path/to/ca.crt
|
||||
|
||||
email test@example.com
|
||||
admin {
|
||||
origins localhost:2019 [::1]:2019 127.0.0.1:2019 192.168.10.128
|
||||
}
|
||||
on_demand_tls {
|
||||
ask https://example.com
|
||||
interval 30s
|
||||
burst 20
|
||||
}
|
||||
local_certs
|
||||
key_type ed25519
|
||||
}
|
||||
|
||||
:80
|
||||
----------
|
||||
{
|
||||
"admin": {
|
||||
"listen": "localhost:2019",
|
||||
"origins": [
|
||||
"localhost:2019",
|
||||
"[::1]:2019",
|
||||
"127.0.0.1:2019",
|
||||
"192.168.10.128"
|
||||
]
|
||||
},
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"storage": {
|
||||
"module": "file_system",
|
||||
"root": "/data"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 8080,
|
||||
"https_port": 8443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"automation": {
|
||||
"policies": [
|
||||
{
|
||||
"issuer": {
|
||||
"module": "internal"
|
||||
},
|
||||
"key_type": "ed25519"
|
||||
}
|
||||
],
|
||||
"on_demand": {
|
||||
"rate_limit": {
|
||||
"interval": 30000000000,
|
||||
"burst": 20
|
||||
},
|
||||
"ask": "https://example.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
:80
|
||||
handle_path /api/v1/* {
|
||||
respond "API v1"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/api/v1/*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"strip_path_prefix": "/api/v1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "API v1",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
:80 {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
http://a.caddy.localhost {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"a.caddy.localhost"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
localhost:80 {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
http://a.caddy.localhost:81 {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":81"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"automatic_https": {
|
||||
"skip": [
|
||||
"a.caddy.localhost"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
a.caddy.localhost {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
example.com
|
||||
|
||||
import testdata/import_respond.txt Groot Rocket
|
||||
import testdata/import_respond.txt you "the confused man"
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "'I am Groot', hears Rocket",
|
||||
"handler": "static_response"
|
||||
},
|
||||
{
|
||||
"body": "'I am you', hears the confused man",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
(logging) {
|
||||
log {
|
||||
output file /var/log/caddy/{args.0}.access.log
|
||||
}
|
||||
}
|
||||
|
||||
a.example.com {
|
||||
import logging a.example.com
|
||||
}
|
||||
|
||||
b.example.com {
|
||||
import logging b.example.com
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.log0",
|
||||
"http.log.access.log1"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"filename": "/var/log/caddy/a.example.com.access.log",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log1": {
|
||||
"writer": {
|
||||
"filename": "/var/log/caddy/b.example.com.access.log",
|
||||
"output": "file"
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log1"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"b.example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"logs": {
|
||||
"logger_names": {
|
||||
"a.example.com": "log0",
|
||||
"b.example.com": "log1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
:80
|
||||
|
||||
log {
|
||||
output file /var/log/access.log {
|
||||
roll_size 1gb
|
||||
roll_keep 5
|
||||
roll_keep_for 90d
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"exclude": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
},
|
||||
"log0": {
|
||||
"writer": {
|
||||
"filename": "/var/log/access.log",
|
||||
"output": "file",
|
||||
"roll_keep": 5,
|
||||
"roll_keep_days": 90,
|
||||
"roll_size_mb": 954
|
||||
},
|
||||
"include": [
|
||||
"http.log.access.log0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"logs": {
|
||||
"default_logger_name": "log0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
:80 {
|
||||
@matcher {
|
||||
method GET
|
||||
}
|
||||
respond @matcher "get"
|
||||
|
||||
@matcher2 method POST
|
||||
respond @matcher2 "post"
|
||||
|
||||
@matcher3 not method PUT
|
||||
respond @matcher3 "not put"
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"method": [
|
||||
"GET"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "get",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"method": [
|
||||
"POST"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "post",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"not": [
|
||||
{
|
||||
"method": [
|
||||
"PUT"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "not put",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
:80
|
||||
|
||||
@test {
|
||||
not {
|
||||
header Abc "123"
|
||||
header Bcd "123"
|
||||
}
|
||||
}
|
||||
respond @test 403
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"not": [
|
||||
{
|
||||
"header": {
|
||||
"Abc": [
|
||||
"123"
|
||||
],
|
||||
"Bcd": [
|
||||
"123"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"status_code": 403
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
:8886
|
||||
|
||||
route {
|
||||
# Add trailing slash for directory requests
|
||||
@canonicalPath {
|
||||
file {
|
||||
try_files {path}/index.php
|
||||
}
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
|
||||
# If the requested file does not exist, try index files
|
||||
@indexFiles {
|
||||
file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
|
||||
# Proxy PHP files to the FastCGI responder
|
||||
@phpFiles {
|
||||
path *.php
|
||||
}
|
||||
reverse_proxy @phpFiles 127.0.0.1:9000 {
|
||||
transport fastcgi {
|
||||
split .php
|
||||
}
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8886"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"split_path": [
|
||||
".php"
|
||||
],
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php",
|
||||
"index.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi",
|
||||
"split_path": [
|
||||
".php"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
:8884
|
||||
|
||||
php_fastcgi localhost:9000 {
|
||||
# some php_fastcgi-specific subdirectives
|
||||
split .php .php5
|
||||
env VAR1 value1
|
||||
env VAR2 value2
|
||||
root /var/www
|
||||
index off
|
||||
|
||||
# passed through to reverse_proxy (directive order doesn't matter!)
|
||||
lb_policy random
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php",
|
||||
"*.php5"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"load_balancing": {
|
||||
"selection_policy": {
|
||||
"policy": "random"
|
||||
}
|
||||
},
|
||||
"transport": {
|
||||
"env": {
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
"protocol": "fastcgi",
|
||||
"root": "/var/www",
|
||||
"split_path": [
|
||||
".php",
|
||||
".php5"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
:8884
|
||||
|
||||
@api host example.com
|
||||
php_fastcgi @api localhost:9000
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"split_path": [
|
||||
".php"
|
||||
],
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php",
|
||||
"index.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi",
|
||||
"split_path": [
|
||||
".php"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
:8884
|
||||
|
||||
php_fastcgi localhost:9000 {
|
||||
# some php_fastcgi-specific subdirectives
|
||||
split .php .php5
|
||||
env VAR1 value1
|
||||
env VAR2 value2
|
||||
root /var/www
|
||||
index index.php5
|
||||
|
||||
# passed through to reverse_proxy (directive order doesn't matter!)
|
||||
lb_policy random
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}/index.php5"
|
||||
]
|
||||
},
|
||||
"not": [
|
||||
{
|
||||
"path": [
|
||||
"*/"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "static_response",
|
||||
"headers": {
|
||||
"Location": [
|
||||
"{http.request.uri.path}/"
|
||||
]
|
||||
},
|
||||
"status_code": 308
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"file": {
|
||||
"try_files": [
|
||||
"{http.request.uri.path}",
|
||||
"{http.request.uri.path}/index.php5",
|
||||
"index.php5"
|
||||
],
|
||||
"split_path": [
|
||||
".php",
|
||||
".php5"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "rewrite",
|
||||
"uri": "{http.matchers.file.relative}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"*.php",
|
||||
"*.php5"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"load_balancing": {
|
||||
"selection_policy": {
|
||||
"policy": "random"
|
||||
}
|
||||
},
|
||||
"transport": {
|
||||
"env": {
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
"protocol": "fastcgi",
|
||||
"root": "/var/www",
|
||||
"split_path": [
|
||||
".php",
|
||||
".php5"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:9000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
:8884
|
||||
|
||||
reverse_proxy 127.0.0.1:65535 {
|
||||
transport fastcgi
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":8884"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "fastcgi"
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "127.0.0.1:65535"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
|
||||
https://example.com {
|
||||
reverse_proxy /path http://localhost:54321 {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote}
|
||||
header_up X-Forwarded-For {remote}
|
||||
header_up X-Forwarded-Port {server_port}
|
||||
header_up X-Forwarded-Proto "http"
|
||||
transport http {
|
||||
versions h2c 2
|
||||
compression off
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"example.com"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"headers": {
|
||||
"request": {
|
||||
"set": {
|
||||
"Host": [
|
||||
"{http.request.host}"
|
||||
],
|
||||
"X-Forwarded-For": [
|
||||
"{http.request.remote}"
|
||||
],
|
||||
"X-Forwarded-Port": [
|
||||
"{server_port}"
|
||||
],
|
||||
"X-Forwarded-Proto": [
|
||||
"http"
|
||||
],
|
||||
"X-Real-Ip": [
|
||||
"{http.request.remote}"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"transport": {
|
||||
"compression": false,
|
||||
"protocol": "http",
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/path"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
localhost:80
|
||||
respond * "{header.content-type} {labels.0} {query.p} {path.0} {re.name.0}"
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "{http.request.header.content-type} {http.request.host.labels.0} {http.request.uri.query.p} {http.request.uri.path.0} {http.regexp.name.0}",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
client_auth {
|
||||
mode request
|
||||
trusted_ca_cert_file ../caddy.ca.cer
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"match": {
|
||||
"sni": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"client_authentication": {
|
||||
"trusted_ca_certs": [
|
||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||
],
|
||||
"mode": "request"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
localhost
|
||||
|
||||
respond "hello from localhost"
|
||||
tls {
|
||||
client_auth {
|
||||
mode request
|
||||
trusted_ca_cert MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ==
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"match": {
|
||||
"sni": [
|
||||
"localhost"
|
||||
]
|
||||
},
|
||||
"client_authentication": {
|
||||
"trusted_ca_certs": [
|
||||
"MIIDSzCCAjOgAwIBAgIUfIRObjWNUA4jxQ/0x8BOCvE2Vw4wDQYJKoZIhvcNAQELBQAwFjEUMBIGA1UEAwwLRWFzeS1SU0EgQ0EwHhcNMTkwODI4MTYyNTU5WhcNMjkwODI1MTYyNTU5WjAWMRQwEgYDVQQDDAtFYXN5LVJTQSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5m5elxhQfMp/3aVJ4JnpN9PUSz6LlP6LePAPFU7gqohVVFVtDkChJAG3FNkNQNlieVTja/bgH9IcC6oKbROwdY1h0MvNV8AHHigvl03WuJD8g2ReVFXXwsnrPmKXCFzQyMI6TYk3m2gYrXsZOU1GLnfMRC3KAMRgE2F45twOs9hqG169YJ6mM2eQjzjCHWI6S2/iUYvYxRkCOlYUbLsMD/AhgAf1plzg6LPqNxtdlwxZnA0ytgkmhK67HtzJu0+ovUCsMv0RwcMhsEo9T8nyFAGt9XLZ63X5WpBCTUApaAUhnG0XnerjmUWb6eUWw4zev54sEfY5F3x002iQaW6cECAwEAAaOBkDCBjTAdBgNVHQ4EFgQU4CBUbZsS2GaNIkGRz/cBsD5ivjswUQYDVR0jBEowSIAU4CBUbZsS2GaNIkGRz/cBsD5ivjuhGqQYMBYxFDASBgNVBAMMC0Vhc3ktUlNBIENBghR8hE5uNY1QDiPFD/THwE4K8TZXDjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAKB3V4HIzoiO/Ch6WMj9bLJ2FGbpkMrcb/Eq01hT5zcfKD66lVS1MlK+cRL446Z2b2KDP1oFyVs+qmrmtdwrWgD+nfe2sBmmIHo9m9KygMkEOfG3MghGTEcS+0cTKEcoHYWYyOqQh6jnedXY8Cdm4GM1hAc9MiL3/sqV8YCVSLNnkoNysmr06/rZ0MCUZPGUtRmfd0heWhrfzAKw2HLgX+RAmpOE2MZqWcjvqKGyaRiaZks4nJkP6521aC2Lgp0HhCz1j8/uQ5ldoDszCnu/iro0NAsNtudTMD+YoLQxLqdleIh6CW+illc2VdXwj7mn6J04yns9jfE2jRjW/yTLFuQ=="
|
||||
],
|
||||
"mode": "request"
|
||||
}
|
||||
},
|
||||
{}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestCaddyfileAdaptToJSON(t *testing.T) {
|
||||
// load the list of test files from the dir
|
||||
files, err := ioutil.ReadDir("./caddyfile_adapt")
|
||||
if err != nil {
|
||||
t.Errorf("failed to read caddyfile_adapt dir: %s", err)
|
||||
}
|
||||
|
||||
// prep a regexp to fix strings on windows
|
||||
winNewlines := regexp.MustCompile(`\r?\n`)
|
||||
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// read the test file
|
||||
filename := f.Name()
|
||||
data, err := ioutil.ReadFile("./caddyfile_adapt/" + filename)
|
||||
if err != nil {
|
||||
t.Errorf("failed to read %s dir: %s", filename, err)
|
||||
}
|
||||
|
||||
// split the Caddyfile (first) and JSON (second) parts
|
||||
parts := strings.Split(string(data), "----------")
|
||||
caddyfile, json := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])
|
||||
|
||||
// replace windows newlines in the json with unix newlines
|
||||
json = winNewlines.ReplaceAllString(json, "\n")
|
||||
|
||||
// run the test
|
||||
ok := caddytest.CompareAdapt(t, caddyfile, "caddyfile", json)
|
||||
if !ok {
|
||||
t.Errorf("failed to adapt %s", filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
@@ -9,7 +11,8 @@ import (
|
||||
func TestRespond(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
@@ -17,19 +20,20 @@ func TestRespond(t *testing.T) {
|
||||
|
||||
localhost:9080 {
|
||||
respond /version 200 {
|
||||
body "hello from a.caddy.localhost"
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
caddytest.AssertGetResponse(t, "http://localhost:9080/version", 200, "hello from a.caddy.localhost")
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost")
|
||||
}
|
||||
|
||||
func TestRedirect(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
@@ -40,16 +44,16 @@ func TestRedirect(t *testing.T) {
|
||||
redir / http://localhost:9080/hello 301
|
||||
|
||||
respond /hello 200 {
|
||||
body "hello from b"
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
caddytest.AssertRedirect(t, "http://localhost:9080/", "http://localhost:9080/hello", 301)
|
||||
tester.AssertRedirect("http://localhost:9080/", "http://localhost:9080/hello", 301)
|
||||
|
||||
// follow redirect
|
||||
caddytest.AssertGetResponse(t, "http://localhost:9080/", 200, "hello from b")
|
||||
tester.AssertGetResponse("http://localhost:9080/", 200, "hello from localhost")
|
||||
}
|
||||
|
||||
func TestDuplicateHosts(t *testing.T) {
|
||||
@@ -67,25 +71,33 @@ func TestDuplicateHosts(t *testing.T) {
|
||||
"duplicate site address not allowed")
|
||||
}
|
||||
|
||||
func TestDefaultSNI(t *testing.T) {
|
||||
func TestReadCookie(t *testing.T) {
|
||||
|
||||
localhost, _ := url.Parse("http://localhost")
|
||||
cookie := http.Cookie{
|
||||
Name: "clientname",
|
||||
Value: "caddytest",
|
||||
}
|
||||
|
||||
// arrange
|
||||
caddytest.InitServer(t, `
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.Client.Jar.SetCookies(localhost, []*http.Cookie{&cookie})
|
||||
tester.InitServer(`
|
||||
{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
default_sni *.caddy.localhost
|
||||
}
|
||||
|
||||
127.0.0.1:9443 {
|
||||
tls /caddy.localhost.crt /caddy.localhost.key {
|
||||
localhost:9080 {
|
||||
templates {
|
||||
root testdata
|
||||
}
|
||||
file_server {
|
||||
root testdata
|
||||
}
|
||||
respond /version 200 {
|
||||
body "hello from a.caddy.localhost"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
caddytest.AssertGetResponse(t, "https://127.0.0.1:9443/version", 200, "hello from a.caddy.localhost")
|
||||
tester.AssertGetResponse("http://localhost:9080/cookie.html", 200, "<h2>Cookie.ClientName caddytest</h2>")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
|
||||
map http.request.method dest-name {
|
||||
default unknown
|
||||
G.T get-called
|
||||
POST post-called
|
||||
}
|
||||
|
||||
respond /version 200 {
|
||||
body "hello from localhost {dest-name}"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
|
||||
}
|
||||
|
||||
func TestMapRespondWithDefault(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
http_port 9080
|
||||
https_port 9443
|
||||
}
|
||||
|
||||
localhost:9080 {
|
||||
|
||||
map http.request.method dest-name {
|
||||
default unknown
|
||||
GET get-called
|
||||
}
|
||||
|
||||
respond /version 200 {
|
||||
body "hello from localhost {dest-name}"
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// act and assert
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost unknown")
|
||||
}
|
||||
|
||||
func TestMapAsJson(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9080"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "map",
|
||||
"source": "http.request.method",
|
||||
"destination": "dest-name",
|
||||
"default": "unknown",
|
||||
"items": [
|
||||
{
|
||||
"expression": "GET",
|
||||
"value": "get-called"
|
||||
},
|
||||
{
|
||||
"expression": "POST",
|
||||
"value": "post-called"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost {dest-name}",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"localhost"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:9080/version", 200, "hello from localhost get-called")
|
||||
tester.AssertPostResponseBody("http://localhost:9080/version", []string{}, bytes.NewBuffer([]byte{}), 200, "hello from localhost post-called")
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestDefaultSNI(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"match": {
|
||||
"sni": [
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "*.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/caddy.localhost.crt",
|
||||
"key": "/caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithNamedHostAndExplicitIP(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"host": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost",
|
||||
"match": {
|
||||
"sni": [
|
||||
"a.caddy.localhost",
|
||||
"127.0.0.1",
|
||||
""
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestDefaultSNIWithPortMappingOnly(t *testing.T) {
|
||||
|
||||
// arrange
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from a.caddy.localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// act and assert
|
||||
// makes a request with no sni
|
||||
tester.AssertGetResponse("https://127.0.0.1:9443/version", 200, "hello from a")
|
||||
}
|
||||
|
||||
func TestHttpOnlyOnDomainWithSNI(t *testing.T) {
|
||||
caddytest.AssertAdapt(t, `
|
||||
{
|
||||
default_sni a.caddy.localhost
|
||||
}
|
||||
:80 {
|
||||
respond /version 200 {
|
||||
body "hello from localhost"
|
||||
}
|
||||
}
|
||||
`, "caddyfile", `{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/version"
|
||||
]
|
||||
}
|
||||
],
|
||||
"handle": [
|
||||
{
|
||||
"body": "hello from localhost",
|
||||
"handler": "static_response",
|
||||
"status_code": 200
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
// (see https://github.com/caddyserver/caddy/issues/3556 for use case)
|
||||
func TestH2ToH2CStream(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"transport": {
|
||||
"protocol": "http",
|
||||
"compression": false,
|
||||
"versions": [
|
||||
"h2c",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/tov2ray"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": ["cert0"]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities" : {
|
||||
"local" : {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
expectedBody := "some data to be echoed"
|
||||
// start the server
|
||||
server := testH2ToH2CStreamServeH2C(t)
|
||||
go server.ListenAndServe()
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
r, w := io.Pipe()
|
||||
req := &http.Request{
|
||||
Method: "PUT",
|
||||
Body: ioutil.NopCloser(r),
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "127.0.0.1:9443",
|
||||
Path: "/tov2ray",
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// Disable any compression method from server.
|
||||
req.Header.Set("Accept-Encoding", "identity")
|
||||
|
||||
resp := tester.AssertResponseCode(req, 200)
|
||||
if 200 != resp.StatusCode {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
fmt.Fprint(w, expectedBody)
|
||||
w.Close()
|
||||
}()
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if !strings.Contains(body, expectedBody) {
|
||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testH2ToH2CStreamServeH2C(t *testing.T) *http.Server {
|
||||
h2s := &http2.Server{}
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rstring, err := httputil.DumpRequest(r, false)
|
||||
if err == nil {
|
||||
t.Logf("h2c server received req: %s", rstring)
|
||||
}
|
||||
// We only accept HTTP/2!
|
||||
if r.ProtoMajor != 2 {
|
||||
t.Error("Not a HTTP/2 request, rejected!")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if r.Host != "127.0.0.1:9443" {
|
||||
t.Errorf("r.Host doesn't match, %v!", r.Host)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.WriteHeader(200)
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
|
||||
buf := make([]byte, 4*1024)
|
||||
|
||||
for {
|
||||
n, err := r.Body.Read(buf)
|
||||
if n > 0 {
|
||||
w.Write(buf[:n])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
r.Body.Close()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "127.0.0.1:54321",
|
||||
Handler: h2c.NewHandler(handler, h2s),
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// (see https://github.com/caddyserver/caddy/issues/3606 for use case)
|
||||
func TestH2ToH1ChunkedResponse(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
"logging": {
|
||||
"logs": {
|
||||
"default": {
|
||||
"level": "DEBUG"
|
||||
}
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":9443"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "subroute",
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"encodings": {
|
||||
"gzip": {}
|
||||
},
|
||||
"handler": "encode"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"handler": "reverse_proxy",
|
||||
"upstreams": [
|
||||
{
|
||||
"dial": "localhost:54321"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"match": [
|
||||
{
|
||||
"path": [
|
||||
"/tov2ray"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"terminal": true
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {
|
||||
"any_tag": [
|
||||
"cert0"
|
||||
]
|
||||
},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": [
|
||||
"cert0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {
|
||||
"install_trust": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`, "json")
|
||||
|
||||
// need a large body here to trigger caddy's compression, larger than gzip.miniLength
|
||||
expectedBody, err := GenerateRandomString(1024)
|
||||
if err != nil {
|
||||
t.Fatalf("generate expected body failed, err: %s", err)
|
||||
}
|
||||
|
||||
// start the server
|
||||
server := testH2ToH1ChunkedResponseServeH1(t)
|
||||
go server.ListenAndServe()
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
r, w := io.Pipe()
|
||||
req := &http.Request{
|
||||
Method: "PUT",
|
||||
Body: ioutil.NopCloser(r),
|
||||
URL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "127.0.0.1:9443",
|
||||
Path: "/tov2ray",
|
||||
},
|
||||
Proto: "HTTP/2",
|
||||
ProtoMajor: 2,
|
||||
ProtoMinor: 0,
|
||||
Header: make(http.Header),
|
||||
}
|
||||
// underlying transport will automaticlly add gzip
|
||||
// req.Header.Set("Accept-Encoding", "gzip")
|
||||
go func() {
|
||||
fmt.Fprint(w, expectedBody)
|
||||
w.Close()
|
||||
}()
|
||||
resp := tester.AssertResponseCode(req, 200)
|
||||
if 200 != resp.StatusCode {
|
||||
return
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
body := string(bytes)
|
||||
|
||||
if body != expectedBody {
|
||||
t.Errorf("requesting \"%s\" expected response body \"%s\" but got \"%s\"", req.RequestURI, expectedBody, body)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testH2ToH1ChunkedResponseServeH1(t *testing.T) *http.Server {
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Host != "127.0.0.1:9443" {
|
||||
t.Errorf("r.Host doesn't match, %v!", r.Host)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, "/tov2ray") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
defer r.Body.Close()
|
||||
bytes, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to read the response body %s", err)
|
||||
}
|
||||
|
||||
n := len(bytes)
|
||||
|
||||
var writer io.Writer
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
gw, err := gzip.NewWriterLevel(w, 5)
|
||||
if err != nil {
|
||||
t.Error("can't return gzip data")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer gw.Close()
|
||||
writer = gw
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
w.Header().Del("Content-Length")
|
||||
w.WriteHeader(200)
|
||||
} else {
|
||||
writer = w
|
||||
}
|
||||
if n > 0 {
|
||||
writer.Write(bytes[:])
|
||||
}
|
||||
})
|
||||
|
||||
server := &http.Server{
|
||||
Addr: "127.0.0.1:54321",
|
||||
Handler: handler,
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
// GenerateRandomBytes returns securely generated random bytes.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
// Note that err == nil only if we read len(b) bytes.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GenerateRandomString returns a securely generated random string.
|
||||
// It will return an error if the system's secure random
|
||||
// number generator fails to function correctly, in which
|
||||
// case the caller should not continue.
|
||||
func GenerateRandomString(n int) (string, error) {
|
||||
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
||||
bytes, err := GenerateRandomBytes(n)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for i, b := range bytes {
|
||||
bytes[i] = letters[b%byte(len(letters))]
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
<h2>Cookie.ClientName {{.Cookie "clientname"}}</h2>
|
||||
@@ -0,0 +1 @@
|
||||
respond "'I am {args.0}', hears {args.1}"
|
||||
+114
-48
@@ -16,6 +16,7 @@ package caddycmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -35,13 +36,14 @@ import (
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func cmdStart(fl Flags) (int, error) {
|
||||
startCmdConfigFlag := fl.String("config")
|
||||
startCmdConfigAdapterFlag := fl.String("adapter")
|
||||
startCmdPidfileFlag := fl.String("pidfile")
|
||||
startCmdWatchFlag := fl.Bool("watch")
|
||||
|
||||
// open a listener to which the child process will connect when
|
||||
// it is ready to confirm that it has successfully started
|
||||
@@ -68,6 +70,12 @@ func cmdStart(fl Flags) (int, error) {
|
||||
if startCmdConfigAdapterFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--adapter", startCmdConfigAdapterFlag)
|
||||
}
|
||||
if startCmdWatchFlag {
|
||||
cmd.Args = append(cmd.Args, "--watch")
|
||||
}
|
||||
if startCmdPidfileFlag != "" {
|
||||
cmd.Args = append(cmd.Args, "--pidfile", startCmdPidfileFlag)
|
||||
}
|
||||
stdinpipe, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
@@ -141,12 +149,25 @@ func cmdStart(fl Flags) (int, error) {
|
||||
}
|
||||
|
||||
func cmdRun(fl Flags) (int, error) {
|
||||
caddy.TrapSignals()
|
||||
|
||||
runCmdConfigFlag := fl.String("config")
|
||||
runCmdConfigAdapterFlag := fl.String("adapter")
|
||||
runCmdResumeFlag := fl.Bool("resume")
|
||||
runCmdLoadEnvfileFlag := fl.String("envfile")
|
||||
runCmdPrintEnvFlag := fl.Bool("environ")
|
||||
runCmdWatchFlag := fl.Bool("watch")
|
||||
runCmdPidfileFlag := fl.String("pidfile")
|
||||
runCmdPingbackFlag := fl.String("pingback")
|
||||
|
||||
// load all additional envs as soon as possible
|
||||
if runCmdLoadEnvfileFlag != "" {
|
||||
if err := loadEnvFromFile(runCmdLoadEnvfileFlag); err != nil {
|
||||
return caddy.ExitCodeFailedStartup,
|
||||
fmt.Errorf("loading additional environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// if we are supposed to print the environment, do that first
|
||||
if runCmdPrintEnvFlag {
|
||||
printEnvironment()
|
||||
@@ -167,22 +188,26 @@ func cmdRun(fl Flags) (int, error) {
|
||||
} else if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
} else {
|
||||
caddy.Log().Info("resuming from last configuration", zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||
if runCmdConfigFlag == "" {
|
||||
caddy.Log().Info("resuming from last configuration",
|
||||
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||
} else {
|
||||
// if they also specified a config file, user should be aware that we're not
|
||||
// using it (doing so could lead to data/config loss by overwriting!)
|
||||
caddy.Log().Warn("--config and --resume flags were used together; ignoring --config and resuming from last configuration",
|
||||
zap.String("autosave_file", caddy.ConfigAutosavePath))
|
||||
}
|
||||
}
|
||||
}
|
||||
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
|
||||
var configFile string
|
||||
if !runCmdResumeFlag {
|
||||
config, _, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
|
||||
config, configFile, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
}
|
||||
|
||||
// set a fitting User-Agent for ACME requests
|
||||
goModule := caddy.GoModule()
|
||||
cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
|
||||
certmagic.UserAgent = "Caddy/" + cleanModVersion
|
||||
|
||||
// run the initial config
|
||||
err = caddy.Load(config, true)
|
||||
if err != nil {
|
||||
@@ -211,6 +236,22 @@ func cmdRun(fl Flags) (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// if enabled, reload config file automatically on changes
|
||||
// (this better only be used in dev!)
|
||||
if runCmdWatchFlag {
|
||||
go watchConfigFile(configFile, runCmdConfigAdapterFlag)
|
||||
}
|
||||
|
||||
// create pidfile
|
||||
if runCmdPidfileFlag != "" {
|
||||
err := caddy.PIDFile(runCmdPidfileFlag)
|
||||
if err != nil {
|
||||
caddy.Log().Error("unable to write PID file",
|
||||
zap.String("pidfile", runCmdPidfileFlag),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// warn if the environment does not provide enough information about the disk
|
||||
hasXDG := os.Getenv("XDG_DATA_HOME") != "" &&
|
||||
os.Getenv("XDG_CONFIG_HOME") != "" &&
|
||||
@@ -236,24 +277,9 @@ func cmdRun(fl Flags) (int, error) {
|
||||
func cmdStop(fl Flags) (int, error) {
|
||||
stopCmdAddrFlag := fl.String("address")
|
||||
|
||||
adminAddr := caddy.DefaultAdminListen
|
||||
if stopCmdAddrFlag != "" {
|
||||
adminAddr = stopCmdAddrFlag
|
||||
}
|
||||
stopEndpoint := fmt.Sprintf("http://%s/stop", adminAddr)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, stopEndpoint, nil)
|
||||
err := apiRequest(stopCmdAddrFlag, http.MethodPost, "/stop", nil)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
|
||||
}
|
||||
req.Header.Set("Origin", adminAddr)
|
||||
|
||||
err = apiRequest(req)
|
||||
if err != nil {
|
||||
caddy.Log().Warn("failed using API to stop instance",
|
||||
zap.String("endpoint", stopEndpoint),
|
||||
zap.Error(err),
|
||||
)
|
||||
caddy.Log().Warn("failed using API to stop instance", zap.Error(err))
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
@@ -266,15 +292,15 @@ func cmdReload(fl Flags) (int, error) {
|
||||
reloadCmdAddrFlag := fl.String("address")
|
||||
|
||||
// get the config in caddy's native format
|
||||
config, hasConfig, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
|
||||
config, configFile, err := loadConfig(reloadCmdConfigFlag, reloadCmdConfigAdapterFlag)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
if !hasConfig {
|
||||
if configFile == "" {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("no config file to load")
|
||||
}
|
||||
|
||||
// get the address of the admin listener and craft endpoint URL
|
||||
// get the address of the admin listener; use flag if specified
|
||||
adminAddr := reloadCmdAddrFlag
|
||||
if adminAddr == "" && len(config) > 0 {
|
||||
var tmpStruct struct {
|
||||
@@ -287,20 +313,8 @@ func cmdReload(fl Flags) (int, error) {
|
||||
}
|
||||
adminAddr = tmpStruct.Admin.Listen
|
||||
}
|
||||
if adminAddr == "" {
|
||||
adminAddr = caddy.DefaultAdminListen
|
||||
}
|
||||
loadEndpoint := fmt.Sprintf("http://%s/load", adminAddr)
|
||||
|
||||
// prepare the request to update the configuration
|
||||
req, err := http.NewRequest(http.MethodPost, loadEndpoint, bytes.NewReader(config))
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("making request: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Origin", adminAddr)
|
||||
|
||||
err = apiRequest(req)
|
||||
err = apiRequest(adminAddr, http.MethodPost, "/load", bytes.NewReader(config))
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("sending configuration to instance: %v", err)
|
||||
}
|
||||
@@ -514,14 +528,12 @@ func cmdValidateConfig(fl Flags) (int, error) {
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
func cmdFormatConfig(fl Flags) (int, error) {
|
||||
// Default path of file is Caddyfile
|
||||
func cmdFmt(fl Flags) (int, error) {
|
||||
formatCmdConfigFile := fl.Arg(0)
|
||||
if formatCmdConfigFile == "" {
|
||||
formatCmdConfigFile = "Caddyfile"
|
||||
}
|
||||
|
||||
formatCmdWriteFlag := fl.Bool("write")
|
||||
overwrite := fl.Bool("overwrite")
|
||||
|
||||
input, err := ioutil.ReadFile(formatCmdConfigFile)
|
||||
if err != nil {
|
||||
@@ -531,7 +543,7 @@ func cmdFormatConfig(fl Flags) (int, error) {
|
||||
|
||||
output := caddyfile.Format(input)
|
||||
|
||||
if formatCmdWriteFlag {
|
||||
if overwrite {
|
||||
err = ioutil.WriteFile(formatCmdConfigFile, output, 0644)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, nil
|
||||
@@ -607,8 +619,62 @@ commands:
|
||||
return caddy.ExitCodeSuccess, nil
|
||||
}
|
||||
|
||||
func apiRequest(req *http.Request) error {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
// apiRequest makes an API request to the endpoint adminAddr with the
|
||||
// given HTTP method and request URI. If body is non-nil, it will be
|
||||
// assumed to be Content-Type application/json.
|
||||
func apiRequest(adminAddr, method, uri string, body io.Reader) error {
|
||||
// parse the admin address
|
||||
if adminAddr == "" {
|
||||
adminAddr = caddy.DefaultAdminListen
|
||||
}
|
||||
parsedAddr, err := caddy.ParseNetworkAddress(adminAddr)
|
||||
if err != nil || parsedAddr.PortRangeSize() > 1 {
|
||||
return fmt.Errorf("invalid admin address %s: %v", adminAddr, err)
|
||||
}
|
||||
origin := parsedAddr.JoinHostPort(0)
|
||||
if parsedAddr.IsUnixNetwork() {
|
||||
origin = "unixsocket" // hack so that http.NewRequest() is happy
|
||||
}
|
||||
|
||||
// form the request
|
||||
req, err := http.NewRequest(method, "http://"+origin+uri, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("making request: %v", err)
|
||||
}
|
||||
if parsedAddr.IsUnixNetwork() {
|
||||
// When listening on a unix socket, the admin endpoint doesn't
|
||||
// accept any Host header because there is no host:port for
|
||||
// a unix socket's address. The server's host check is fairly
|
||||
// strict for security reasons, so we don't allow just any
|
||||
// Host header. For unix sockets, the Host header must be
|
||||
// empty. Unfortunately, Go makes it impossible to make HTTP
|
||||
// requests with an empty Host header... except with this one
|
||||
// weird trick. (Hopefully they don't fix it. It's already
|
||||
// hard enough to use HTTP over unix sockets.)
|
||||
//
|
||||
// An equivalent curl command would be something like:
|
||||
// $ curl --unix-socket caddy.sock http:/:$REQUEST_URI
|
||||
req.URL.Host = " "
|
||||
req.Host = ""
|
||||
} else {
|
||||
req.Header.Set("Origin", origin)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// make an HTTP client that dials our network type, since admin
|
||||
// endpoints aren't always TCP, which is what the default transport
|
||||
// expects; reuse is not of particular concern here
|
||||
client := http.Client{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
|
||||
return net.Dial(parsedAddr.Network, parsedAddr.JoinHostPort(0))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("performing request: %v", err)
|
||||
}
|
||||
|
||||
+20
-8
@@ -27,7 +27,7 @@ type Command struct {
|
||||
// Required.
|
||||
Name string
|
||||
|
||||
// Run is a function that executes a subcommand using
|
||||
// Func is a function that executes a subcommand using
|
||||
// the parsed flags. It returns an exit code and any
|
||||
// associated error.
|
||||
// Required.
|
||||
@@ -74,7 +74,7 @@ func init() {
|
||||
RegisterCommand(Command{
|
||||
Name: "start",
|
||||
Func: cmdStart,
|
||||
Usage: "[--config <path> [[--adapter <name>]]",
|
||||
Usage: "[--config <path> [--adapter <name>]] [--watch] [--pidfile <file>]",
|
||||
Short: "Starts the Caddy process in the background and then returns",
|
||||
Long: `
|
||||
Starts the Caddy process, optionally bootstrapped with an initial config file.
|
||||
@@ -87,6 +87,8 @@ using 'caddy run' instead to keep it in the foreground.`,
|
||||
fs := flag.NewFlagSet("start", flag.ExitOnError)
|
||||
fs.String("config", "", "Configuration file")
|
||||
fs.String("adapter", "", "Name of config adapter to apply")
|
||||
fs.String("pidfile", "", "Path of file to which to write process ID")
|
||||
fs.Bool("watch", false, "Reload changed config file automatically")
|
||||
return fs
|
||||
}(),
|
||||
})
|
||||
@@ -94,7 +96,7 @@ using 'caddy run' instead to keep it in the foreground.`,
|
||||
RegisterCommand(Command{
|
||||
Name: "run",
|
||||
Func: cmdRun,
|
||||
Usage: "[--config <path> [--adapter <name>]] [--environ]",
|
||||
Usage: "[--config <path> [--adapter <name>]] [--envfile <path>] [--environ] [--resume] [--watch] [--pidfile <fil>]",
|
||||
Short: `Starts the Caddy process and blocks indefinitely`,
|
||||
Long: `
|
||||
Starts the Caddy process, optionally bootstrapped with an initial config file,
|
||||
@@ -114,18 +116,28 @@ As a special case, if the current working directory has a file called
|
||||
that file will be loaded and used to configure Caddy, even without any command
|
||||
line flags.
|
||||
|
||||
If --envfile is specified, an environment file with environment variables in
|
||||
the KEY=VALUE format will be loaded into the Caddy process.
|
||||
|
||||
If --environ is specified, the environment as seen by the Caddy process will
|
||||
be printed before starting. This is the same as the environ command but does
|
||||
not quit after printing, and can be useful for troubleshooting.
|
||||
|
||||
The --resume flag will override the --config flag if there is a config auto-
|
||||
save file. It is not an error if --resume is used and no autosave file exists.`,
|
||||
save file. It is not an error if --resume is used and no autosave file exists.
|
||||
|
||||
If --watch is specified, the config file will be loaded automatically after
|
||||
changes. ⚠️ This is dangerous in production! Only use this option in a local
|
||||
development environment.`,
|
||||
Flags: func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("run", flag.ExitOnError)
|
||||
fs.String("config", "", "Configuration file")
|
||||
fs.String("adapter", "", "Name of config adapter to apply")
|
||||
fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)")
|
||||
fs.String("envfile", "", "Environment file to load")
|
||||
fs.Bool("environ", false, "Print environment")
|
||||
fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)")
|
||||
fs.Bool("watch", false, "Watch config file for changes and reload it automatically")
|
||||
fs.String("pidfile", "", "Path of file to which to write process ID")
|
||||
fs.String("pingback", "", "Echo confirmation bytes to this address on success")
|
||||
return fs
|
||||
}(),
|
||||
@@ -244,8 +256,8 @@ provisioning stages.`,
|
||||
|
||||
RegisterCommand(Command{
|
||||
Name: "fmt",
|
||||
Func: cmdFormatConfig,
|
||||
Usage: "[--write] [<path>]",
|
||||
Func: cmdFmt,
|
||||
Usage: "[--overwrite] [<path>]",
|
||||
Short: "Formats a Caddyfile",
|
||||
Long: `
|
||||
Formats the Caddyfile by adding proper indentation and spaces to improve
|
||||
@@ -255,7 +267,7 @@ If --write is specified, the output will be written to the config file
|
||||
directly instead of printing it.`,
|
||||
Flags: func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("format", flag.ExitOnError)
|
||||
fs.Bool("write", false, "Over-write the output to specified file")
|
||||
fs.Bool("overwrite", false, "Overwrite the input file with the results")
|
||||
return fs
|
||||
}(),
|
||||
})
|
||||
|
||||
+175
-10
@@ -15,29 +15,42 @@
|
||||
package caddycmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// set a fitting User-Agent for ACME requests
|
||||
goModule := caddy.GoModule()
|
||||
cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
|
||||
certmagic.UserAgent = "Caddy/" + cleanModVersion
|
||||
|
||||
// by using Caddy, user indicates agreement to CA terms
|
||||
// (very important, or ACME account creation will fail!)
|
||||
certmagic.DefaultACME.Agreed = true
|
||||
}
|
||||
|
||||
// Main implements the main function of the caddy command.
|
||||
// Call this if Caddy is to be the main() if your program.
|
||||
func Main() {
|
||||
caddy.TrapSignals()
|
||||
|
||||
switch len(os.Args) {
|
||||
case 0:
|
||||
fmt.Printf("[FATAL] no arguments provided by OS; args[0] must be command\n")
|
||||
@@ -99,10 +112,10 @@ func handlePingbackConn(conn net.Conn, expect []byte) error {
|
||||
// there is no config available. It prints any warnings to stderr,
|
||||
// and returns the resulting JSON config bytes along with
|
||||
// whether a config file was loaded or not.
|
||||
func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
func loadConfig(configFile, adapterName string) ([]byte, string, error) {
|
||||
// specifying an adapter without a config file is ambiguous
|
||||
if adapterName != "" && configFile == "" {
|
||||
return nil, false, fmt.Errorf("cannot adapt config without config file (use --config)")
|
||||
return nil, "", fmt.Errorf("cannot adapt config without config file (use --config)")
|
||||
}
|
||||
|
||||
// load initial config and adapter
|
||||
@@ -112,7 +125,7 @@ func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
if configFile != "" {
|
||||
config, err = ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("reading config file: %v", err)
|
||||
return nil, "", fmt.Errorf("reading config file: %v", err)
|
||||
}
|
||||
caddy.Log().Info("using provided configuration",
|
||||
zap.String("config_file", configFile),
|
||||
@@ -129,7 +142,7 @@ func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
cfgAdapter = nil
|
||||
} else if err != nil {
|
||||
// default Caddyfile exists, but error reading it
|
||||
return nil, false, fmt.Errorf("reading default Caddyfile: %v", err)
|
||||
return nil, "", fmt.Errorf("reading default Caddyfile: %v", err)
|
||||
} else {
|
||||
// success reading default Caddyfile
|
||||
configFile = "Caddyfile"
|
||||
@@ -151,7 +164,7 @@ func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
if adapterName != "" {
|
||||
cfgAdapter = caddyconfig.GetAdapter(adapterName)
|
||||
if cfgAdapter == nil {
|
||||
return nil, false, fmt.Errorf("unrecognized config adapter: %s", adapterName)
|
||||
return nil, "", fmt.Errorf("unrecognized config adapter: %s", adapterName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +174,7 @@ func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
"filename": configFile,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("adapting config using %s: %v", adapterName, err)
|
||||
return nil, "", fmt.Errorf("adapting config using %s: %v", adapterName, err)
|
||||
}
|
||||
for _, warn := range warnings {
|
||||
msg := warn.Message
|
||||
@@ -173,7 +186,91 @@ func loadConfig(configFile, adapterName string) ([]byte, bool, error) {
|
||||
config = adaptedConfig
|
||||
}
|
||||
|
||||
return config, configFile != "", nil
|
||||
return config, configFile, nil
|
||||
}
|
||||
|
||||
// watchConfigFile watches the config file at filename for changes
|
||||
// and reloads the config if the file was updated. This function
|
||||
// blocks indefinitely; it only quits if the poller has errors for
|
||||
// long enough time. The filename passed in must be the actual
|
||||
// config file used, not one to be discovered.
|
||||
func watchConfigFile(filename, adapterName string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Printf("[PANIC] watching config file: %v\n%s", err, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
// make our logger; since config reloads can change the
|
||||
// default logger, we need to get it dynamically each time
|
||||
logger := func() *zap.Logger {
|
||||
return caddy.Log().
|
||||
Named("watcher").
|
||||
With(zap.String("config_file", filename))
|
||||
}
|
||||
|
||||
// get the initial timestamp on the config file
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
logger().Error("cannot watch config file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
lastModified := info.ModTime()
|
||||
|
||||
logger().Info("watching config file for changes")
|
||||
|
||||
// if the file disappears or something, we can
|
||||
// stop polling if the error lasts long enough
|
||||
var lastErr time.Time
|
||||
finalError := func(err error) bool {
|
||||
if lastErr.IsZero() {
|
||||
lastErr = time.Now()
|
||||
return false
|
||||
}
|
||||
if time.Since(lastErr) > 30*time.Second {
|
||||
logger().Error("giving up watching config file; too many errors",
|
||||
zap.Error(err))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// begin poller
|
||||
for range time.Tick(1 * time.Second) {
|
||||
// get the file info
|
||||
info, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
if finalError(err) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
lastErr = time.Time{} // no error, so clear any memory of one
|
||||
|
||||
// if it hasn't changed, nothing to do
|
||||
if !info.ModTime().After(lastModified) {
|
||||
continue
|
||||
}
|
||||
|
||||
logger().Info("config file changed; reloading")
|
||||
|
||||
// remember this timestamp
|
||||
lastModified = info.ModTime()
|
||||
|
||||
// load the contents of the file
|
||||
config, _, err := loadConfig(filename, adapterName)
|
||||
if err != nil {
|
||||
logger().Error("unable to load latest config", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// apply the updated config
|
||||
err = caddy.Load(config, false)
|
||||
if err != nil {
|
||||
logger().Error("applying latest config", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flags wraps a FlagSet so that typed values
|
||||
@@ -221,7 +318,7 @@ func (f Flags) Float64(name string) float64 {
|
||||
// is not a duration type. It panics if the flag is
|
||||
// not in the flag set.
|
||||
func (f Flags) Duration(name string) time.Duration {
|
||||
val, _ := time.ParseDuration(f.String(name))
|
||||
val, _ := caddy.ParseDuration(f.String(name))
|
||||
return val
|
||||
}
|
||||
|
||||
@@ -241,11 +338,79 @@ func flagHelp(fs *flag.FlagSet) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func loadEnvFromFile(envFile string) error {
|
||||
file, err := os.Open(envFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading environment file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
envMap, err := parseEnvFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing environment file: %v", err)
|
||||
}
|
||||
|
||||
for k, v := range envMap {
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return fmt.Errorf("setting environment variables: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEnvFile(envInput io.Reader) (map[string]string, error) {
|
||||
envMap := make(map[string]string)
|
||||
|
||||
scanner := bufio.NewScanner(envInput)
|
||||
var line string
|
||||
lineNumber := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
line = strings.TrimSpace(scanner.Text())
|
||||
lineNumber++
|
||||
|
||||
// skip lines starting with comment
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip empty line
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.SplitN(line, "=", 2)
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("can't parse line %d; line should be in KEY=VALUE format", lineNumber)
|
||||
}
|
||||
|
||||
if strings.Contains(fields[0], " ") {
|
||||
return nil, fmt.Errorf("bad key on line %d: contains whitespace", lineNumber)
|
||||
}
|
||||
|
||||
key := fields[0]
|
||||
val := fields[1]
|
||||
|
||||
if key == "" {
|
||||
return nil, fmt.Errorf("missing or empty key on line %d", lineNumber)
|
||||
}
|
||||
envMap[key] = val
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return envMap, nil
|
||||
}
|
||||
|
||||
func printEnvironment() {
|
||||
fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir())
|
||||
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
|
||||
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
|
||||
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
|
||||
fmt.Printf("caddy.Version=%s\n", caddy.GoModule().Version)
|
||||
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
|
||||
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
|
||||
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
|
||||
|
||||
+30
-3
@@ -92,6 +92,7 @@ func (ctx *Context) OnCancel(f func()) {
|
||||
//
|
||||
// json.RawMessage => interface{}
|
||||
// []json.RawMessage => []interface{}
|
||||
// [][]json.RawMessage => [][]interface{}
|
||||
// map[string]json.RawMessage => map[string]interface{}
|
||||
// []map[string]json.RawMessage => []map[string]interface{}
|
||||
//
|
||||
@@ -179,6 +180,27 @@ func (ctx Context) LoadModule(structPointer interface{}, fieldName string) (inte
|
||||
}
|
||||
result = all
|
||||
|
||||
} else if typ.Elem().Kind() == reflect.Slice && isJSONRawMessage(typ.Elem().Elem()) {
|
||||
// val is `[][]json.RawMessage`
|
||||
|
||||
if inlineModuleKey == "" {
|
||||
panic("unable to determine module name without inline_key because type is not a ModuleMap")
|
||||
}
|
||||
var all [][]interface{}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
innerVal := val.Index(i)
|
||||
var allInner []interface{}
|
||||
for j := 0; j < innerVal.Len(); j++ {
|
||||
innerInnerVal, err := ctx.loadModuleInline(inlineModuleKey, moduleNamespace, innerVal.Index(j).Interface().(json.RawMessage))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("position %d: %v", j, err)
|
||||
}
|
||||
allInner = append(allInner, innerInnerVal)
|
||||
}
|
||||
all = append(all, allInner)
|
||||
}
|
||||
result = all
|
||||
|
||||
} else if isModuleMapType(typ.Elem()) {
|
||||
// val is `[]map[string]json.RawMessage`
|
||||
|
||||
@@ -377,9 +399,14 @@ func (ctx Context) loadModuleInline(moduleNameKey, moduleScope string, raw json.
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// App returns the configured app named name. If no app with
|
||||
// that name is currently configured, a new empty one will be
|
||||
// instantiated. (The app module must still be registered.)
|
||||
// App returns the configured app named name. If that app has
|
||||
// not yet been loaded and provisioned, it will be immediately
|
||||
// loaded and provisioned. If no app with that name is
|
||||
// configured, a new empty one will be instantiated instead.
|
||||
// (The app module must still be registered.) This must not be
|
||||
// called during the Provision/Validate phase to reference a
|
||||
// module's own host app (since the parent app module is still
|
||||
// in the process of being provisioned, it is not yet ready).
|
||||
func (ctx Context) App(name string) (interface{}, error) {
|
||||
if app, ok := ctx.cfg.apps[name]; ok {
|
||||
return app, nil
|
||||
|
||||
@@ -3,33 +3,31 @@ module github.com/caddyserver/caddy/v2
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Masterminds/sprig/v3 v3.0.2
|
||||
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a
|
||||
github.com/andybalholm/brotli v1.0.0
|
||||
github.com/caddyserver/certmagic v0.10.2
|
||||
github.com/Masterminds/sprig/v3 v3.1.0
|
||||
github.com/alecthomas/chroma v0.8.0
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a
|
||||
github.com/caddyserver/certmagic v0.11.3-0.20200810220624-10a8b5c72339
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac
|
||||
github.com/go-acme/lego/v3 v3.4.0
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
|
||||
github.com/ilibs/json5 v1.0.1
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/google/cel-go v0.5.1
|
||||
github.com/jsternberg/zap-logfmt v1.2.0
|
||||
github.com/klauspost/compress v1.10.3
|
||||
github.com/klauspost/cpuid v1.2.3
|
||||
github.com/lucas-clemente/quic-go v0.15.2
|
||||
github.com/manifoldco/promptui v0.7.0 // indirect
|
||||
github.com/miekg/dns v1.1.28 // indirect
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20200303171503-1e787b591db7
|
||||
github.com/klauspost/compress v1.10.10
|
||||
github.com/klauspost/cpuid v1.2.5
|
||||
github.com/lucas-clemente/quic-go v0.17.3
|
||||
github.com/mholt/acmez v0.1.1-0.20200810215816-dbe88fc6cf09
|
||||
github.com/naoina/go-stringutil v0.1.0 // indirect
|
||||
github.com/naoina/toml v0.1.1
|
||||
github.com/smallstep/certificates v0.14.0-rc.5
|
||||
github.com/smallstep/cli v0.14.0-rc.3
|
||||
github.com/smallstep/truststore v0.9.4
|
||||
github.com/vulcand/oxy v1.0.0
|
||||
github.com/yuin/goldmark v1.1.25
|
||||
github.com/smallstep/certificates v0.14.6
|
||||
github.com/smallstep/cli v0.14.6
|
||||
github.com/smallstep/nosql v0.3.0
|
||||
github.com/smallstep/truststore v0.9.6
|
||||
github.com/yuin/goldmark v1.2.1
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||
go.uber.org/zap v1.14.0
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
||||
go.uber.org/zap v1.15.0
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98
|
||||
google.golang.org/protobuf v1.24.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
||||
@@ -7,14 +7,12 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
|
||||
cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
|
||||
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
|
||||
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||
@@ -25,22 +23,11 @@ dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D
|
||||
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
|
||||
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
|
||||
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
|
||||
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
|
||||
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
|
||||
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
|
||||
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
|
||||
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
|
||||
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
|
||||
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
|
||||
@@ -48,36 +35,31 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVGT+JqLk=
|
||||
github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14=
|
||||
github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.0.0 h1:KSQz7Nb08/3VU9E4ns29dDxcczhOD1q7O1UfM4G3t3g=
|
||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U=
|
||||
github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8=
|
||||
github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y=
|
||||
github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA=
|
||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/ThomasRooney/gexpect v0.0.0-20161231170123-5482f0350944/go.mod h1:sPML5WwI6oxLRLPuuqbtoOKhtmpVDCYtwsps+I+vjIY=
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang v0.9.0/go.mod h1:zpDJeKyp9ScW4NNrbdr+Eyxvry3ilGPewKoXw3XGN1k=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75 h1:3ILjVyslFbc4jl1w5TWuvvslFD/nDfR2H8tVaMVLrEY=
|
||||
github.com/alangpierce/go-forceexport v0.0.0-20160317203124-8f1d6941cd75/go.mod h1:uAXEEpARkRhCZfEvy/y0Jcc888f9tHCc1W7/UeEtreE=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a h1:3v1NrYWWqp2S72e4HLgxKt83B3l0lnORDholH/ihoMM=
|
||||
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
|
||||
github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY=
|
||||
github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
@@ -85,21 +67,17 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee h1:NYqDBPkhVYt68W3yoGoRRi32i3MLx2ey7SFkJ1v/UI0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190808125512-07798873deee/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f h1:0cEys61Sr2hUBEXfNV8eyQP01oZuBgoMeHunebPirK8=
|
||||
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180315120708-ccb8e960c48f/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
|
||||
github.com/aws/aws-sdk-go v1.30.29 h1:NXNqBS9hjOCpDL8SyCyl38gZX3LLLunKOJc5E7vJ8P0=
|
||||
github.com/aws/aws-sdk-go v1.30.29/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
@@ -108,12 +86,11 @@ github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTK
|
||||
github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/caddyserver/certmagic v0.10.2 h1:uz+QTSZULEN9iW2woZYf01ipR2O88iLx/bkIAoEXYbo=
|
||||
github.com/caddyserver/certmagic v0.10.2/go.mod h1:Y8jcUBctgk/IhpAzlHKfimZNyXCkfGgRTC0orl8gROQ=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0 h1:6VeaLF9aI+MAUQ95106HwWzYZgJJpZ4stumjj6RFYAU=
|
||||
github.com/cenkalti/backoff/v4 v4.0.0/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
|
||||
github.com/caddyserver/certmagic v0.11.3-0.20200810220624-10a8b5c72339 h1:wTD+Y63XoBtiTJhe/Xn7WLrwKenmjkt2WxH3FP+Y0DM=
|
||||
github.com/caddyserver/certmagic v0.11.3-0.20200810220624-10a8b5c72339/go.mod h1:mqOzOvKa7UcC+TWbBLcP0ZLRut/xaaQBw0hRGWHBIkY=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
|
||||
@@ -125,10 +102,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2 h1:VBodKICVPnwmDxstcW3biKcDSpFIfS/RELUXsZSBYK4=
|
||||
github.com/cloudflare/cloudflare-go v0.10.2/go.mod h1:qhVI5MKwBGhdNU89ZRz2plgYutcJ5PCekLxXn56w6SY=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
@@ -141,12 +114,9 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
|
||||
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/corpix/uarand v0.0.0-20170903190822-2b8494104d86/go.mod h1:JSm890tOkDN+M1jqN8pUGDKnzJrsVbJwSMHBY4zwz7M=
|
||||
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
|
||||
github.com/cpu/goacmedns v0.0.1/go.mod h1:sesf/pNnCYwUevQEQfEwY0Y3DydlQWSGZbaMElOWxok=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
@@ -159,31 +129,25 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.5.3 h1:5oWIuRvwn93cie+OSt1zSnkaIQ1JFQM8bGlIv6O6Sts=
|
||||
github.com/dgraph-io/badger v1.5.3/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
|
||||
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200413122845-09dd2e1a4195 h1:n8KbImHW5qZCXv1y3tHjz5yz418/eTxeRJZ2ZuDm1ZU=
|
||||
github.com/dgraph-io/badger/v2 v2.0.1-rc1.0.20200413122845-09dd2e1a4195/go.mod h1:3KY8+bsP8wI0OEnQJAKpd4wIJW/Mm32yw2j/9FUVnIM=
|
||||
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3 h1:MQLRM35Pp0yAyBYksjbj1nZI/w6eyRY/mWoM1sFf4kU=
|
||||
github.com/dgraph-io/ristretto v0.0.2-0.20200115201040-8f368f2f2ab3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
|
||||
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
|
||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dnaeon/go-vcr v0.0.0-20180814043457-aafff18a5cc2/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||
github.com/dnsimple/dnsimple-go v0.30.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac h1:opbrjaN/L8gg6Xh5D04Tem+8xVcz6ajZlGCs49mQgyg=
|
||||
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/exoscale/egoscale v0.18.1/go.mod h1:Z7OOdzzTOz1Q1PjQXumlz9Wn/CddH0zSYdCF3rnBKXE=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||
@@ -191,24 +155,23 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-acme/lego/v3 v3.4.0 h1:deB9NkelA+TfjGHVw8J7iKl/rMtffcGMWSMmptvMv0A=
|
||||
github.com/go-acme/lego/v3 v3.4.0/go.mod h1:xYbLDuxq3Hy4bMUT1t9JIuz6GWIWb3m5X+TeTHYaT7M=
|
||||
github.com/go-chi/chi v3.3.4-0.20181024101233-0ebf7795c516+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-critic/go-critic v0.4.0/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ini/ini v1.44.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-piv/piv-go v1.5.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
@@ -225,34 +188,37 @@ github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslW
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1 h1:ZFgWrT+bLgsYPirOnRfKLYJLvssAegOj/hgyMFdJZe0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||
@@ -277,10 +243,11 @@ github.com/golangci/revgrep v0.0.0-20180812185044-276a5c0a1039/go.mod h1:qOQCunE
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/cel-go v0.5.1 h1:oDsbtAwlwFPEcC8dMoRWNuVzWJUDeDZeHjoet9rXjTs=
|
||||
github.com/google/cel-go v0.5.1/go.mod h1:9SvtVVTtZV4DTB1/RuAD1D2HhuqEIdmZEE/r/lrFyKE=
|
||||
github.com/google/cel-spec v0.4.0/go.mod h1:2pBM5cU4UKjbPDXBgwWkiwBsVgnxknuEJ7C5TDWwORQ=
|
||||
github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36jiHGfuSK1fHICIg6sUkRxPAbCs=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
@@ -306,56 +273,45 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gophercloud/gophercloud v0.3.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/gravitational/trace v0.0.0-20190726142706-a535a178675f/go.mod h1:RvdOUHE4SHqR3oXlFFKnGzms8a5dugHygGw1bqDstYI=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0=
|
||||
github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
|
||||
github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
|
||||
github.com/ilibs/json5 v1.0.1 h1:3e14wUQM8PyK6Hf1bM+zAQFxfG+N5oZj35x5vCNeQ58=
|
||||
github.com/ilibs/json5 v1.0.1/go.mod h1:kXsGuzHMPuZZTN15l0IQzy5PR8DrDhPB24tFgwpdKME=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
@@ -373,14 +329,12 @@ github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7IL
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
|
||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
|
||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.3 h1:CCtW0xUnWGVINKvE/WWOYKdsPV6mawAtvQuSl8guwQs=
|
||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kolo/xmlrpc v0.0.0-20190717152603-07c4ee3fd181/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/klauspost/cpuid v1.2.5 h1:VBd9MyVIiJHzzgnrLQG5Bcv75H4YaWrlKqWHjurxCGo=
|
||||
github.com/klauspost/cpuid v1.2.5/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -394,60 +348,47 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
|
||||
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
|
||||
github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/linode/linodego v0.10.0/go.mod h1:cziNP7pbvE3mXIPneHj0oRY8L1WtGEIKlZ8LANE4eXA=
|
||||
github.com/liquidweb/liquidweb-go v1.6.0/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821 h1:663opx/RKxiISi1ozf0WbvweQpYBgf34dx8hKSIau3w=
|
||||
github.com/libdns/libdns v0.0.0-20200501023120-186724ffc821/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucas-clemente/quic-go v0.15.2 h1:RgxRJ7rPde0Q/uXDeb3/UdblVvxrYGDAG9G9GO78LmI=
|
||||
github.com/lucas-clemente/quic-go v0.15.2/go.mod h1:qxmO5Y4ZMhdNkunGfxuZnZXnJwYpW9vjQkyrZ7BsgUI=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lucas-clemente/quic-go v0.17.3 h1:jMX/MmDNCljfisgMmPGUcBJ+zUh9w3d3ia4YJjYS3TM=
|
||||
github.com/lucas-clemente/quic-go v0.17.3/go.mod h1:I0+fcNTdb9eS1ZcjQZbDVPGchJ86chcIxPALn9lEJqE=
|
||||
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8=
|
||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f/go.mod h1:V3EvCedtJTvUYzJF2GZMRB0JMlai+6cBu3VCTQz33GQ=
|
||||
github.com/mailgun/multibuf v0.0.0-20150714184110-565402cd71fb/go.mod h1:E0vRBBIQUHcRtmL/oR6w/jehh4FJqJFxe86gBnw9gXc=
|
||||
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ=
|
||||
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51/go.mod h1:RYmqHbhWwIz3z9eVmQ2rx82rulEMG0t+Q1bzfc9DYN4=
|
||||
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f/go.mod h1:8heskWJ5c0v5J9WH89ADhyal1DOZcayll8fSbhB+/9A=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/manifoldco/promptui v0.3.1 h1:BxqNa7q1hVHXIXy3iupJMkXYS3aHhbubJWv2Jmg6x64=
|
||||
github.com/manifoldco/promptui v0.3.1/go.mod h1:zoCNXiJnyM03LlBgTsWv8mq28s7aTC71UgKasqRJHww=
|
||||
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
|
||||
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/marten-seemann/qpack v0.1.0 h1:/0M7lkda/6mus9B8u34Asqm8ZhHAAt9Ho0vniNuVSVg=
|
||||
github.com/marten-seemann/qpack v0.1.0/go.mod h1:LFt1NU/Ptjip0C2CPkhimBz5CGE3WGDAUWqna+CNTrI=
|
||||
github.com/marten-seemann/qtls v0.8.0 h1:aj+MPLibzKByw8CmG0WvWgbtBkctYPAXeB11cQJC8mo=
|
||||
github.com/marten-seemann/qtls v0.8.0/go.mod h1:Lao6jDqlCfxyLKYFmZXGm2LSHBgVn+P+ROOex6YkT+k=
|
||||
github.com/marten-seemann/qtls v0.9.1 h1:O0YKQxNVPaiFgMng0suWEOY2Sb4LT2sRn9Qimq3Z1IQ=
|
||||
github.com/marten-seemann/qtls v0.9.1/go.mod h1:T1MmAdDPyISzxlK6kjRr0pcZFBVd1OZbBb/j3cvzHhk=
|
||||
github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/acmez v0.1.1-0.20200810215816-dbe88fc6cf09 h1:J7NVJ46iBFeWsUc5aeVv8QNO2mLhI6rJKIbpAsH7d7g=
|
||||
github.com/mholt/acmez v0.1.1-0.20200810215816-dbe88fc6cf09/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.28 h1:gQhy5bsJa8zTlVI8lywCTZp1lguor+xevFoYlzeCTQY=
|
||||
github.com/miekg/dns v1.1.28/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/dns v1.1.30 h1:Qww6FseFn8PRfw07jueqIXqodm0JKiiKuK0DeXSqfyo=
|
||||
github.com/miekg/dns v1.1.30/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
@@ -455,122 +396,97 @@ github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20200303171503-1e787b591db7 h1:IhnWtz7gaLvL0wn8xr86ERSvSmwNAsaHMRAqgsu/CVQ=
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20200303171503-1e787b591db7/go.mod h1:saF2fIVw4banK0H4+/EuqfFLpRnoy5S+ECwTOCcRcSU=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8=
|
||||
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
|
||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/newrelic/go-agent v1.11.0/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
|
||||
github.com/newrelic/go-agent v2.15.0+incompatible/go.mod h1:a8Fv1b/fYhFSReoTU6HDkTYIMZeSVNffmoS726Y0LzQ=
|
||||
github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/nrdcg/auroradns v1.0.0/go.mod h1:6JPXKzIRzZzMqtTDgueIhTi6rFf1QvYE/HzqidhOhjw=
|
||||
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
|
||||
github.com/nrdcg/goinwx v0.6.1/go.mod h1:XPiut7enlbEdntAqalBIqcYcTEVhpv/dKWgDCX2SwKQ=
|
||||
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw=
|
||||
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
|
||||
github.com/ovh/go-ovh v0.0.0-20181109152953-ba5adb4cf014/go.mod h1:joRatxRJaZBsY3JAOEMcoOp05CnZzsx4scTxi95DHyQ=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.0.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk=
|
||||
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sacloud/libsacloud v1.26.1/go.mod h1:79ZwATmHLIFZIMd7sxA3LwzVy/B77uj3LDoToVTxDoQ=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 h1:CmSpbxmewNQbzqztaY0bke1qzHhyNyC29wYgh17Gxfo=
|
||||
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189/go.mod h1:UUwuHEJ9zkkPDxspIHOa59PUeSkGFljESGzbxntLmIg=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
|
||||
github.com/securego/gosec v0.0.0-20200106085552-9cb83e10afad/go.mod h1:7fJLcv5NlMd4t9waQEDLgpZeE3nv4D5DMz5JuZZGufg=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
@@ -603,70 +519,41 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smallstep/assert v0.0.0-20180720014142-de77670473b5/go.mod h1:TC9A4+RjIOS+HyTH7wG17/gSqVv95uDw2J64dQZx7RE=
|
||||
github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15 h1:kSImCuenAkXtCaBeQ1UhmzzJGRhSm8sVH7I3sHE2Qdg=
|
||||
github.com/smallstep/assert v0.0.0-20200103212524-b99dc1097b15/go.mod h1:MyOHs9Po2fbM1LHej6sBUT8ozbxmMOFG+E+rx/GSGuc=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191023014154-4669bef8c700/go.mod h1:/WOAB2LkcjkEbKG5rDol+A22Lp3UsttkLPLkY7tVtuk=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191025192352-8ef9b020ed24/go.mod h1:043iBnsMvNhQ+QFwSh0N6JR3H2yamHPPAc78vCf+8Tc=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191126035953-e88034bea402/go.mod h1:r2UTcAZNriKlwvNNXymNAcF3iKL6mTYOYrOCtBYYGJU=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191210005525-50152391a397/go.mod h1:8leACUXHFo0JVm9YcrcX09aar2H8hz1BAWxD1D/GpsU=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191213215656-d2100821138c/go.mod h1:HMXt9hWBm7M7ZrUy0uZ/T/077te2x9bnXZCxrdVsBf4=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191217235337-aa5894058226/go.mod h1:MTKifeJBe1B/dzH5NDoPFpIPaWD0MzRozzONVkF8egc=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20191218224459-1fa35491ea07/go.mod h1:eEtpedAL4inqaAx6ZqJnE4NOx1/GxDh6VQOmbs7CPf0=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20200110185849-085ae821636e/go.mod h1:weY9Os8g0yPfyxd+Zy1CTAwCb7YMqg/u5XnEagBN5Rk=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20200111012147-3ce267cdd6b7/go.mod h1:jljUh6mTvHOAqvIUvbD2L3Q/aqSpTI6HzJiNFQkj1Hc=
|
||||
github.com/smallstep/certificates v0.14.0-rc.1.0.20200128212940-432ed0090f3d/go.mod h1:lWKe0ZOg45lNWtByxh82fOfzXwx93S0TeWzTCOjc19k=
|
||||
github.com/smallstep/certificates v0.14.0-rc.2.0.20200129195847-7846696fbb69/go.mod h1:vZjJp4hweYVx+rBouWEVOf3KlH2Yilxo/50dsj7y8aY=
|
||||
github.com/smallstep/certificates v0.14.0-rc.5 h1:AifQ0Agi3FPvcxi+AUkxzjfmj3jGQOd2jKSzpcyhSd0=
|
||||
github.com/smallstep/certificates v0.14.0-rc.5/go.mod h1:ii3ce5KzeY72D4+f/HBz2rXjgQ9dfpkN8KQRiIxImVM=
|
||||
github.com/smallstep/certinfo v0.0.0-20191008000228-b0e530932339/go.mod h1:n4YHPL9hJIyB+N4F2rPBy3mpPxMxTGJP5Pdsyaoc2Ns=
|
||||
github.com/smallstep/certinfo v0.0.0-20191029235839-00563809d483/go.mod h1:xmx5n8+7jI0lrjTUwc8WMMqXeOHRyxYUW9U1wrvP3Vo=
|
||||
github.com/smallstep/certinfo v1.0.0/go.mod h1:xmx5n8+7jI0lrjTUwc8WMMqXeOHRyxYUW9U1wrvP3Vo=
|
||||
github.com/smallstep/certinfo v1.1.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU=
|
||||
github.com/smallstep/cli v0.12.1-0.20191016010425-15911d8625df/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
|
||||
github.com/smallstep/cli v0.13.3 h1:S29UydCtDVy0QQBtGdatq064tnks1/0DYxxnEtNiQpc=
|
||||
github.com/smallstep/cli v0.13.3/go.mod h1:zGPm8vWCqzvDqkdC1laFJNdIOjNSB8V4qDp68Ny538o=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191024214139-914a67ed80c2/go.mod h1:GoA1cE4YrZRRvVbFlPKJUsMuWHnFBX+R88j1pmpbGgk=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191105013638-8cf838b56d03/go.mod h1:dklnISxr+GzUmurBngEF9Jvj0aI9KK5uVgZwOdFniNs=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191127025104-2821b0b811c1/go.mod h1:F6/cZ7VguiUV4nsoqPdDyZtGOgg3oLHz+LstEQsiSAg=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191211225301-a5e848783407/go.mod h1:1DDxP5W6pSuPL7DudNMbr/qVVjToo8qz3tlRt8ka8TA=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191217223638-5ee30a55af45/go.mod h1:6pTiWJKfIQcUYtK7lVnI0pOXRiYAWuy0qrlFVnn9q8M=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20191218000521-3e7348324838/go.mod h1:JPG34JrC37Pw0HjoB+cAtXT1yFOXfab/5nrM7ZTSw8c=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20200110185014-8a0d0cd3202e/go.mod h1:MA99N6UETSrq7/Pk/iZcgHqqiIU3tDscFNx2pGcdLlU=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20200111011727-83a91ec8e405/go.mod h1:MCvJvfMNtWCi/VBfXxP1JONqLLfF9TcBj1/t5Rqme90=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20200127233252-e55637e57819/go.mod h1:SUBVVdOk5XI7yllSupRYHzN5y4MBo89X27CN4P0d+Jw=
|
||||
github.com/smallstep/cli v0.14.0-rc.1.0.20200128213701-65805ae554f6/go.mod h1:50kmsPMAiR9XD0jHZYY19fkSSD3mKF9ztQjgtTLefjU=
|
||||
github.com/smallstep/cli v0.14.0-rc.3 h1:IphfrTtJHR2tpFiiBUKfPpO7SCGvfca72PbYJq8k1kU=
|
||||
github.com/smallstep/cli v0.14.0-rc.3/go.mod h1:5kg85FrLTaQE0JgV3IZAxuVRS7G5qvV0hxOh0u/H6IE=
|
||||
github.com/smallstep/nosql v0.1.1-0.20191009043502-4b26d8029e61/go.mod h1:MFhYHIE/0V7OOHjYzjnWHqySJ40PVbwhjy24UBkJI2g=
|
||||
github.com/smallstep/nosql v0.1.1/go.mod h1:qyxCqeyGwkuM6bfJSY3sg+aiXEiD0GbQOPzIF8/ZD8Q=
|
||||
github.com/smallstep/nosql v0.2.0 h1:IscXK9m9hRyl5GoYgn+Iml//5Bpad3LyIj6R0dZosKM=
|
||||
github.com/smallstep/nosql v0.2.0/go.mod h1:qyxCqeyGwkuM6bfJSY3sg+aiXEiD0GbQOPzIF8/ZD8Q=
|
||||
github.com/smallstep/certificates v0.14.5/go.mod h1:zzpB8wMz967gL8FmK6zvCNB4pDVwFDKjPg1diTVc1h8=
|
||||
github.com/smallstep/certificates v0.14.6 h1:sLVnIvIjZhd6qAosuf4Mw1Mh7IMA9XrZ7lKrGBuoHZQ=
|
||||
github.com/smallstep/certificates v0.14.6/go.mod h1:nhih0lhKpbWE8JB0fg4EVCh/po18ivLiTPcF8eBeNtA=
|
||||
github.com/smallstep/certinfo v1.3.0/go.mod h1:1gQJekdPwPvUwFWGTi7bZELmQT09cxC9wJ0VBkBNiwU=
|
||||
github.com/smallstep/cli v0.14.5/go.mod h1:mRFuqC3cGwQESBGJvog4o76jZZZ7bMjkE+hAnq2QyR8=
|
||||
github.com/smallstep/cli v0.14.6 h1:xc9rawDKB70Vgvg10gfQAh9EpDWS3k1O002J5bApqUk=
|
||||
github.com/smallstep/cli v0.14.6/go.mod h1:Gs9mXwk5tIBlSISt1tvCAMso7eEAiJRyNgR/6JsoojI=
|
||||
github.com/smallstep/nosql v0.3.0 h1:V1X5vfDsDt89499h3jZFUlR4VnnsYYs5tXaQZ0w8z5U=
|
||||
github.com/smallstep/nosql v0.3.0/go.mod h1:QG7gNOpidifn99MjZaiNbm7HPesIyBd97F/OfacNz8Q=
|
||||
github.com/smallstep/truststore v0.9.3/go.mod h1:PRSkpRIhAYBK/KLWkHNgRdYgzWMEy45bN7PSJCfKKGE=
|
||||
github.com/smallstep/truststore v0.9.4 h1:HYGtv629TkxRUP63W5R1/9sJGMEL8CrVjIl8z1uvJIM=
|
||||
github.com/smallstep/truststore v0.9.4/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
||||
github.com/smallstep/zcrypto v0.0.0-20191008000232-9fc4bea33f70/go.mod h1:8LA6x9T22WADMj89Ksf6DnOVCOJF3zLKUdSRAcZmW4U=
|
||||
github.com/smallstep/zcrypto v0.0.0-20191122194514-76530dff70e7/go.mod h1:8LA6x9T22WADMj89Ksf6DnOVCOJF3zLKUdSRAcZmW4U=
|
||||
github.com/smallstep/truststore v0.9.6 h1:vNzEJmaJL0XOZD8uouXLmYu4/aP1UQ/wHUopH3qKeYA=
|
||||
github.com/smallstep/truststore v0.9.6/go.mod h1:HwHKRcBi0RUxxw1LYDpTRhYC4jZUuxPpkHdVonlkoDM=
|
||||
github.com/smallstep/zcrypto v0.0.0-20200203191936-fbc32cf76bce/go.mod h1:+F24VU3UCxfVFvvqgm5jNUFQOm/L6ed13ImwWGFgg/g=
|
||||
github.com/smallstep/zlint v0.0.0-20180727184541-d84eaafe274f/go.mod h1:GeHHT7sJDI9ti3oEaFnvx1F4N8n3ZSw2YM1+sbEoxc4=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
@@ -686,27 +573,22 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/timewasted/linode v0.0.0-20160829202747-37e84520dcf7/go.mod h1:imsgLplxEC/etjIhdr3dNzV3JeT27LbVu5pYWm0JCBY=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/transip/gotransip v0.0.0-20190812104329-6d8d9179b66f/go.mod h1:i0f4R4o2HM0m3DZYQWsj6/MEowD57VzoH0v3d7igeFY=
|
||||
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM=
|
||||
@@ -718,80 +600,71 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||
github.com/vulcand/oxy v1.0.0 h1:7vL5/pjDFzHGbtBEhmlHITUi6KLH4xXTDF33/wrdRKw=
|
||||
github.com/vulcand/oxy v1.0.0/go.mod h1:6EXgOAl6CRa46/2ZGcDJKf3ywJUp5WtT7vSlGSkvecI=
|
||||
github.com/vulcand/predicate v1.1.0/go.mod h1:mlccC5IRBoc2cIFmCB8ZM62I3VDb6p2GXESMHa3CnZg=
|
||||
github.com/vultr/govultr v0.1.4/go.mod h1:9H008Uxr/C4vFNGLqKx232C206GL0PBHzOP0809bGNA=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.10.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.25 h1:isv+Q6HQAmmL2Ofcmg8QauBmDPlUUnSoNhEcC940Rds=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio=
|
||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
github.com/zmap/zcertificate v0.0.0-20190521191901-30e388164f71/go.mod h1:gIZi1KPgkZNUQzPZXsZrNnUnxy05nTc0+tmlqvIkhRw=
|
||||
github.com/zmap/zcrypto v0.0.0-20190329181646-dff83107394d/go.mod h1:ix3q2kpLy0ibAuFXlr7qOhPKwFRRSjuynGuTR8EUPCk=
|
||||
github.com/zmap/zlint v0.0.0-20190516161541-9047d02cf65a/go.mod h1:xwLbce0UzBXp44sIAL1cii+hoK8j4AxRKlymZA2AIcY=
|
||||
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.etcd.io/etcd v3.3.18+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM=
|
||||
go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E=
|
||||
go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
|
||||
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.14.0 h1:/pduUoebOeeJzTDFuoMgC6nRkiasr1sBCIEorly7m4o=
|
||||
go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
|
||||
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
|
||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@@ -811,7 +684,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180611182652-db08ff08e862/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -821,28 +693,25 @@ golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190301231341-16b79f2e4e95/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191027093000-83d349e8ac1a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -856,49 +725,43 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190424175732-18eb32c0e2f0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e h1:LwyF2AFISC9nVbS6MgzsaQNSUsRXI49GS+YQ5KX/QH0=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -908,8 +771,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0 h1:xQwXv67TxFo9nC1GJFyab5eq/5B590r6RlnL/G8Sz7w=
|
||||
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -950,27 +811,23 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc h1:MR2F33ipDGog0C4eMhU6u9o3q6c3dvYis2aG6Jl12Wg=
|
||||
golang.org/x/tools v0.0.0-20200106190116-7be0a674c9fc/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
@@ -995,25 +852,41 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb h1:ADPHZzpzM4tk4V4S5cnCrr5SwzvlrPRmqqCuJDB8UTs=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc h1:TnonUr8u3himcMY0vSh23jFOXA+cnucl1gB6EQTReBI=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -1022,36 +895,25 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
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/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc h1:GAcf+t0o8gdJAdSFYdE9wChu4bIyguMVqz0RHiFL5VY=
|
||||
gopkg.in/ns1/ns1-go.v2 v2.0.0-20190730140822-b51389932cbc/go.mod h1:VV+3haRsgDiVLxyifmMBrBIuCWFBPYKbRssXB9z67Hw=
|
||||
gopkg.in/resty.v1 v1.9.1/go.mod h1:vo52Hzryw9PnPHcJfPsBiFW62XhNx5OczbV9y+IMpgc=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y=
|
||||
gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@@ -1060,8 +922,9 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5 h1:AQkaJpH+/FmqRjmXZPELom5zIERYZfwTjnHpfoVMQEc=
|
||||
howett.net/plist v0.0.0-20200419221736-3b63eb3a43b5/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||
|
||||
+67
-27
@@ -254,49 +254,76 @@ type globalListener struct {
|
||||
pc net.PacketConn
|
||||
}
|
||||
|
||||
// ParsedAddress contains the individual components
|
||||
// NetworkAddress contains the individual components
|
||||
// for a parsed network address of the form accepted
|
||||
// by ParseNetworkAddress(). Network should be a
|
||||
// network value accepted by Go's net package. Port
|
||||
// ranges are given by [StartPort, EndPort].
|
||||
type ParsedAddress struct {
|
||||
type NetworkAddress struct {
|
||||
Network string
|
||||
Host string
|
||||
StartPort uint
|
||||
EndPort uint
|
||||
}
|
||||
|
||||
// IsUnixNetwork returns true if pa.Network is
|
||||
// IsUnixNetwork returns true if na.Network is
|
||||
// unix, unixgram, or unixpacket.
|
||||
func (pa ParsedAddress) IsUnixNetwork() bool {
|
||||
return isUnixNetwork(pa.Network)
|
||||
func (na NetworkAddress) IsUnixNetwork() bool {
|
||||
return isUnixNetwork(na.Network)
|
||||
}
|
||||
|
||||
// JoinHostPort is like net.JoinHostPort, but where the port
|
||||
// is StartPort + offset.
|
||||
func (pa ParsedAddress) JoinHostPort(offset uint) string {
|
||||
if pa.IsUnixNetwork() {
|
||||
return pa.Host
|
||||
func (na NetworkAddress) JoinHostPort(offset uint) string {
|
||||
if na.IsUnixNetwork() {
|
||||
return na.Host
|
||||
}
|
||||
return net.JoinHostPort(pa.Host, strconv.Itoa(int(pa.StartPort+offset)))
|
||||
return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
|
||||
}
|
||||
|
||||
// PortRangeSize returns how many ports are in
|
||||
// pa's port range. Port ranges are inclusive,
|
||||
// so the size is the difference of start and
|
||||
// end ports plus one.
|
||||
func (pa ParsedAddress) PortRangeSize() uint {
|
||||
return (pa.EndPort - pa.StartPort) + 1
|
||||
func (na NetworkAddress) PortRangeSize() uint {
|
||||
return (na.EndPort - na.StartPort) + 1
|
||||
}
|
||||
|
||||
func (na NetworkAddress) isLoopback() bool {
|
||||
if na.IsUnixNetwork() {
|
||||
return true
|
||||
}
|
||||
if na.Host == "localhost" {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(na.Host); ip != nil {
|
||||
return ip.IsLoopback()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (na NetworkAddress) isWildcardInterface() bool {
|
||||
if na.Host == "" {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(na.Host); ip != nil {
|
||||
return ip.IsUnspecified()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (na NetworkAddress) port() string {
|
||||
if na.StartPort == na.EndPort {
|
||||
return strconv.FormatUint(uint64(na.StartPort), 10)
|
||||
}
|
||||
return fmt.Sprintf("%d-%d", na.StartPort, na.EndPort)
|
||||
}
|
||||
|
||||
// String reconstructs the address string to the form expected
|
||||
// by ParseNetworkAddress().
|
||||
func (pa ParsedAddress) String() string {
|
||||
port := strconv.FormatUint(uint64(pa.StartPort), 10)
|
||||
if pa.StartPort != pa.EndPort {
|
||||
port += "-" + strconv.FormatUint(uint64(pa.EndPort), 10)
|
||||
}
|
||||
return JoinNetworkAddress(pa.Network, pa.Host, port)
|
||||
// by ParseNetworkAddress(). If the address is a unix socket,
|
||||
// any non-zero port will be dropped.
|
||||
func (na NetworkAddress) String() string {
|
||||
return JoinNetworkAddress(na.Network, na.Host, na.port())
|
||||
}
|
||||
|
||||
func isUnixNetwork(netw string) bool {
|
||||
@@ -311,17 +338,17 @@ func isUnixNetwork(netw string) bool {
|
||||
//
|
||||
// Network addresses are distinct from URLs and do not
|
||||
// use URL syntax.
|
||||
func ParseNetworkAddress(addr string) (ParsedAddress, error) {
|
||||
func ParseNetworkAddress(addr string) (NetworkAddress, error) {
|
||||
var host, port string
|
||||
network, host, port, err := SplitNetworkAddress(addr)
|
||||
if network == "" {
|
||||
network = "tcp"
|
||||
}
|
||||
if err != nil {
|
||||
return ParsedAddress{}, err
|
||||
return NetworkAddress{}, err
|
||||
}
|
||||
if isUnixNetwork(network) {
|
||||
return ParsedAddress{
|
||||
return NetworkAddress{
|
||||
Network: network,
|
||||
Host: host,
|
||||
}, nil
|
||||
@@ -333,19 +360,19 @@ func ParseNetworkAddress(addr string) (ParsedAddress, error) {
|
||||
var start, end uint64
|
||||
start, err = strconv.ParseUint(ports[0], 10, 16)
|
||||
if err != nil {
|
||||
return ParsedAddress{}, fmt.Errorf("invalid start port: %v", err)
|
||||
return NetworkAddress{}, fmt.Errorf("invalid start port: %v", err)
|
||||
}
|
||||
end, err = strconv.ParseUint(ports[1], 10, 16)
|
||||
if err != nil {
|
||||
return ParsedAddress{}, fmt.Errorf("invalid end port: %v", err)
|
||||
return NetworkAddress{}, fmt.Errorf("invalid end port: %v", err)
|
||||
}
|
||||
if end < start {
|
||||
return ParsedAddress{}, fmt.Errorf("end port must not be less than start port")
|
||||
return NetworkAddress{}, fmt.Errorf("end port must not be less than start port")
|
||||
}
|
||||
if (end - start) > maxPortSpan {
|
||||
return ParsedAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
|
||||
return NetworkAddress{}, fmt.Errorf("port range exceeds %d ports", maxPortSpan)
|
||||
}
|
||||
return ParsedAddress{
|
||||
return NetworkAddress{
|
||||
Network: network,
|
||||
Host: host,
|
||||
StartPort: uint(start),
|
||||
@@ -378,7 +405,7 @@ func JoinNetworkAddress(network, host, port string) string {
|
||||
if network != "" {
|
||||
a = network + "/"
|
||||
}
|
||||
if host != "" && port == "" {
|
||||
if (host != "" && port == "") || isUnixNetwork(network) {
|
||||
a += host
|
||||
} else if port != "" {
|
||||
a += net.JoinHostPort(host, port)
|
||||
@@ -386,6 +413,19 @@ func JoinNetworkAddress(network, host, port string) string {
|
||||
return a
|
||||
}
|
||||
|
||||
// ListenerWrapper is a type that wraps a listener
|
||||
// so it can modify the input listener's methods.
|
||||
// Modules that implement this interface are found
|
||||
// in the caddy.listeners namespace. Usually, to
|
||||
// wrap a listener, you will define your own struct
|
||||
// type that embeds the input listener, then
|
||||
// implement your own methods that you want to wrap,
|
||||
// calling the underlying listener's methods where
|
||||
// appropriate.
|
||||
type ListenerWrapper interface {
|
||||
WrapListener(net.Listener) net.Listener
|
||||
}
|
||||
|
||||
var (
|
||||
listeners = make(map[string]*globalListener)
|
||||
listenersMu sync.Mutex
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
// +build gofuzz
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
package caddy
|
||||
|
||||
|
||||
+21
-13
@@ -138,6 +138,14 @@ func TestJoinNetworkAddress(t *testing.T) {
|
||||
network: "unix", host: "/foo/bar", port: "",
|
||||
expect: "unix//foo/bar",
|
||||
},
|
||||
{
|
||||
network: "unix", host: "/foo/bar", port: "0",
|
||||
expect: "unix//foo/bar",
|
||||
},
|
||||
{
|
||||
network: "unix", host: "/foo/bar", port: "1234",
|
||||
expect: "unix//foo/bar",
|
||||
},
|
||||
{
|
||||
network: "", host: "::1", port: "1234",
|
||||
expect: "[::1]:1234",
|
||||
@@ -153,7 +161,7 @@ func TestJoinNetworkAddress(t *testing.T) {
|
||||
func TestParseNetworkAddress(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
input string
|
||||
expectAddr ParsedAddress
|
||||
expectAddr NetworkAddress
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
@@ -166,7 +174,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: ":1234",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "",
|
||||
StartPort: 1234,
|
||||
@@ -175,7 +183,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "tcp/:1234",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "",
|
||||
StartPort: 1234,
|
||||
@@ -184,7 +192,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "tcp6/:1234",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp6",
|
||||
Host: "",
|
||||
StartPort: 1234,
|
||||
@@ -193,7 +201,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "tcp4/localhost:1234",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp4",
|
||||
Host: "localhost",
|
||||
StartPort: 1234,
|
||||
@@ -202,14 +210,14 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "unix//foo/bar",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "unix",
|
||||
Host: "/foo/bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: "localhost:1234-1234",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "localhost",
|
||||
StartPort: 1234,
|
||||
@@ -222,7 +230,7 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
},
|
||||
{
|
||||
input: "localhost:0",
|
||||
expectAddr: ParsedAddress{
|
||||
expectAddr: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "localhost",
|
||||
StartPort: 0,
|
||||
@@ -253,12 +261,12 @@ func TestParseNetworkAddress(t *testing.T) {
|
||||
|
||||
func TestJoinHostPort(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
pa ParsedAddress
|
||||
pa NetworkAddress
|
||||
offset uint
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
pa: ParsedAddress{
|
||||
pa: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "localhost",
|
||||
StartPort: 1234,
|
||||
@@ -267,7 +275,7 @@ func TestJoinHostPort(t *testing.T) {
|
||||
expect: "localhost:1234",
|
||||
},
|
||||
{
|
||||
pa: ParsedAddress{
|
||||
pa: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "localhost",
|
||||
StartPort: 1234,
|
||||
@@ -276,7 +284,7 @@ func TestJoinHostPort(t *testing.T) {
|
||||
expect: "localhost:1234",
|
||||
},
|
||||
{
|
||||
pa: ParsedAddress{
|
||||
pa: NetworkAddress{
|
||||
Network: "tcp",
|
||||
Host: "localhost",
|
||||
StartPort: 1234,
|
||||
@@ -286,7 +294,7 @@ func TestJoinHostPort(t *testing.T) {
|
||||
expect: "localhost:1235",
|
||||
},
|
||||
{
|
||||
pa: ParsedAddress{
|
||||
pa: NetworkAddress{
|
||||
Network: "unix",
|
||||
Host: "/run/php/php7.3-fpm.sock",
|
||||
},
|
||||
|
||||
+42
-26
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -35,12 +36,14 @@ func init() {
|
||||
RegisterModule(DiscardWriter{})
|
||||
}
|
||||
|
||||
// Logging facilitates logging within Caddy.
|
||||
// Logging facilitates logging within Caddy. The default log is
|
||||
// called "default" and you can customize it. You can also define
|
||||
// additional logs.
|
||||
//
|
||||
// By default, all logs at INFO level and higher are written to
|
||||
// standard error ("stderr" writer) in a human-readable format
|
||||
// ("console" encoder). The default log is called "default" and
|
||||
// you can customize it. You can also define additional logs.
|
||||
// ("console" encoder if stdout is an interactive terminal, "json"
|
||||
// encoder otherwise).
|
||||
//
|
||||
// All defined logs accept all log entries by default, but you
|
||||
// can filter by level and module/logger names. A logger's name
|
||||
@@ -50,10 +53,10 @@ func init() {
|
||||
// "http.handlers", because all HTTP handler module names have
|
||||
// that prefix.
|
||||
//
|
||||
// Caddy logs (except the sink) are mostly zero-allocation, so
|
||||
// they are very high-performing in terms of memory and CPU time.
|
||||
// Enabling sampling can further increase throughput on extremely
|
||||
// high-load servers.
|
||||
// Caddy logs (except the sink) are zero-allocation, so they are
|
||||
// very high-performing in terms of memory and CPU time. Enabling
|
||||
// sampling can further increase throughput on extremely high-load
|
||||
// servers.
|
||||
type Logging struct {
|
||||
// Sink is the destination for all unstructured logs emitted
|
||||
// from Go's standard library logger. These logs are common
|
||||
@@ -214,7 +217,7 @@ func (logging *Logging) Logger(mod Module) *zap.Logger {
|
||||
|
||||
multiCore := zapcore.NewTee(cores...)
|
||||
|
||||
return zap.New(multiCore).Named(string(modID))
|
||||
return zap.New(multiCore).Named(modID)
|
||||
}
|
||||
|
||||
// openWriter opens a writer using opener, and returns true if
|
||||
@@ -393,17 +396,6 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
|
||||
}
|
||||
}
|
||||
|
||||
if cl.EncoderRaw != nil {
|
||||
mod, err := ctx.LoadModule(cl, "EncoderRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading log encoder module: %v", err)
|
||||
}
|
||||
cl.encoder = mod.(zapcore.Encoder)
|
||||
}
|
||||
if cl.encoder == nil {
|
||||
cl.encoder = newDefaultProductionLogEncoder()
|
||||
}
|
||||
|
||||
if cl.WriterRaw != nil {
|
||||
mod, err := ctx.LoadModule(cl, "WriterRaw")
|
||||
if err != nil {
|
||||
@@ -420,6 +412,24 @@ func (cl *CustomLog) provision(ctx Context, logging *Logging) error {
|
||||
return fmt.Errorf("opening log writer using %#v: %v", cl.writerOpener, err)
|
||||
}
|
||||
|
||||
if cl.EncoderRaw != nil {
|
||||
mod, err := ctx.LoadModule(cl, "EncoderRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading log encoder module: %v", err)
|
||||
}
|
||||
cl.encoder = mod.(zapcore.Encoder)
|
||||
}
|
||||
if cl.encoder == nil {
|
||||
// only allow colorized output if this log is going to stdout or stderr
|
||||
var colorize bool
|
||||
switch cl.writerOpener.(type) {
|
||||
case StdoutWriter, StderrWriter,
|
||||
*StdoutWriter, *StderrWriter:
|
||||
colorize = true
|
||||
}
|
||||
cl.encoder = newDefaultProductionLogEncoder(colorize)
|
||||
}
|
||||
|
||||
cl.buildCore()
|
||||
|
||||
return nil
|
||||
@@ -455,7 +465,7 @@ func (cl *CustomLog) buildCore() {
|
||||
}
|
||||
|
||||
func (cl *CustomLog) matchesModule(moduleID string) bool {
|
||||
return cl.loggerAllowed(string(moduleID), true)
|
||||
return cl.loggerAllowed(moduleID, true)
|
||||
}
|
||||
|
||||
// loggerAllowed returns true if name is allowed to emit
|
||||
@@ -647,7 +657,7 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cl.encoder = newDefaultProductionLogEncoder()
|
||||
cl.encoder = newDefaultProductionLogEncoder(true)
|
||||
cl.levelEnabler = zapcore.InfoLevel
|
||||
|
||||
cl.buildCore()
|
||||
@@ -658,13 +668,19 @@ func newDefaultProductionLog() (*defaultCustomLog, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newDefaultProductionLogEncoder() zapcore.Encoder {
|
||||
func newDefaultProductionLogEncoder(colorize bool) zapcore.Encoder {
|
||||
encCfg := zap.NewProductionEncoderConfig()
|
||||
encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||
encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
|
||||
if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||
// if interactive terminal, make output more human-readable by default
|
||||
encCfg.EncodeTime = func(ts time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||
encoder.AppendString(ts.UTC().Format("2006/01/02 15:04:05.000"))
|
||||
}
|
||||
if colorize {
|
||||
encCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(encCfg)
|
||||
}
|
||||
return zapcore.NewConsoleEncoder(encCfg)
|
||||
return zapcore.NewJSONEncoder(encCfg)
|
||||
}
|
||||
|
||||
// Log returns the current default logger.
|
||||
|
||||
+18
-14
@@ -65,7 +65,12 @@ type ModuleInfo struct {
|
||||
|
||||
// New returns a pointer to a new, empty
|
||||
// instance of the module's type. This
|
||||
// function must not have any side-effects.
|
||||
// method must not have any side-effects,
|
||||
// and no other initialization should
|
||||
// occur within it. Any initialization
|
||||
// of the returned value should be done
|
||||
// in a Provision() method (see the
|
||||
// Provisioner interface).
|
||||
New func() Module
|
||||
}
|
||||
|
||||
@@ -125,33 +130,32 @@ type ModuleMap map[string]json.RawMessage
|
||||
// be properly recorded, this should be called in the
|
||||
// init phase of runtime. Typically, the module package
|
||||
// will do this as a side-effect of being imported.
|
||||
// This function returns an error if the module's info
|
||||
// is incomplete or invalid, or if the module is
|
||||
// already registered.
|
||||
func RegisterModule(instance Module) error {
|
||||
// This function panics if the module's info is
|
||||
// incomplete or invalid, or if the module is already
|
||||
// registered.
|
||||
func RegisterModule(instance Module) {
|
||||
mod := instance.CaddyModule()
|
||||
|
||||
if mod.ID == "" {
|
||||
return fmt.Errorf("module ID missing")
|
||||
panic("module ID missing")
|
||||
}
|
||||
if mod.ID == "caddy" || mod.ID == "admin" {
|
||||
return fmt.Errorf("module ID '%s' is reserved", mod.ID)
|
||||
panic(fmt.Sprintf("module ID '%s' is reserved", mod.ID))
|
||||
}
|
||||
if mod.New == nil {
|
||||
return fmt.Errorf("missing ModuleInfo.New")
|
||||
panic("missing ModuleInfo.New")
|
||||
}
|
||||
if val := mod.New(); val == nil {
|
||||
return fmt.Errorf("ModuleInfo.New must return a non-nil module instance")
|
||||
panic("ModuleInfo.New must return a non-nil module instance")
|
||||
}
|
||||
|
||||
modulesMu.Lock()
|
||||
defer modulesMu.Unlock()
|
||||
|
||||
if _, ok := modules[string(mod.ID)]; ok {
|
||||
return fmt.Errorf("module already registered: %s", mod.ID)
|
||||
panic(fmt.Sprintf("module already registered: %s", mod.ID))
|
||||
}
|
||||
modules[string(mod.ID)] = mod
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModule returns module information from its ID (full name).
|
||||
@@ -210,7 +214,7 @@ func GetModules(scope string) []ModuleInfo {
|
||||
var mods []ModuleInfo
|
||||
iterateModules:
|
||||
for id, m := range modules {
|
||||
modParts := strings.Split(string(id), ".")
|
||||
modParts := strings.Split(id, ".")
|
||||
|
||||
// match only the next level of nesting
|
||||
if len(modParts) != len(scopeParts)+1 {
|
||||
@@ -241,9 +245,9 @@ func Modules() []string {
|
||||
modulesMu.RLock()
|
||||
defer modulesMu.RUnlock()
|
||||
|
||||
var names []string
|
||||
names := make([]string, 0, len(modules))
|
||||
for name := range modules {
|
||||
names = append(names, string(name))
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(App{})
|
||||
}
|
||||
|
||||
// App is a robust, production-ready HTTP server.
|
||||
//
|
||||
// HTTPS is enabled by default if host matchers with qualifying names are used
|
||||
// in any of routes; certificates are automatically provisioned and renewed.
|
||||
// Additionally, automatic HTTPS will also enable HTTPS for servers that listen
|
||||
// only on the HTTPS port but which do not have any TLS connection policies
|
||||
// defined by adding a good, default TLS connection policy.
|
||||
//
|
||||
// In HTTP routes, additional placeholders are available (replace any `*`):
|
||||
//
|
||||
// Placeholder | Description
|
||||
// ------------|---------------
|
||||
// `{http.request.body}` | The request body (⚠️ inefficient; use only for debugging)
|
||||
// `{http.request.cookie.*}` | HTTP request cookie
|
||||
// `{http.request.header.*}` | Specific request header field
|
||||
// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo
|
||||
// `{http.request.host}` | The host part of the request's Host header
|
||||
// `{http.request.hostport}` | The host and port from the request's Host header
|
||||
// `{http.request.method}` | The request method
|
||||
// `{http.request.orig_method}` | The request's original method
|
||||
// `{http.request.orig_uri.path.dir}` | The request's original directory
|
||||
// `{http.request.orig_uri.path.file}` | The request's original filename
|
||||
// `{http.request.orig_uri.path}` | The request's original path
|
||||
// `{http.request.orig_uri.query}` | The request's original query string (without `?`)
|
||||
// `{http.request.orig_uri}` | The request's original URI
|
||||
// `{http.request.port}` | The port part of the request's Host header
|
||||
// `{http.request.proto}` | The protocol of the request
|
||||
// `{http.request.remote.host}` | The host part of the remote client's address
|
||||
// `{http.request.remote.port}` | The port part of the remote client's address
|
||||
// `{http.request.remote}` | The address of the remote client
|
||||
// `{http.request.scheme}` | The request scheme
|
||||
// `{http.request.tls.version}` | The TLS version name
|
||||
// `{http.request.tls.cipher_suite}` | The TLS cipher suite
|
||||
// `{http.request.tls.resumed}` | The TLS connection resumed a previous connection
|
||||
// `{http.request.tls.proto}` | The negotiated next protocol
|
||||
// `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server
|
||||
// `{http.request.tls.server_name}` | The server name requested by the client, if any
|
||||
// `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate
|
||||
// `{http.request.tls.client.public_key}` | The public key of the client certificate.
|
||||
// `{http.request.tls.client.public_key_sha256}` | The SHA256 checksum of the client's public key.
|
||||
// `{http.request.tls.client.issuer}` | The issuer DN of the client certificate
|
||||
// `{http.request.tls.client.serial}` | The serial number of the client certificate
|
||||
// `{http.request.tls.client.subject}` | The subject DN of the client certificate
|
||||
// `{http.request.tls.client.san.dns_names.*}` | SAN DNS names(index optional)
|
||||
// `{http.request.tls.client.san.emails.*}` | SAN email addresses (index optional)
|
||||
// `{http.request.tls.client.san.ips.*}` | SAN IP addresses (index optional)
|
||||
// `{http.request.tls.client.san.uris.*}` | SAN URIs (index optional)
|
||||
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
|
||||
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
|
||||
// `{http.request.uri.path.file}` | The filename of the path, excluding directory
|
||||
// `{http.request.uri.path}` | The path component of the request URI
|
||||
// `{http.request.uri.query.*}` | Individual query string value
|
||||
// `{http.request.uri.query}` | The query string (without `?`)
|
||||
// `{http.request.uri}` | The full request URI
|
||||
// `{http.response.header.*}` | Specific response header field
|
||||
// `{http.vars.*}` | Custom variables in the HTTP handler chain
|
||||
type App struct {
|
||||
// HTTPPort specifies the port to use for HTTP (as opposed to HTTPS),
|
||||
// which is used when setting up HTTP->HTTPS redirects or ACME HTTP
|
||||
// challenge solvers. Default: 80.
|
||||
HTTPPort int `json:"http_port,omitempty"`
|
||||
|
||||
// HTTPSPort specifies the port to use for HTTPS, which is used when
|
||||
// solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed
|
||||
// but no specific port number is given. Default: 443.
|
||||
HTTPSPort int `json:"https_port,omitempty"`
|
||||
|
||||
// GracePeriod is how long to wait for active connections when shutting
|
||||
// down the server. Once the grace period is over, connections will
|
||||
// be forcefully closed.
|
||||
GracePeriod caddy.Duration `json:"grace_period,omitempty"`
|
||||
|
||||
// Servers is the list of servers, keyed by arbitrary names chosen
|
||||
// at your discretion for your own convenience; the keys do not
|
||||
// affect functionality.
|
||||
Servers map[string]*Server `json:"servers,omitempty"`
|
||||
|
||||
servers []*http.Server
|
||||
h3servers []*http3.Server
|
||||
h3listeners []net.PacketConn
|
||||
|
||||
ctx caddy.Context
|
||||
logger *zap.Logger
|
||||
tlsApp *caddytls.TLS
|
||||
|
||||
// used temporarily between phases 1 and 2 of auto HTTPS
|
||||
allCertDomains []string
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (App) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http",
|
||||
New: func() caddy.Module { return new(App) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up the app.
|
||||
func (app *App) Provision(ctx caddy.Context) error {
|
||||
// store some references
|
||||
tlsAppIface, err := ctx.App("tls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tls app: %v", err)
|
||||
}
|
||||
app.tlsApp = tlsAppIface.(*caddytls.TLS)
|
||||
app.ctx = ctx
|
||||
app.logger = ctx.Logger(app)
|
||||
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
// this provisions the matchers for each route,
|
||||
// and prepares auto HTTP->HTTPS redirects, and
|
||||
// is required before we provision each server
|
||||
err = app.automaticHTTPSPhase1(ctx, repl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepare each server
|
||||
for srvName, srv := range app.Servers {
|
||||
srv.tlsApp = app.tlsApp
|
||||
srv.logger = app.logger.Named("log")
|
||||
srv.errorLogger = app.logger.Named("log.error")
|
||||
|
||||
// only enable access logs if configured
|
||||
if srv.Logs != nil {
|
||||
srv.accessLogger = app.logger.Named("log.access")
|
||||
}
|
||||
|
||||
// if not explicitly configured by the user, disallow TLS
|
||||
// client auth bypass (domain fronting) which could
|
||||
// otherwise be exploited by sending an unprotected SNI
|
||||
// value during a TLS handshake, then putting a protected
|
||||
// domain in the Host header after establishing connection;
|
||||
// this is a safe default, but we allow users to override
|
||||
// it for example in the case of running a proxy where
|
||||
// domain fronting is desired and access is not restricted
|
||||
// based on hostname
|
||||
if srv.StrictSNIHost == nil && srv.hasTLSClientAuth() {
|
||||
app.logger.Info("enabling strict SNI-Host matching because TLS client auth is configured",
|
||||
zap.String("server_name", srvName),
|
||||
)
|
||||
trueBool := true
|
||||
srv.StrictSNIHost = &trueBool
|
||||
}
|
||||
|
||||
// process each listener address
|
||||
for i := range srv.Listen {
|
||||
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s, listener %d: %v",
|
||||
srvName, i, err)
|
||||
}
|
||||
srv.Listen[i] = lnOut
|
||||
}
|
||||
|
||||
// set up each listener modifier
|
||||
if srv.ListenerWrappersRaw != nil {
|
||||
vals, err := ctx.LoadModule(srv, "ListenerWrappersRaw")
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading listener wrapper modules: %v", err)
|
||||
}
|
||||
var hasTLSPlaceholder bool
|
||||
for i, val := range vals.([]interface{}) {
|
||||
if _, ok := val.(*tlsPlaceholderWrapper); ok {
|
||||
if i == 0 {
|
||||
// putting the tls placeholder wrapper first is nonsensical because
|
||||
// that is the default, implicit setting: without it, all wrappers
|
||||
// will go after the TLS listener anyway
|
||||
return fmt.Errorf("it is unnecessary to specify the TLS listener wrapper in the first position because that is the default")
|
||||
}
|
||||
if hasTLSPlaceholder {
|
||||
return fmt.Errorf("TLS listener wrapper can only be specified once")
|
||||
}
|
||||
hasTLSPlaceholder = true
|
||||
}
|
||||
srv.listenerWrappers = append(srv.listenerWrappers, val.(caddy.ListenerWrapper))
|
||||
}
|
||||
// if any wrappers were configured but the TLS placeholder wrapper is
|
||||
// absent, prepend it so all defined wrappers come after the TLS
|
||||
// handshake; this simplifies logic when starting the server, since we
|
||||
// can simply assume the TLS placeholder will always be there
|
||||
if !hasTLSPlaceholder && len(srv.listenerWrappers) > 0 {
|
||||
srv.listenerWrappers = append([]caddy.ListenerWrapper{new(tlsPlaceholderWrapper)}, srv.listenerWrappers...)
|
||||
}
|
||||
}
|
||||
|
||||
// pre-compile the primary handler chain, and be sure to wrap it in our
|
||||
// route handler so that important security checks are done, etc.
|
||||
primaryRoute := emptyHandler
|
||||
if srv.Routes != nil {
|
||||
err := srv.Routes.ProvisionHandlers(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
|
||||
}
|
||||
primaryRoute = srv.Routes.Compile(emptyHandler)
|
||||
}
|
||||
srv.primaryHandlerChain = srv.wrapPrimaryRoute(primaryRoute)
|
||||
|
||||
// pre-compile the error handler chain
|
||||
if srv.Errors != nil {
|
||||
err := srv.Errors.Routes.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err)
|
||||
}
|
||||
srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
|
||||
}
|
||||
|
||||
// prepare the TLS connection policies
|
||||
err = srv.TLSConnPolicies.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up TLS connection policies: %v", srvName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures the app's configuration is valid.
|
||||
func (app *App) Validate() error {
|
||||
// each server must use distinct listener addresses
|
||||
lnAddrs := make(map[string]string)
|
||||
for srvName, srv := range app.Servers {
|
||||
for _, addr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid listener address '%s': %v", addr, err)
|
||||
}
|
||||
// check that every address in the port range is unique to this server;
|
||||
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
||||
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
||||
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
|
||||
if sn, ok := lnAddrs[addr]; ok {
|
||||
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
||||
}
|
||||
lnAddrs[addr] = srvName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start runs the app. It finishes automatic HTTPS if enabled,
|
||||
// including management of certificates.
|
||||
func (app *App) Start() error {
|
||||
for srvName, srv := range app.Servers {
|
||||
s := &http.Server{
|
||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
|
||||
WriteTimeout: time.Duration(srv.WriteTimeout),
|
||||
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||
Handler: srv,
|
||||
}
|
||||
|
||||
// enable h2c if configured
|
||||
if srv.AllowH2C {
|
||||
h2server := &http2.Server{
|
||||
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||
}
|
||||
s.Handler = h2c.NewHandler(srv, h2server)
|
||||
}
|
||||
|
||||
for _, lnAddr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
||||
}
|
||||
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
||||
// create the listener for this socket
|
||||
hostport := listenAddr.JoinHostPort(portOffset)
|
||||
ln, err := caddy.Listen(listenAddr.Network, hostport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: listening on %s: %v", listenAddr.Network, hostport, err)
|
||||
}
|
||||
|
||||
// wrap listener before TLS (up to the TLS placeholder wrapper)
|
||||
var lnWrapperIdx int
|
||||
for i, lnWrapper := range srv.listenerWrappers {
|
||||
if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
|
||||
lnWrapperIdx = i + 1 // mark the next wrapper's spot
|
||||
break
|
||||
}
|
||||
ln = lnWrapper.WrapListener(ln)
|
||||
}
|
||||
|
||||
// enable TLS if there is a policy and if this is not the HTTP port
|
||||
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
||||
if useTLS {
|
||||
// create TLS listener
|
||||
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||
ln = tls.NewListener(ln, tlsCfg)
|
||||
|
||||
/////////
|
||||
// TODO: HTTP/3 support is experimental for now
|
||||
if srv.ExperimentalHTTP3 {
|
||||
app.logger.Info("enabling experimental HTTP/3 listener",
|
||||
zap.String("addr", hostport),
|
||||
)
|
||||
h3ln, err := caddy.ListenPacket("udp", hostport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting HTTP/3 UDP listener: %v", err)
|
||||
}
|
||||
h3srv := &http3.Server{
|
||||
Server: &http.Server{
|
||||
Addr: hostport,
|
||||
Handler: srv,
|
||||
TLSConfig: tlsCfg,
|
||||
},
|
||||
}
|
||||
go h3srv.Serve(h3ln)
|
||||
app.h3servers = append(app.h3servers, h3srv)
|
||||
app.h3listeners = append(app.h3listeners, h3ln)
|
||||
srv.h3server = h3srv
|
||||
}
|
||||
/////////
|
||||
}
|
||||
|
||||
// finish wrapping listener where we left off before TLS
|
||||
for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ {
|
||||
ln = srv.listenerWrappers[i].WrapListener(ln)
|
||||
}
|
||||
|
||||
// if binding to port 0, the OS chooses a port for us;
|
||||
// but the user won't know the port unless we print it
|
||||
if listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
|
||||
app.logger.Info("port 0 listener",
|
||||
zap.String("input_address", lnAddr),
|
||||
zap.String("actual_address", ln.Addr().String()),
|
||||
)
|
||||
}
|
||||
|
||||
app.logger.Debug("starting server loop",
|
||||
zap.String("address", ln.Addr().String()),
|
||||
zap.Bool("http3", srv.ExperimentalHTTP3),
|
||||
zap.Bool("tls", useTLS),
|
||||
)
|
||||
|
||||
go s.Serve(ln)
|
||||
app.servers = append(app.servers, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finish automatic HTTPS by finally beginning
|
||||
// certificate management
|
||||
err := app.automaticHTTPSPhase2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finalizing automatic HTTPS: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the HTTP server.
|
||||
func (app *App) Stop() error {
|
||||
ctx := context.Background()
|
||||
if app.GracePeriod > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
|
||||
defer cancel()
|
||||
}
|
||||
for _, s := range app.servers {
|
||||
err := s.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// close the http3 servers; it's unclear whether the bug reported in
|
||||
// https://github.com/caddyserver/caddy/pull/2727#issuecomment-526856566
|
||||
// was ever truly fixed, since it seemed racey/nondeterministic; but
|
||||
// recent tests in 2020 were unable to replicate the issue again after
|
||||
// repeated attempts (the bug manifested after a config reload; i.e.
|
||||
// reusing a http3 server or listener was problematic), but it seems
|
||||
// to be working fine now
|
||||
for _, s := range app.h3servers {
|
||||
// TODO: CloseGracefully, once implemented upstream
|
||||
// (see https://github.com/lucas-clemente/quic-go/issues/2103)
|
||||
err := s.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// closing an http3.Server does not close their underlying listeners
|
||||
// since apparently the listener can be used both by servers and
|
||||
// clients at the same time; so we need to manually call Close()
|
||||
// on the underlying h3 listeners (see lucas-clemente/quic-go#2103)
|
||||
for _, pc := range app.h3listeners {
|
||||
err := pc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *App) httpPort() int {
|
||||
if app.HTTPPort == 0 {
|
||||
return DefaultHTTPPort
|
||||
}
|
||||
return app.HTTPPort
|
||||
}
|
||||
|
||||
func (app *App) httpsPort() int {
|
||||
if app.HTTPSPort == 0 {
|
||||
return DefaultHTTPSPort
|
||||
}
|
||||
return app.HTTPSPort
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.App = (*App)(nil)
|
||||
_ caddy.Provisioner = (*App)(nil)
|
||||
_ caddy.Validator = (*App)(nil)
|
||||
)
|
||||
+216
-97
@@ -1,3 +1,17 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddyhttp
|
||||
|
||||
import (
|
||||
@@ -67,8 +81,10 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
uniqueDomainsForCerts := make(map[string]struct{})
|
||||
|
||||
// this maps domain names for automatic HTTP->HTTPS
|
||||
// redirects to their destination server address
|
||||
redirDomains := make(map[string]caddy.ParsedAddress)
|
||||
// redirects to their destination server addresses
|
||||
// (there might be more than 1 if bind is used; see
|
||||
// https://github.com/caddyserver/caddy/issues/3443)
|
||||
redirDomains := make(map[string][]caddy.NetworkAddress)
|
||||
|
||||
for srvName, srv := range app.Servers {
|
||||
// as a prerequisite, provision route matchers; this is
|
||||
@@ -115,6 +131,7 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
}
|
||||
|
||||
// find all qualifying domain names (deduplicated) in this server
|
||||
// (this is where we need the provisioned, decoded request matchers)
|
||||
serverDomainSet := make(map[string]struct{})
|
||||
for routeIdx, route := range srv.Routes {
|
||||
for matcherSetIdx, matcherSet := range route.MatcherSets {
|
||||
@@ -136,9 +153,14 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
}
|
||||
}
|
||||
|
||||
// nothing more to do here if there are no
|
||||
// domains that qualify for automatic HTTPS
|
||||
if len(serverDomainSet) == 0 {
|
||||
// nothing more to do here if there are no domains that qualify for
|
||||
// automatic HTTPS and there are no explicit TLS connection policies:
|
||||
// if there is at least one domain but no TLS conn policy (F&&T), we'll
|
||||
// add one below; if there are no domains but at least one TLS conn
|
||||
// policy (meaning TLS is enabled) (T&&F), it could be a catch-all with
|
||||
// on-demand TLS -- and in that case we would still need HTTP->HTTPS
|
||||
// redirects, which we set up below; hence these two conditions
|
||||
if len(serverDomainSet) == 0 && len(srv.TLSConnPolicies) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -193,13 +215,24 @@ func (app *App) automaticHTTPSPhase1(ctx caddy.Context, repl *caddy.Replacer) er
|
||||
return fmt.Errorf("%s: invalid listener address: %v", srvName, addr)
|
||||
}
|
||||
|
||||
// this address might not have a hostname, i.e. might be a
|
||||
// catch-all address for a particular port; we need to keep
|
||||
// track if it is, so we can set up redirects for it anyway
|
||||
// (e.g. the user might have enabled on-demand TLS); we use
|
||||
// an empty string to indicate a catch-all, which we have to
|
||||
// treat special later
|
||||
if len(serverDomainSet) == 0 {
|
||||
redirDomains[""] = append(redirDomains[""], addr)
|
||||
continue
|
||||
}
|
||||
|
||||
// ...and associate it with each domain in this server
|
||||
for d := range serverDomainSet {
|
||||
// if this domain is used on more than one HTTPS-enabled
|
||||
// port, we'll have to choose one, so prefer the HTTPS port
|
||||
if _, ok := redirDomains[d]; !ok ||
|
||||
addr.StartPort == uint(app.httpsPort()) {
|
||||
redirDomains[d] = addr
|
||||
redirDomains[d] = append(redirDomains[d], addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,10 +251,10 @@ uniqueDomainsLoop:
|
||||
// some names we've found might already have automation policies
|
||||
// explicitly specified for them; we should exclude those from
|
||||
// our hidden/implicit policy, since applying a name to more than
|
||||
// one automation policy would be confusing and an error
|
||||
// one automation policy would be confusing and an error
|
||||
if app.tlsApp.Automation != nil {
|
||||
for _, ap := range app.tlsApp.Automation.Policies {
|
||||
for _, apHost := range ap.Hosts {
|
||||
for _, apHost := range ap.Subjects {
|
||||
if apHost == d {
|
||||
continue uniqueDomainsLoop
|
||||
}
|
||||
@@ -244,34 +277,32 @@ uniqueDomainsLoop:
|
||||
return err
|
||||
}
|
||||
|
||||
// we're done if there are no HTTP->HTTPS redirects to add
|
||||
if len(redirDomains) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to reduce the mapping, i.e. group domains by address
|
||||
// since new routes are appended to servers by their address
|
||||
domainsByAddr := make(map[string][]string)
|
||||
for domain, addr := range redirDomains {
|
||||
addrStr := addr.String()
|
||||
domainsByAddr[addrStr] = append(domainsByAddr[addrStr], domain)
|
||||
for domain, addrs := range redirDomains {
|
||||
for _, addr := range addrs {
|
||||
addrStr := addr.String()
|
||||
domainsByAddr[addrStr] = append(domainsByAddr[addrStr], domain)
|
||||
}
|
||||
}
|
||||
|
||||
// these keep track of the redirect server address(es)
|
||||
// and the routes for those servers which actually
|
||||
// respond with the redirects
|
||||
redirServerAddrs := make(map[string]struct{})
|
||||
redirServers := make(map[string][]Route)
|
||||
var redirRoutes RouteList
|
||||
|
||||
redirServers := make(map[string][]Route)
|
||||
|
||||
for addrStr, domains := range domainsByAddr {
|
||||
// build the matcher set for this redirect route
|
||||
// (note that we happen to bypass Provision and
|
||||
// Validate steps for these matcher modules)
|
||||
matcherSet := MatcherSet{
|
||||
MatchProtocol("http"),
|
||||
MatchHost(domains),
|
||||
// build the matcher set for this redirect route; (note that we happen
|
||||
// to bypass Provision and Validate steps for these matcher modules)
|
||||
matcherSet := MatcherSet{MatchProtocol("http")}
|
||||
// match on known domain names, unless it's our special case of a
|
||||
// catch-all which is an empty string (common among catch-all sites
|
||||
// that enable on-demand TLS for yet-unknown domain names)
|
||||
if !(len(domains) == 1 && domains[0] == "") {
|
||||
matcherSet = append(matcherSet, MatchHost(domains))
|
||||
}
|
||||
|
||||
// build the address to which to redirect
|
||||
@@ -318,6 +349,13 @@ uniqueDomainsLoop:
|
||||
// not entirely clear what the redirect destination should be,
|
||||
// so I'm going to just hard-code the app's HTTPS port and call
|
||||
// it good for now...
|
||||
// TODO: This implies that all plaintext requests will be blindly
|
||||
// redirected to their HTTPS equivalent, even if this server
|
||||
// doesn't handle that hostname at all; I don't think this is a
|
||||
// bad thing, and it also obscures the actual hostnames that this
|
||||
// server is configured to match on, which may be desirable, but
|
||||
// it's not something that should be relied on. We can change this
|
||||
// if we want to.
|
||||
appendCatchAll := func(routes []Route) []Route {
|
||||
redirTo := "https://{http.request.host}"
|
||||
if app.httpsPort() != DefaultHTTPSPort {
|
||||
@@ -325,7 +363,7 @@ uniqueDomainsLoop:
|
||||
}
|
||||
redirTo += "{http.request.uri}"
|
||||
routes = append(routes, Route{
|
||||
MatcherSets: []MatcherSet{MatcherSet{MatchProtocol("http")}},
|
||||
MatcherSets: []MatcherSet{{MatchProtocol("http")}},
|
||||
Handlers: []MiddlewareHandler{
|
||||
StaticResponse{
|
||||
StatusCode: WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
|
||||
@@ -393,98 +431,133 @@ redirServersLoop:
|
||||
// base for the new ones (this is important for preserving behavior the
|
||||
// user intends to be "defaults").
|
||||
func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, internalNames []string) error {
|
||||
// nothing to do if no names to manage certs for
|
||||
if len(publicNames) == 0 && len(internalNames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// start by finding a base policy that the user may have defined
|
||||
// which should, in theory, apply to any policies derived from it;
|
||||
// typically this would be a "catch-all" policy with no host filter
|
||||
// before we begin, loop through the existing automation policies
|
||||
// and, for any ACMEIssuers we find, make sure they're filled in
|
||||
// with default values that might be specified in our HTTP app; also
|
||||
// look for a base (or "catch-all" / default) automation policy,
|
||||
// which we're going to essentially require, to make sure it has
|
||||
// those defaults, too
|
||||
var basePolicy *caddytls.AutomationPolicy
|
||||
if app.tlsApp.Automation != nil {
|
||||
for _, ap := range app.tlsApp.Automation.Policies {
|
||||
// if an existing policy matches (specifically, a catch-all policy),
|
||||
// we should inherit from it, because that is what the user expects;
|
||||
// this is very common for user setting a default issuer, with a
|
||||
// custom CA endpoint, for example - whichever one we choose must
|
||||
// have a host list that is a superset of the policy we make...
|
||||
// the policy with no host filter is guaranteed to qualify
|
||||
if len(ap.Hosts) == 0 {
|
||||
basePolicy = ap
|
||||
break
|
||||
var foundBasePolicy bool
|
||||
if app.tlsApp.Automation == nil {
|
||||
// we will expect this to not be nil from now on
|
||||
app.tlsApp.Automation = new(caddytls.AutomationConfig)
|
||||
}
|
||||
for _, ap := range app.tlsApp.Automation.Policies {
|
||||
// set up default issuer -- honestly, this is only
|
||||
// really necessary because the HTTP app is opinionated
|
||||
// and has settings which could be inferred as new
|
||||
// defaults for the ACMEIssuer in the TLS app
|
||||
if ap.Issuer == nil {
|
||||
ap.Issuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if acmeIssuer, ok := ap.Issuer.(acmeCapable); ok {
|
||||
err := app.fillInACMEIssuer(acmeIssuer.GetACMEIssuer())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// while we're here, is this the catch-all/base policy?
|
||||
if !foundBasePolicy && len(ap.Subjects) == 0 {
|
||||
basePolicy = ap
|
||||
foundBasePolicy = true
|
||||
}
|
||||
}
|
||||
|
||||
if basePolicy == nil {
|
||||
// no base policy found, we will make one!
|
||||
basePolicy = new(caddytls.AutomationPolicy)
|
||||
}
|
||||
|
||||
// addPolicy adds an automation policy that uses issuer for hosts.
|
||||
addPolicy := func(issuer certmagic.Issuer, hosts []string) error {
|
||||
// shallow-copy the matching policy; we want to inherit
|
||||
// if the basePolicy has an existing ACMEIssuer (particularly to
|
||||
// include any type that embeds/wraps an ACMEIssuer), let's use it,
|
||||
// otherwise we'll make one
|
||||
var baseACMEIssuer *caddytls.ACMEIssuer
|
||||
if acmeWrapper, ok := basePolicy.Issuer.(acmeCapable); ok {
|
||||
baseACMEIssuer = acmeWrapper.GetACMEIssuer()
|
||||
}
|
||||
if baseACMEIssuer == nil {
|
||||
// note that this happens if basePolicy.Issuer is nil
|
||||
// OR if it is not nil but is not an ACMEIssuer
|
||||
baseACMEIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
|
||||
// if there was a base policy to begin with, we already
|
||||
// filled in its issuer's defaults; if there wasn't, we
|
||||
// stil need to do that
|
||||
if !foundBasePolicy {
|
||||
err := app.fillInACMEIssuer(baseACMEIssuer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// never overwrite any other issuer that might already be configured
|
||||
if basePolicy.Issuer == nil {
|
||||
basePolicy.Issuer = baseACMEIssuer
|
||||
}
|
||||
|
||||
if !foundBasePolicy {
|
||||
// there was no base policy to begin with, so add
|
||||
// our base/catch-all policy - this will serve the
|
||||
// public-looking names as well as any other names
|
||||
// that don't match any other policy
|
||||
app.tlsApp.AddAutomationPolicy(basePolicy)
|
||||
} else {
|
||||
// a base policy already existed; we might have
|
||||
// changed it, so re-provision it
|
||||
err := basePolicy.Provision(app.tlsApp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// public names will be taken care of by the base (catch-all)
|
||||
// policy, which we've ensured exists if not already specified;
|
||||
// internal names, however, need to be handled by an internal
|
||||
// issuer, which we need to make a new policy for, scoped to
|
||||
// just those names (yes, this logic is a bit asymmetric, but
|
||||
// it works, because our assumed/natural default issuer is an
|
||||
// ACME issuer)
|
||||
if len(internalNames) > 0 {
|
||||
internalIssuer := new(caddytls.InternalIssuer)
|
||||
|
||||
// shallow-copy the base policy; we want to inherit
|
||||
// from it, not replace it... this takes two lines to
|
||||
// overrule compiler optimizations
|
||||
policyCopy := *basePolicy
|
||||
newPolicy := &policyCopy
|
||||
|
||||
// very important to provision it, since we are
|
||||
// bypassing the JSON-unmarshaling step
|
||||
if prov, ok := issuer.(caddy.Provisioner); ok {
|
||||
err := prov.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// very important to provision the issuer, since we
|
||||
// are bypassing the JSON-unmarshaling step
|
||||
if err := internalIssuer.Provision(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
newPolicy.Issuer = issuer
|
||||
newPolicy.Hosts = hosts
|
||||
|
||||
return app.tlsApp.AddAutomationPolicy(newPolicy)
|
||||
}
|
||||
// this policy should apply only to the given names
|
||||
// and should use our issuer -- yes, this overrides
|
||||
// any issuer that may have been set in the base
|
||||
// policy, but we do this because these names do not
|
||||
// already have a policy associated with them, which
|
||||
// is easy to do; consider the case of a Caddyfile
|
||||
// that has only "localhost" as a name, but sets the
|
||||
// default/global ACME CA to the Let's Encrypt staging
|
||||
// endpoint... they probably don't intend to change the
|
||||
// fundamental set of names that setting applies to,
|
||||
// rather they just want to change the CA for the set
|
||||
// of names that would normally use the production API;
|
||||
// anyway, that gets into the weeds a bit...
|
||||
newPolicy.Subjects = internalNames
|
||||
newPolicy.Issuer = internalIssuer
|
||||
|
||||
if len(publicNames) > 0 {
|
||||
var acmeIssuer *caddytls.ACMEIssuer
|
||||
// if it has an ACME issuer, maybe we can just use that
|
||||
// TODO: we might need a deep copy here, like a Clone() method on ACMEIssuer...
|
||||
acmeIssuer, _ = basePolicy.Issuer.(*caddytls.ACMEIssuer)
|
||||
if acmeIssuer == nil {
|
||||
acmeIssuer = new(caddytls.ACMEIssuer)
|
||||
}
|
||||
if app.HTTPPort > 0 || app.HTTPSPort > 0 {
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
}
|
||||
if app.HTTPPort > 0 {
|
||||
if acmeIssuer.Challenges.HTTP == nil {
|
||||
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
|
||||
}
|
||||
// don't overwrite existing explicit config
|
||||
if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
|
||||
acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
|
||||
}
|
||||
}
|
||||
if app.HTTPSPort > 0 {
|
||||
if acmeIssuer.Challenges.TLSALPN == nil {
|
||||
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
|
||||
}
|
||||
// don't overwrite existing explicit config
|
||||
if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
|
||||
acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
|
||||
}
|
||||
}
|
||||
if err := addPolicy(acmeIssuer, publicNames); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(internalNames) > 0 {
|
||||
internalIssuer := new(caddytls.InternalIssuer)
|
||||
if err := addPolicy(internalIssuer, internalNames); err != nil {
|
||||
err := app.tlsApp.AddAutomationPolicy(newPolicy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// we just changed a lot of stuff, so double-check that it's all good
|
||||
err := app.tlsApp.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -493,6 +566,50 @@ func (app *App) createAutomationPolicies(ctx caddy.Context, publicNames, interna
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillInACMEIssuer fills in default values into acmeIssuer that
|
||||
// are defined in app; these values at time of writing are just
|
||||
// app.HTTPPort and app.HTTPSPort, which are used by ACMEIssuer.
|
||||
// Sure, we could just use the global/CertMagic defaults, but if
|
||||
// a user has configured those ports in the HTTP app, it makes
|
||||
// sense to use them in the TLS app too, even if they forgot (or
|
||||
// were too lazy, like me) to set it in each automation policy
|
||||
// that uses it -- this just makes things a little less tedious
|
||||
// for the user, so they don't have to repeat those ports in
|
||||
// potentially many places. This function never steps on existing
|
||||
// config values. If any changes are made, acmeIssuer is
|
||||
// reprovisioned. acmeIssuer must not be nil.
|
||||
func (app *App) fillInACMEIssuer(acmeIssuer *caddytls.ACMEIssuer) error {
|
||||
if app.HTTPPort > 0 || app.HTTPSPort > 0 {
|
||||
if acmeIssuer.Challenges == nil {
|
||||
acmeIssuer.Challenges = new(caddytls.ChallengesConfig)
|
||||
}
|
||||
}
|
||||
if app.HTTPPort > 0 {
|
||||
if acmeIssuer.Challenges.HTTP == nil {
|
||||
acmeIssuer.Challenges.HTTP = new(caddytls.HTTPChallengeConfig)
|
||||
}
|
||||
// don't overwrite existing explicit config
|
||||
if acmeIssuer.Challenges.HTTP.AlternatePort == 0 {
|
||||
acmeIssuer.Challenges.HTTP.AlternatePort = app.HTTPPort
|
||||
}
|
||||
}
|
||||
if app.HTTPSPort > 0 {
|
||||
if acmeIssuer.Challenges.TLSALPN == nil {
|
||||
acmeIssuer.Challenges.TLSALPN = new(caddytls.TLSALPNChallengeConfig)
|
||||
}
|
||||
// don't overwrite existing explicit config
|
||||
if acmeIssuer.Challenges.TLSALPN.AlternatePort == 0 {
|
||||
acmeIssuer.Challenges.TLSALPN.AlternatePort = app.HTTPSPort
|
||||
}
|
||||
}
|
||||
// we must provision all ACME issuers, even if nothing
|
||||
// was changed, because we don't know if they are new
|
||||
// and haven't been provisioned yet; if an ACME issuer
|
||||
// never gets provisioned, its Agree field stays false,
|
||||
// which leads to, um, problems later on
|
||||
return acmeIssuer.Provision(app.ctx)
|
||||
}
|
||||
|
||||
// automaticHTTPSPhase2 begins certificate management for
|
||||
// all names in the qualifying domain set for each server.
|
||||
// This phase must occur after provisioning and at the end
|
||||
@@ -517,3 +634,5 @@ func (app *App) automaticHTTPSPhase2() error {
|
||||
app.allCertDomains = nil // no longer needed; allow GC to deallocate
|
||||
return nil
|
||||
}
|
||||
|
||||
type acmeCapable interface{ GetACMEIssuer() *caddytls.ACMEIssuer }
|
||||
|
||||
@@ -16,15 +16,21 @@ package caddyauth
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
weakrand "math/rand"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(HTTPBasicAuth{})
|
||||
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// HTTPBasicAuth facilitates HTTP basic authentication.
|
||||
@@ -38,6 +44,17 @@ type HTTPBasicAuth struct {
|
||||
// The name of the realm. Default: restricted
|
||||
Realm string `json:"realm,omitempty"`
|
||||
|
||||
// If non-nil, a mapping of plaintext passwords to their
|
||||
// hashes will be cached in memory (with random eviction).
|
||||
// This can greatly improve the performance of traffic-heavy
|
||||
// servers that use secure password hashing algorithms, with
|
||||
// the downside that plaintext passwords will be stored in
|
||||
// memory for a longer time (this should not be a problem
|
||||
// as long as your machine is not compromised, at which point
|
||||
// all bets are off, since basicauth necessitates plaintext
|
||||
// passwords being received over the wire anyway).
|
||||
HashCache *Cache `json:"hash_cache,omitempty"`
|
||||
|
||||
Accounts map[string]Account `json:"-"`
|
||||
Hash Comparer `json:"-"`
|
||||
}
|
||||
@@ -77,8 +94,8 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
|
||||
acct.Username = repl.ReplaceAll(acct.Username, "")
|
||||
acct.Password = repl.ReplaceAll(string(acct.Password), "")
|
||||
acct.Salt = repl.ReplaceAll(string(acct.Salt), "")
|
||||
acct.Password = repl.ReplaceAll(acct.Password, "")
|
||||
acct.Salt = repl.ReplaceAll(acct.Salt, "")
|
||||
|
||||
if acct.Username == "" || acct.Password == "" {
|
||||
return fmt.Errorf("account %d: username and password are required", i)
|
||||
@@ -99,45 +116,126 @@ func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error {
|
||||
}
|
||||
hba.AccountList = nil // allow GC to deallocate
|
||||
|
||||
if hba.HashCache != nil {
|
||||
hba.HashCache.cache = make(map[string]bool)
|
||||
hba.HashCache.mu = new(sync.Mutex)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticate validates the user credentials in req and returns the user, if valid.
|
||||
func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) {
|
||||
username, plaintextPasswordStr, ok := req.BasicAuth()
|
||||
|
||||
// if basic auth is missing or invalid, prompt for credentials
|
||||
if !ok {
|
||||
// browsers show a message that says something like:
|
||||
// "The website says: <realm>"
|
||||
// which is kinda dumb, but whatever.
|
||||
realm := hba.Realm
|
||||
if realm == "" {
|
||||
realm = "restricted"
|
||||
}
|
||||
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||
|
||||
return User{}, false, nil
|
||||
return hba.promptForCredentials(w, nil)
|
||||
}
|
||||
|
||||
plaintextPassword := []byte(plaintextPasswordStr)
|
||||
|
||||
account, accountExists := hba.Accounts[username]
|
||||
// don't return early if account does not exist; we want
|
||||
// to try to avoid side-channels that leak existence
|
||||
|
||||
same, err := hba.Hash.Compare(account.password, plaintextPassword, account.salt)
|
||||
same, err := hba.correctPassword(account, []byte(plaintextPasswordStr))
|
||||
if err != nil {
|
||||
return User{}, false, err
|
||||
return hba.promptForCredentials(w, err)
|
||||
}
|
||||
if !same || !accountExists {
|
||||
return User{}, false, nil
|
||||
return hba.promptForCredentials(w, nil)
|
||||
}
|
||||
|
||||
return User{ID: username}, true, nil
|
||||
}
|
||||
|
||||
func (hba HTTPBasicAuth) correctPassword(account Account, plaintextPassword []byte) (bool, error) {
|
||||
compare := func() (bool, error) {
|
||||
return hba.Hash.Compare(account.password, plaintextPassword, account.salt)
|
||||
}
|
||||
|
||||
// if no caching is enabled, simply return the result of hashing + comparing
|
||||
if hba.HashCache == nil {
|
||||
return compare()
|
||||
}
|
||||
|
||||
// compute a cache key that is unique for these input parameters
|
||||
cacheKey := hex.EncodeToString(append(append(account.password, account.salt...), plaintextPassword...))
|
||||
|
||||
// fast track: if the result of the input is already cached, use it
|
||||
hba.HashCache.mu.Lock()
|
||||
same, ok := hba.HashCache.cache[cacheKey]
|
||||
if ok {
|
||||
hba.HashCache.mu.Unlock()
|
||||
return same, nil
|
||||
}
|
||||
hba.HashCache.mu.Unlock()
|
||||
|
||||
// slow track: do the expensive op, then add it to the cache
|
||||
same, err := compare()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
hba.HashCache.mu.Lock()
|
||||
if len(hba.HashCache.cache) >= 1000 {
|
||||
hba.HashCache.makeRoom() // keep cache size under control
|
||||
}
|
||||
hba.HashCache.cache[cacheKey] = same
|
||||
hba.HashCache.mu.Unlock()
|
||||
|
||||
return same, nil
|
||||
}
|
||||
|
||||
func (hba HTTPBasicAuth) promptForCredentials(w http.ResponseWriter, err error) (User, bool, error) {
|
||||
// browsers show a message that says something like:
|
||||
// "The website says: <realm>"
|
||||
// which is kinda dumb, but whatever.
|
||||
realm := hba.Realm
|
||||
if realm == "" {
|
||||
realm = "restricted"
|
||||
}
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm))
|
||||
return User{}, false, err
|
||||
}
|
||||
|
||||
// Cache enables caching of basic auth results. This is especially
|
||||
// helpful for secure password hashes which can be expensive to
|
||||
// compute on every HTTP request.
|
||||
type Cache struct {
|
||||
mu *sync.Mutex
|
||||
|
||||
// map of concatenated hashed password + plaintext password + salt, to result
|
||||
cache map[string]bool
|
||||
}
|
||||
|
||||
// makeRoom deletes about 1/10 of the items in the cache
|
||||
// in order to keep its size under control. It must not be
|
||||
// called without a lock on c.mu.
|
||||
func (c *Cache) makeRoom() {
|
||||
// we delete more than just 1 entry so that we don't have
|
||||
// to do this on every request; assuming the capacity of
|
||||
// the cache is on a long tail, we can save a lot of CPU
|
||||
// time by doing a whole bunch of deletions now and then
|
||||
// we won't have to do them again for a while
|
||||
numToDelete := len(c.cache) / 10
|
||||
if numToDelete < 1 {
|
||||
numToDelete = 1
|
||||
}
|
||||
for deleted := 0; deleted <= numToDelete; deleted++ {
|
||||
// Go maps are "nondeterministic" not actually random,
|
||||
// so although we could just chop off the "front" of the
|
||||
// map with less code, this is a heavily skewed eviction
|
||||
// strategy; generating random numbers is cheap and
|
||||
// ensures a much better distribution.
|
||||
rnd := weakrand.Intn(len(c.cache))
|
||||
i := 0
|
||||
for key := range c.cache {
|
||||
if i == rnd {
|
||||
delete(c.cache, key)
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Comparer is a type that can securely compare
|
||||
// a plaintext password with a hashed password
|
||||
// in constant-time. Comparers should hash the
|
||||
|
||||
@@ -29,6 +29,8 @@ func init() {
|
||||
|
||||
// Authentication is a middleware which provides user authentication.
|
||||
// Rejects requests with HTTP 401 if the request is not authenticated.
|
||||
//
|
||||
// Its API is still experimental and may be subject to change.
|
||||
type Authentication struct {
|
||||
// A set of authentication providers. If none are specified,
|
||||
// all requests will always be unauthenticated.
|
||||
@@ -77,7 +79,10 @@ func (a Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request, next c
|
||||
}
|
||||
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
repl.Set("http.authentication.user.id", user.ID)
|
||||
repl.Set("http.auth.user.id", user.ID)
|
||||
for k, v := range user.Metadata {
|
||||
repl.Set("http.auth.user."+k, v)
|
||||
}
|
||||
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -92,7 +97,15 @@ type Authenticator interface {
|
||||
|
||||
// User represents an authenticated user.
|
||||
type User struct {
|
||||
// The ID of the authenticated user.
|
||||
ID string
|
||||
|
||||
// Any other relevant data about this
|
||||
// user. Keys should be adhere to Caddy
|
||||
// conventions (snake_casing), as all
|
||||
// keys will be made available as
|
||||
// placeholders.
|
||||
Metadata map[string]string
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
|
||||
@@ -27,7 +27,7 @@ func init() {
|
||||
|
||||
// parseCaddyfile sets up the handler from Caddyfile tokens. Syntax:
|
||||
//
|
||||
// basicauth [<matcher>] [<hash_algorithm>] {
|
||||
// basicauth [<matcher>] [<hash_algorithm> [<realm>]] {
|
||||
// <username> <hashed_password_base64> [<salt_base64>]
|
||||
// ...
|
||||
// }
|
||||
@@ -35,6 +35,7 @@ func init() {
|
||||
// If no hash algorithm is supplied, bcrypt will be assumed.
|
||||
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
||||
var ba HTTPBasicAuth
|
||||
ba.HashCache = new(Cache)
|
||||
|
||||
for h.Next() {
|
||||
var cmp Comparer
|
||||
@@ -46,6 +47,9 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
hashName = "bcrypt"
|
||||
case 1:
|
||||
hashName = args[0]
|
||||
case 2:
|
||||
hashName = args[0]
|
||||
ba.Realm = args[1]
|
||||
default:
|
||||
return nil, h.ArgErr()
|
||||
}
|
||||
|
||||
@@ -15,26 +15,35 @@
|
||||
package caddyauth
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddycmd.RegisterCommand(caddycmd.Command{
|
||||
Name: "hash-password",
|
||||
Func: cmdHashPassword,
|
||||
Usage: "--plaintext <password> [--salt <string>] [--algorithm <name>]",
|
||||
Usage: "[--algorithm <name>] [--salt <string>] [--plaintext <password>]",
|
||||
Short: "Hashes a password and writes base64",
|
||||
Long: `
|
||||
Convenient way to hash a plaintext password. The resulting
|
||||
hash is written to stdout as a base64 string.
|
||||
|
||||
--plaintext, when omitted, will be read from stdin. If
|
||||
Caddy is attached to a controlling tty, the plaintext will
|
||||
not be echoed.
|
||||
|
||||
--algorithm may be bcrypt or scrypt. If script, the default
|
||||
parameters are used.
|
||||
|
||||
@@ -52,19 +61,62 @@ be provided (scrypt).
|
||||
}
|
||||
|
||||
func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||
var err error
|
||||
|
||||
algorithm := fs.String("algorithm")
|
||||
plaintext := []byte(fs.String("plaintext"))
|
||||
salt := []byte(fs.String("salt"))
|
||||
|
||||
if len(plaintext) == 0 {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("password is required")
|
||||
fd := int(os.Stdin.Fd())
|
||||
if terminal.IsTerminal(fd) {
|
||||
// ensure the terminal state is restored on SIGINT
|
||||
state, _ := terminal.GetState(fd)
|
||||
c := make(chan os.Signal)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
go func() {
|
||||
<-c
|
||||
_ = terminal.Restore(fd, state)
|
||||
os.Exit(caddy.ExitCodeFailedStartup)
|
||||
}()
|
||||
defer signal.Stop(c)
|
||||
|
||||
fmt.Fprint(os.Stderr, "Enter password: ")
|
||||
plaintext, err = terminal.ReadPassword(fd)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
fmt.Fprint(os.Stderr, "Confirm password: ")
|
||||
confirmation, err := terminal.ReadPassword(fd)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(plaintext, confirmation) {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("password does not match")
|
||||
}
|
||||
} else {
|
||||
rd := bufio.NewReader(os.Stdin)
|
||||
plaintext, err = rd.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
plaintext = plaintext[:len(plaintext)-1] // Trailing newline
|
||||
}
|
||||
|
||||
if len(plaintext) == 0 {
|
||||
return caddy.ExitCodeFailedStartup, fmt.Errorf("plaintext is required")
|
||||
}
|
||||
}
|
||||
|
||||
var hash []byte
|
||||
var err error
|
||||
switch algorithm {
|
||||
case "bcrypt":
|
||||
hash, err = bcrypt.GenerateFromPassword(plaintext, bcrypt.DefaultCost)
|
||||
hash, err = bcrypt.GenerateFromPassword(plaintext, 14)
|
||||
case "scrypt":
|
||||
def := ScryptHash{}
|
||||
def.SetDefaults()
|
||||
@@ -76,7 +128,7 @@ func cmdHashPassword(fs caddycmd.Flags) (int, error) {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
hashBase64 := base64.StdEncoding.EncodeToString([]byte(hash))
|
||||
hashBase64 := base64.StdEncoding.EncodeToString(hash)
|
||||
|
||||
fmt.Println(hashBase64)
|
||||
|
||||
|
||||
+57
-370
@@ -16,10 +16,7 @@ package caddyhttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
weakrand "math/rand"
|
||||
"net"
|
||||
@@ -28,359 +25,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||
"github.com/lucas-clemente/quic-go/http3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func init() {
|
||||
weakrand.Seed(time.Now().UnixNano())
|
||||
|
||||
err := caddy.RegisterModule(App{})
|
||||
if err != nil {
|
||||
caddy.Log().Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// App is a robust, production-ready HTTP server.
|
||||
//
|
||||
// HTTPS is enabled by default if host matchers with qualifying names are used
|
||||
// in any of routes; certificates are automatically provisioned and renewed.
|
||||
// Additionally, automatic HTTPS will also enable HTTPS for servers that listen
|
||||
// only on the HTTPS port but which do not have any TLS connection policies
|
||||
// defined by adding a good, default TLS connection policy.
|
||||
//
|
||||
// In HTTP routes, additional placeholders are available (replace any `*`):
|
||||
//
|
||||
// Placeholder | Description
|
||||
// ------------|---------------
|
||||
// `{http.request.cookie.*}` | HTTP request cookie
|
||||
// `{http.request.header.*}` | Specific request header field
|
||||
// `{http.request.host.labels.*}` | Request host labels (0-based from right); e.g. for foo.example.com: 0=com, 1=example, 2=foo
|
||||
// `{http.request.host}` | The host part of the request's Host header
|
||||
// `{http.request.hostport}` | The host and port from the request's Host header
|
||||
// `{http.request.method}` | The request method
|
||||
// `{http.request.orig_method}` | The request's original method
|
||||
// `{http.request.orig_uri.path.dir}` | The request's original directory
|
||||
// `{http.request.orig_uri.path.file}` | The request's original filename
|
||||
// `{http.request.orig_uri.path}` | The request's original path
|
||||
// `{http.request.orig_uri.query}` | The request's original query string (without `?`)
|
||||
// `{http.request.orig_uri}` | The request's original URI
|
||||
// `{http.request.port}` | The port part of the request's Host header
|
||||
// `{http.request.proto}` | The protocol of the request
|
||||
// `{http.request.remote.host}` | The host part of the remote client's address
|
||||
// `{http.request.remote.port}` | The port part of the remote client's address
|
||||
// `{http.request.remote}` | The address of the remote client
|
||||
// `{http.request.scheme}` | The request scheme
|
||||
// `{http.request.tls.version}` | The TLS version name
|
||||
// `{http.request.tls.cipher_suite}` | The TLS cipher suite
|
||||
// `{http.request.tls.resumed}` | The TLS connection resumed a previous connection
|
||||
// `{http.request.tls.proto}` | The negotiated next protocol
|
||||
// `{http.request.tls.proto_mutual}` | The negotiated next protocol was advertised by the server
|
||||
// `{http.request.tls.server_name}` | The server name requested by the client, if any
|
||||
// `{http.request.tls.client.fingerprint}` | The SHA256 checksum of the client certificate
|
||||
// `{http.request.tls.client.issuer}` | The issuer DN of the client certificate
|
||||
// `{http.request.tls.client.serial}` | The serial number of the client certificate
|
||||
// `{http.request.tls.client.subject}` | The subject DN of the client certificate
|
||||
// `{http.request.uri.path.*}` | Parts of the path, split by `/` (0-based from left)
|
||||
// `{http.request.uri.path.dir}` | The directory, excluding leaf filename
|
||||
// `{http.request.uri.path.file}` | The filename of the path, excluding directory
|
||||
// `{http.request.uri.path}` | The path component of the request URI
|
||||
// `{http.request.uri.query.*}` | Individual query string value
|
||||
// `{http.request.uri.query}` | The query string (without `?`)
|
||||
// `{http.request.uri}` | The full request URI
|
||||
// `{http.response.header.*}` | Specific response header field
|
||||
// `{http.vars.*}` | Custom variables in the HTTP handler chain
|
||||
type App struct {
|
||||
// HTTPPort specifies the port to use for HTTP (as opposed to HTTPS),
|
||||
// which is used when setting up HTTP->HTTPS redirects or ACME HTTP
|
||||
// challenge solvers. Default: 80.
|
||||
HTTPPort int `json:"http_port,omitempty"`
|
||||
|
||||
// HTTPSPort specifies the port to use for HTTPS, which is used when
|
||||
// solving the ACME TLS-ALPN challenges, or whenever HTTPS is needed
|
||||
// but no specific port number is given. Default: 443.
|
||||
HTTPSPort int `json:"https_port,omitempty"`
|
||||
|
||||
// GracePeriod is how long to wait for active connections when shutting
|
||||
// down the server. Once the grace period is over, connections will
|
||||
// be forcefully closed.
|
||||
GracePeriod caddy.Duration `json:"grace_period,omitempty"`
|
||||
|
||||
// Servers is the list of servers, keyed by arbitrary names chosen
|
||||
// at your discretion for your own convenience; the keys do not
|
||||
// affect functionality.
|
||||
Servers map[string]*Server `json:"servers,omitempty"`
|
||||
|
||||
servers []*http.Server
|
||||
h3servers []*http3.Server
|
||||
h3listeners []net.PacketConn
|
||||
|
||||
ctx caddy.Context
|
||||
logger *zap.Logger
|
||||
tlsApp *caddytls.TLS
|
||||
|
||||
// used temporarily between phases 1 and 2 of auto HTTPS
|
||||
allCertDomains []string
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (App) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http",
|
||||
New: func() caddy.Module { return new(App) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up the app.
|
||||
func (app *App) Provision(ctx caddy.Context) error {
|
||||
// store some references
|
||||
tlsAppIface, err := ctx.App("tls")
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting tls app: %v", err)
|
||||
}
|
||||
app.tlsApp = tlsAppIface.(*caddytls.TLS)
|
||||
app.ctx = ctx
|
||||
app.logger = ctx.Logger(app)
|
||||
|
||||
repl := caddy.NewReplacer()
|
||||
|
||||
// this provisions the matchers for each route,
|
||||
// and prepares auto HTTP->HTTPS redirects, and
|
||||
// is required before we provision each server
|
||||
err = app.automaticHTTPSPhase1(ctx, repl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// prepare each server
|
||||
for srvName, srv := range app.Servers {
|
||||
srv.tlsApp = app.tlsApp
|
||||
srv.logger = app.logger.Named("log")
|
||||
srv.errorLogger = app.logger.Named("log.error")
|
||||
|
||||
// only enable access logs if configured
|
||||
if srv.Logs != nil {
|
||||
srv.accessLogger = app.logger.Named("log.access")
|
||||
}
|
||||
|
||||
// if not explicitly configured by the user, disallow TLS
|
||||
// client auth bypass (domain fronting) which could
|
||||
// otherwise be exploited by sending an unprotected SNI
|
||||
// value during a TLS handshake, then putting a protected
|
||||
// domain in the Host header after establishing connection;
|
||||
// this is a safe default, but we allow users to override
|
||||
// it for example in the case of running a proxy where
|
||||
// domain fronting is desired and access is not restricted
|
||||
// based on hostname
|
||||
if srv.StrictSNIHost == nil && srv.hasTLSClientAuth() {
|
||||
app.logger.Info("enabling strict SNI-Host matching because TLS client auth is configured",
|
||||
zap.String("server_name", srvName),
|
||||
)
|
||||
trueBool := true
|
||||
srv.StrictSNIHost = &trueBool
|
||||
}
|
||||
|
||||
for i := range srv.Listen {
|
||||
lnOut, err := repl.ReplaceOrErr(srv.Listen[i], true, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s, listener %d: %v",
|
||||
srvName, i, err)
|
||||
}
|
||||
srv.Listen[i] = lnOut
|
||||
}
|
||||
|
||||
// pre-compile the primary handler chain, and be sure to wrap it in our
|
||||
// route handler so that important security checks are done, etc.
|
||||
primaryRoute := emptyHandler
|
||||
if srv.Routes != nil {
|
||||
err := srv.Routes.ProvisionHandlers(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up route handlers: %v", srvName, err)
|
||||
}
|
||||
primaryRoute = srv.Routes.Compile(emptyHandler)
|
||||
}
|
||||
srv.primaryHandlerChain = srv.wrapPrimaryRoute(primaryRoute)
|
||||
|
||||
// pre-compile the error handler chain
|
||||
if srv.Errors != nil {
|
||||
err := srv.Errors.Routes.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up server error handling routes: %v", srvName, err)
|
||||
}
|
||||
srv.errorHandlerChain = srv.Errors.Routes.Compile(errorEmptyHandler)
|
||||
}
|
||||
|
||||
// prepare the TLS connection policies
|
||||
err = srv.TLSConnPolicies.Provision(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("server %s: setting up TLS connection policies: %v", srvName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate ensures the app's configuration is valid.
|
||||
func (app *App) Validate() error {
|
||||
// each server must use distinct listener addresses
|
||||
lnAddrs := make(map[string]string)
|
||||
for srvName, srv := range app.Servers {
|
||||
for _, addr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid listener address '%s': %v", addr, err)
|
||||
}
|
||||
// check that every address in the port range is unique to this server;
|
||||
// we do not use <= here because PortRangeSize() adds 1 to EndPort for us
|
||||
for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
|
||||
addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
|
||||
if sn, ok := lnAddrs[addr]; ok {
|
||||
return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
|
||||
}
|
||||
lnAddrs[addr] = srvName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start runs the app. It finishes automatic HTTPS if enabled,
|
||||
// including management of certificates.
|
||||
func (app *App) Start() error {
|
||||
for srvName, srv := range app.Servers {
|
||||
s := &http.Server{
|
||||
ReadTimeout: time.Duration(srv.ReadTimeout),
|
||||
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
|
||||
WriteTimeout: time.Duration(srv.WriteTimeout),
|
||||
IdleTimeout: time.Duration(srv.IdleTimeout),
|
||||
MaxHeaderBytes: srv.MaxHeaderBytes,
|
||||
Handler: srv,
|
||||
}
|
||||
|
||||
for _, lnAddr := range srv.Listen {
|
||||
listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
|
||||
}
|
||||
for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
|
||||
hostport := listenAddr.JoinHostPort(portOffset)
|
||||
ln, err := caddy.Listen(listenAddr.Network, hostport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: listening on %s: %v", listenAddr.Network, hostport, err)
|
||||
}
|
||||
|
||||
// enable TLS if there is a policy and if this is not the HTTP port
|
||||
useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
|
||||
if useTLS {
|
||||
// create TLS listener
|
||||
tlsCfg := srv.TLSConnPolicies.TLSConfig(app.ctx)
|
||||
ln = tls.NewListener(ln, tlsCfg)
|
||||
|
||||
/////////
|
||||
// TODO: HTTP/3 support is experimental for now
|
||||
if srv.ExperimentalHTTP3 {
|
||||
app.logger.Info("enabling experimental HTTP/3 listener",
|
||||
zap.String("addr", hostport),
|
||||
)
|
||||
h3ln, err := caddy.ListenPacket("udp", hostport)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting HTTP/3 UDP listener: %v", err)
|
||||
}
|
||||
h3srv := &http3.Server{
|
||||
Server: &http.Server{
|
||||
Addr: hostport,
|
||||
Handler: srv,
|
||||
TLSConfig: tlsCfg,
|
||||
},
|
||||
}
|
||||
go h3srv.Serve(h3ln)
|
||||
app.h3servers = append(app.h3servers, h3srv)
|
||||
app.h3listeners = append(app.h3listeners, h3ln)
|
||||
srv.h3server = h3srv
|
||||
}
|
||||
/////////
|
||||
}
|
||||
|
||||
app.logger.Debug("starting server loop",
|
||||
zap.String("address", lnAddr),
|
||||
zap.Bool("http3", srv.ExperimentalHTTP3),
|
||||
zap.Bool("tls", useTLS),
|
||||
)
|
||||
|
||||
go s.Serve(ln)
|
||||
app.servers = append(app.servers, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finish automatic HTTPS by finally beginning
|
||||
// certificate management
|
||||
err := app.automaticHTTPSPhase2()
|
||||
if err != nil {
|
||||
return fmt.Errorf("finalizing automatic HTTPS: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the HTTP server.
|
||||
func (app *App) Stop() error {
|
||||
ctx := context.Background()
|
||||
if app.GracePeriod > 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(ctx, time.Duration(app.GracePeriod))
|
||||
defer cancel()
|
||||
}
|
||||
for _, s := range app.servers {
|
||||
err := s.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// close the http3 servers; it's unclear whether the bug reported in
|
||||
// https://github.com/caddyserver/caddy/pull/2727#issuecomment-526856566
|
||||
// was ever truly fixed, since it seemed racey/nondeterministic; but
|
||||
// recent tests in 2020 were unable to replicate the issue again after
|
||||
// repeated attempts (the bug manifested after a config reload; i.e.
|
||||
// reusing a http3 server or listener was problematic), but it seems
|
||||
// to be working fine now
|
||||
for _, s := range app.h3servers {
|
||||
// TODO: CloseGracefully, once implemented upstream
|
||||
// (see https://github.com/lucas-clemente/quic-go/issues/2103)
|
||||
err := s.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// closing an http3.Server does not close their underlying listeners
|
||||
// since apparently the listener can be used both by servers and
|
||||
// clients at the same time; so we need to manually call Close()
|
||||
// on the underlying h3 listeners (see lucas-clemente/quic-go#2103)
|
||||
for _, pc := range app.h3listeners {
|
||||
err := pc.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *App) httpPort() int {
|
||||
if app.HTTPPort == 0 {
|
||||
return DefaultHTTPPort
|
||||
}
|
||||
return app.HTTPPort
|
||||
}
|
||||
|
||||
func (app *App) httpsPort() int {
|
||||
if app.HTTPSPort == 0 {
|
||||
return DefaultHTTPSPort
|
||||
}
|
||||
return app.HTTPSPort
|
||||
caddy.RegisterModule(tlsPlaceholderWrapper{})
|
||||
}
|
||||
|
||||
// RequestMatcher is a type that can match to a request.
|
||||
@@ -442,6 +92,45 @@ var errorEmptyHandler Handler = HandlerFunc(func(w http.ResponseWriter, r *http.
|
||||
return nil
|
||||
})
|
||||
|
||||
// ResponseHandler pairs a response matcher with custom handling
|
||||
// logic. Either the status code can be changed to something else
|
||||
// while using the original response body, or, if a status code
|
||||
// is not set, it can execute a custom route list; this is useful
|
||||
// for executing handler routes based on the properties of an HTTP
|
||||
// response that has not been written out to the client yet.
|
||||
//
|
||||
// To use this type, provision it at module load time, then when
|
||||
// ready to use, match the response against its matcher; if it
|
||||
// matches (or doesn't have a matcher), change the status code on
|
||||
// the response if configured; otherwise invoke the routes by
|
||||
// calling `rh.Routes.Compile(next).ServeHTTP(rw, req)` (or similar).
|
||||
type ResponseHandler struct {
|
||||
// The response matcher for this handler. If empty/nil,
|
||||
// it always matches.
|
||||
Match *ResponseMatcher `json:"match,omitempty"`
|
||||
|
||||
// To write the original response body but with a different
|
||||
// status code, set this field to the desired status code.
|
||||
// If set, this takes priority over routes.
|
||||
StatusCode WeakString `json:"status_code,omitempty"`
|
||||
|
||||
// The list of HTTP routes to execute if no status code is
|
||||
// specified. If evaluated, the original response body
|
||||
// will not be written.
|
||||
Routes RouteList `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// Provision sets up the routse in rh.
|
||||
func (rh *ResponseHandler) Provision(ctx caddy.Context) error {
|
||||
if rh.Routes != nil {
|
||||
err := rh.Routes.Provision(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WeakString is a type that unmarshals any JSON value
|
||||
// as a string literal, with the following exceptions:
|
||||
//
|
||||
@@ -515,19 +204,6 @@ func (ws WeakString) String() string {
|
||||
return string(ws)
|
||||
}
|
||||
|
||||
// CopyHeader copies HTTP headers by completely
|
||||
// replacing dest with src. (This allows deletions
|
||||
// to be propagated, assuming src started as a
|
||||
// consistent copy of dest.)
|
||||
func CopyHeader(dest, src http.Header) {
|
||||
for field := range dest {
|
||||
delete(dest, field)
|
||||
}
|
||||
for field, val := range src {
|
||||
dest[field] = val
|
||||
}
|
||||
}
|
||||
|
||||
// StatusCodeMatches returns true if a real HTTP status code matches
|
||||
// the configured status code, which may be either a real HTTP status
|
||||
// code or an integer representing a class of codes (e.g. 4 for all
|
||||
@@ -544,6 +220,21 @@ func StatusCodeMatches(actual, configured int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// tlsPlaceholderWrapper is a no-op listener wrapper that marks
|
||||
// where the TLS listener should be in a chain of listener wrappers.
|
||||
// It should only be used if another listener wrapper must be placed
|
||||
// in front of the TLS handshake.
|
||||
type tlsPlaceholderWrapper struct{}
|
||||
|
||||
func (tlsPlaceholderWrapper) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "caddy.listeners.tls",
|
||||
New: func() caddy.Module { return new(tlsPlaceholderWrapper) },
|
||||
}
|
||||
}
|
||||
|
||||
func (tlsPlaceholderWrapper) WrapListener(ln net.Listener) net.Listener { return ln }
|
||||
|
||||
const (
|
||||
// DefaultHTTPPort is the default port for HTTP.
|
||||
DefaultHTTPPort = 80
|
||||
@@ -552,9 +243,5 @@ const (
|
||||
DefaultHTTPSPort = 443
|
||||
)
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.App = (*App)(nil)
|
||||
_ caddy.Provisioner = (*App)(nil)
|
||||
_ caddy.Validator = (*App)(nil)
|
||||
)
|
||||
// Interface guard
|
||||
var _ caddy.ListenerWrapper = (*tlsPlaceholderWrapper)(nil)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddyhttp
|
||||
|
||||
import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter/functions"
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
timestamp "google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(MatchExpression{})
|
||||
}
|
||||
|
||||
// MatchExpression matches requests by evaluating a
|
||||
// [CEL](https://github.com/google/cel-spec) expression.
|
||||
// This enables complex logic to be expressed using a comfortable,
|
||||
// familiar syntax. Please refer to
|
||||
// [the standard definitions of CEL functions and operators](https://github.com/google/cel-spec/blob/master/doc/langdef.md#standard-definitions).
|
||||
//
|
||||
// This matcher's JSON interface is actually a string, not a struct.
|
||||
// The generated docs are not correct because this type has custom
|
||||
// marshaling logic.
|
||||
//
|
||||
// COMPATIBILITY NOTE: This module is still experimental and is not
|
||||
// subject to Caddy's compatibility guarantee.
|
||||
type MatchExpression struct {
|
||||
// The CEL expression to evaluate. Any Caddy placeholders
|
||||
// will be expanded and situated into proper CEL function
|
||||
// calls before evaluating.
|
||||
Expr string
|
||||
|
||||
expandedExpr string
|
||||
prg cel.Program
|
||||
ta ref.TypeAdapter
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (MatchExpression) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.matchers.expression",
|
||||
New: func() caddy.Module { return new(MatchExpression) },
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON marshals m's expression.
|
||||
func (m MatchExpression) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(m.Expr)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals m's expression.
|
||||
func (m *MatchExpression) UnmarshalJSON(data []byte) error {
|
||||
return json.Unmarshal(data, &m.Expr)
|
||||
}
|
||||
|
||||
// Provision sets ups m.
|
||||
func (m *MatchExpression) Provision(_ caddy.Context) error {
|
||||
// replace placeholders with a function call - this is just some
|
||||
// light (and possibly naïve) syntactic sugar
|
||||
m.expandedExpr = placeholderRegexp.ReplaceAllString(m.Expr, placeholderExpansion)
|
||||
|
||||
// our type adapter expands CEL's standard type support
|
||||
m.ta = celTypeAdapter{}
|
||||
|
||||
// create the CEL environment
|
||||
env, err := cel.NewEnv(
|
||||
cel.Declarations(
|
||||
decls.NewVar("request", httpRequestObjectType),
|
||||
decls.NewFunction(placeholderFuncName,
|
||||
decls.NewOverload(placeholderFuncName+"_httpRequest_string",
|
||||
[]*exprpb.Type{httpRequestObjectType, decls.String},
|
||||
decls.Any)),
|
||||
),
|
||||
cel.CustomTypeAdapter(m.ta),
|
||||
ext.Strings(),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting up CEL environment: %v", err)
|
||||
}
|
||||
|
||||
// parse and type-check the expression
|
||||
checked, issues := env.Compile(m.expandedExpr)
|
||||
if issues != nil && issues.Err() != nil {
|
||||
return fmt.Errorf("compiling CEL program: %s", issues.Err())
|
||||
}
|
||||
|
||||
// request matching is a boolean operation, so we don't really know
|
||||
// what to do if the expression returns a non-boolean type
|
||||
if !proto.Equal(checked.ResultType(), decls.Bool) {
|
||||
return fmt.Errorf("CEL request matcher expects return type of bool, not %s", checked.ResultType())
|
||||
}
|
||||
|
||||
// compile the "program"
|
||||
m.prg, err = env.Program(checked,
|
||||
cel.Functions(
|
||||
&functions.Overload{
|
||||
Operator: placeholderFuncName,
|
||||
Binary: m.caddyPlaceholderFunc,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("compiling CEL program: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Match returns true if r matches m.
|
||||
func (m MatchExpression) Match(r *http.Request) bool {
|
||||
out, _, _ := m.prg.Eval(map[string]interface{}{
|
||||
"request": celHTTPRequest{r},
|
||||
})
|
||||
if outBool, ok := out.Value().(bool); ok {
|
||||
return outBool
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
||||
func (m *MatchExpression) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
m.Expr = strings.Join(d.RemainingArgs(), " ")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// caddyPlaceholderFunc implements the custom CEL function that accesses the
|
||||
// Replacer on a request and gets values from it.
|
||||
func (m MatchExpression) caddyPlaceholderFunc(lhs, rhs ref.Val) ref.Val {
|
||||
celReq, ok := lhs.(celHTTPRequest)
|
||||
if !ok {
|
||||
return types.NewErr(
|
||||
"invalid request of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||
lhs.Type())
|
||||
}
|
||||
phStr, ok := rhs.(types.String)
|
||||
if !ok {
|
||||
return types.NewErr(
|
||||
"invalid placeholder variable name of type '%v' to "+placeholderFuncName+"(request, placeholderVarName)",
|
||||
rhs.Type())
|
||||
}
|
||||
|
||||
repl := celReq.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
val, _ := repl.Get(string(phStr))
|
||||
|
||||
return m.ta.NativeToValue(val)
|
||||
}
|
||||
|
||||
// httpRequestCELType is the type representation of a native HTTP request.
|
||||
var httpRequestCELType = types.NewTypeValue("http.Request", traits.ReceiverType)
|
||||
|
||||
// cellHTTPRequest wraps an http.Request with
|
||||
// methods to satisfy the ref.Val interface.
|
||||
type celHTTPRequest struct{ *http.Request }
|
||||
|
||||
func (cr celHTTPRequest) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return cr.Request, nil
|
||||
}
|
||||
func (celHTTPRequest) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (cr celHTTPRequest) Equal(other ref.Val) ref.Val {
|
||||
if o, ok := other.Value().(celHTTPRequest); ok {
|
||||
return types.Bool(o.Request == cr.Request)
|
||||
}
|
||||
return types.ValOrErr(other, "%v is not comparable type", other)
|
||||
}
|
||||
func (celHTTPRequest) Type() ref.Type { return httpRequestCELType }
|
||||
func (cr celHTTPRequest) Value() interface{} { return cr }
|
||||
|
||||
var pkixNameCELType = types.NewTypeValue("pkix.Name", traits.ReceiverType)
|
||||
|
||||
// celPkixName wraps an pkix.Name with
|
||||
// methods to satisfy the ref.Val interface.
|
||||
type celPkixName struct{ *pkix.Name }
|
||||
|
||||
func (pn celPkixName) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||
return pn.Name, nil
|
||||
}
|
||||
func (celPkixName) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (pn celPkixName) Equal(other ref.Val) ref.Val {
|
||||
if o, ok := other.Value().(string); ok {
|
||||
return types.Bool(pn.Name.String() == o)
|
||||
}
|
||||
return types.ValOrErr(other, "%v is not comparable type", other)
|
||||
}
|
||||
func (celPkixName) Type() ref.Type { return pkixNameCELType }
|
||||
func (pn celPkixName) Value() interface{} { return pn }
|
||||
|
||||
// celTypeAdapter can adapt our custom types to a CEL value.
|
||||
type celTypeAdapter struct{}
|
||||
|
||||
func (celTypeAdapter) NativeToValue(value interface{}) ref.Val {
|
||||
switch v := value.(type) {
|
||||
case celHTTPRequest:
|
||||
return v
|
||||
case pkix.Name:
|
||||
return celPkixName{&v}
|
||||
case time.Time:
|
||||
// TODO: eliminate direct protobuf dependency, sigh -- just wrap stdlib time.Time instead...
|
||||
return types.Timestamp{Timestamp: ×tamp.Timestamp{Seconds: v.Unix(), Nanos: int32(v.Nanosecond())}}
|
||||
case error:
|
||||
types.NewErr(v.Error())
|
||||
}
|
||||
return types.DefaultTypeAdapter.NativeToValue(value)
|
||||
}
|
||||
|
||||
// Variables used for replacing Caddy placeholders in CEL
|
||||
// expressions with a proper CEL function call; this is
|
||||
// just for syntactic sugar.
|
||||
var (
|
||||
placeholderRegexp = regexp.MustCompile(`{([\w.-]+)}`)
|
||||
placeholderExpansion = `caddyPlaceholder(request, "${1}")`
|
||||
)
|
||||
|
||||
var httpRequestObjectType = decls.NewObjectType("http.Request")
|
||||
|
||||
// The name of the CEL function which accesses Replacer values.
|
||||
const placeholderFuncName = "caddyPlaceholder"
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ caddy.Provisioner = (*MatchExpression)(nil)
|
||||
_ RequestMatcher = (*MatchExpression)(nil)
|
||||
_ caddyfile.Unmarshaler = (*MatchExpression)(nil)
|
||||
_ json.Marshaler = (*MatchExpression)(nil)
|
||||
_ json.Unmarshaler = (*MatchExpression)(nil)
|
||||
)
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddyhttp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
)
|
||||
|
||||
func TestMatchExpressionProvision(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expression *MatchExpression
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "boolean matches succeed",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.uri.query} != ''",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "reject expressions with non-boolean results",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.uri.query}",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchExpressionMatch(t *testing.T) {
|
||||
|
||||
clientCert := []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIB9jCCAV+gAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1DYWRk
|
||||
eSBUZXN0IENBMB4XDTE4MDcyNDIxMzUwNVoXDTI4MDcyMTIxMzUwNVowHTEbMBkG
|
||||
A1UEAwwSY2xpZW50LmxvY2FsZG9tYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||
iQKBgQDFDEpzF0ew68teT3xDzcUxVFaTII+jXH1ftHXxxP4BEYBU4q90qzeKFneF
|
||||
z83I0nC0WAQ45ZwHfhLMYHFzHPdxr6+jkvKPASf0J2v2HDJuTM1bHBbik5Ls5eq+
|
||||
fVZDP8o/VHKSBKxNs8Goc2NTsr5b07QTIpkRStQK+RJALk4x9QIDAQABo0swSTAJ
|
||||
BgNVHRMEAjAAMAsGA1UdDwQEAwIHgDAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8A
|
||||
AAEwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADgYEANSjz2Sk+
|
||||
eqp31wM9il1n+guTNyxJd+FzVAH+hCZE5K+tCgVDdVFUlDEHHbS/wqb2PSIoouLV
|
||||
3Q9fgDkiUod+uIK0IynzIKvw+Cjg+3nx6NQ0IM0zo8c7v398RzB4apbXKZyeeqUH
|
||||
9fNwfEi+OoXR6s+upSKobCmLGLGi9Na5s5g=
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression *MatchExpression
|
||||
wantErr bool
|
||||
wantResult bool
|
||||
clientCertificate []byte
|
||||
}{
|
||||
{
|
||||
name: "boolean matches succeed for placeholder http.request.tls.client.subject",
|
||||
expression: &MatchExpression{
|
||||
Expr: "{http.request.tls.client.subject} == 'CN=client.localdomain'",
|
||||
},
|
||||
clientCertificate: clientCert,
|
||||
wantResult: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := tt.expression.Provision(caddy.Context{}); (err != nil) != tt.wantErr {
|
||||
t.Errorf("MatchExpression.Provision() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest("GET", "https://example.com/foo", nil)
|
||||
repl := caddy.NewReplacer()
|
||||
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
|
||||
req = req.WithContext(ctx)
|
||||
addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
|
||||
|
||||
if tt.clientCertificate != nil {
|
||||
block, _ := pem.Decode(clientCert)
|
||||
if block == nil {
|
||||
t.Fatalf("failed to decode PEM certificate")
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode PEM certificate: %v", err)
|
||||
}
|
||||
|
||||
req.TLS = &tls.ConnectionState{
|
||||
PeerCertificates: []*x509.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
if tt.expression.Match(req) != tt.wantResult {
|
||||
t.Errorf("MatchExpression.Match() expected to return '%t', for expression : '%s'", tt.wantResult, tt.expression)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// Copyright 2015 Matthew Holt and The Caddy 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 caddybrotli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(Brotli{})
|
||||
}
|
||||
|
||||
// Brotli can create brotli encoders. Note that brotli
|
||||
// is not known for great encoding performance, and
|
||||
// its use during requests is discouraged; instead,
|
||||
// pre-compress the content instead.
|
||||
type Brotli struct {
|
||||
Quality *int `json:"quality,omitempty"`
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Brotli) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.encoders.brotli",
|
||||
New: func() caddy.Module { return new(Brotli) },
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile sets up the handler from Caddyfile tokens.
|
||||
func (b *Brotli) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
for d.Next() {
|
||||
if !d.NextArg() {
|
||||
continue
|
||||
}
|
||||
qualityStr := d.Val()
|
||||
quality, err := strconv.Atoi(qualityStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Quality = &quality
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates b's configuration.
|
||||
func (b Brotli) Validate() error {
|
||||
if b.Quality != nil {
|
||||
quality := *b.Quality
|
||||
if quality < brotli.BestSpeed {
|
||||
return fmt.Errorf("quality too low; must be >= %d", brotli.BestSpeed)
|
||||
}
|
||||
if quality > brotli.BestCompression {
|
||||
return fmt.Errorf("quality too high; must be <= %d", brotli.BestCompression)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcceptEncoding returns the name of the encoding as
|
||||
// used in the Accept-Encoding request headers.
|
||||
func (Brotli) AcceptEncoding() string { return "br" }
|
||||
|
||||
// NewEncoder returns a new brotli writer.
|
||||
func (b Brotli) NewEncoder() encode.Encoder {
|
||||
quality := brotli.DefaultCompression
|
||||
if b.Quality != nil {
|
||||
quality = *b.Quality
|
||||
}
|
||||
return brotli.NewWriterLevel(nil, quality)
|
||||
}
|
||||
|
||||
// Interface guards
|
||||
var (
|
||||
_ encode.Encoding = (*Brotli)(nil)
|
||||
_ caddy.Validator = (*Brotli)(nil)
|
||||
_ caddyfile.Unmarshaler = (*Brotli)(nil)
|
||||
)
|
||||
@@ -42,7 +42,6 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
||||
// encode [<matcher>] <formats...> {
|
||||
// gzip [<level>]
|
||||
// zstd
|
||||
// brotli [<quality>]
|
||||
// }
|
||||
//
|
||||
// Specifying the formats on the first line will use those formats' defaults.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user