Compare commits

..

92 Commits

Author SHA1 Message Date
Sudarsan Reddy 08a8101308 Release 2022.5.2 2022-05-30 09:03:01 +01:00
Sudarsan Reddy a2a4b06eb4 TUN-6304: Fixed some file permission issues 2022-05-29 13:00:31 +00:00
Devin Carr ec509e114a TUN-6292: Debug builds for cloudflared
Allow for cloudflared to be built with debug mode to be used with dlv exec
2022-05-26 11:13:59 -07:00
Igor Postelnik 7bc2462e36 TUN-6282: Upgrade golang to 1.17.10, go-boring to 1.17.9 2022-05-25 16:26:09 +00:00
Sudarsan Reddy 92f647d45c TUN-6285: Upload pkg assets to repos when cloudflared is released.
This effectively means that during every release going forwards, we'll
have assets for the linux releases and distros that we want to support.
2022-05-25 14:31:05 +01:00
Sudarsan Reddy b2ac885370 TUN-6209: Sign RPM packages
This PR uses a provided key to
- sign all the .rpms before they are uploaded to R2.
- detach signs the repomd.xml after createrepo is run.
2022-05-25 13:13:40 +00:00
Igor Postelnik 2c480a72db TUN-6280: Don't wrap qlog connection tracer for gatethering QUIC metrics since we're not writing qlog files. 2022-05-24 16:59:03 -05:00
Sudarsan Reddy 32739e9f98 TUN-6209: Improve feedback process if release_pkgs to deb and rpm fail
This PR mostly raises exceptions so we are aware if release deb or
release pkgs fail. It also makes release_version optional if backup pkgs
are not needed.
2022-05-24 13:20:17 +01:00
Sudarsan Reddy 7ce2bb8b2f TUN-6270: Import gpg keys from environment variables
We now keep the gpg key inputs configurable. This PR imports base64
encoded gpg details into the build environment and uses this information
to sign the linux builds.
2022-05-23 14:51:26 +01:00
João Oliveirinha 6f78ccde04 TUN-6250: Add upstream response status code to tracing span attributes 2022-05-18 15:40:48 +01:00
João Oliveirinha 26a7b59f6f TUN-6248: Fix panic in cloudflared during tracing when origin doesn't provide header map 2022-05-18 13:13:07 +01:00
Sudarsan Reddy 4b6437cc60 TUN-5943: Add RPM support
This PR extends release_pkgs.py to now also support uploading rpm based
assets to R2. The packages are not signed yet  and will be done in a
subsequent PR.

This PR
- Packs the .rpm assets into relevant directories
- Calls createrepo on them to make them yum repo ready
- Uploads them to R2
2022-05-12 16:41:51 +00:00
Nuno Diegues f7fd4ea71c TUN-6197: Publish to brew core should not try to open the browser
The publish to brew core prints a URL with a PR that does the change
in github to brew core formula for cloudflared. It then tries to open
the browser, which obviously fails in CI.
So this adds a flag for it to skip opening the browser.

It's not clear how the PR will be opened, it seems like it must be
done by a human.
But at least this won't fail the build.
2022-05-11 15:26:05 +01:00
João Oliveirinha 7bcab138c5 Release 2022.5.1 2022-05-11 10:31:07 +01:00
João Oliveirinha fa2234d639 TUN-6185: Fix tcpOverWSOriginService not using original scheme for String representation 2022-05-06 18:47:03 +01:00
João Oliveirinha 99d4e48656 TUN-6016: Push local managed tunnels configuration to the edge 2022-05-06 15:43:24 +00:00
Sudarsan Reddy 0180b6d733 TUN-6146: Release_pkgs is now a generic command line script 2022-05-06 15:14:53 +01:00
Sudarsan Reddy 9ef6191515 TUN-5945: Added support for Ubuntu releases 2022-05-06 00:54:08 +01:00
Sudarsan Reddy 2cf43abe8c TUN-6175: Simply debian packaging by structural upload
The way apt works is:

1. It looks at the release file based on the `deb` added to sources.list.
2. It uses this release file to find the relative location of Packages or Packages.gz
3. It uses the pool information from packages to find the relative location of where the .deb file is located and then downloads and installs it.

This PR seeks to take advantage of this information by simply arranging
the files in a way apt expects thereby eliminating the need for an
orchestrating endpoint.
2022-05-05 23:53:00 +00:00
Nuno Diegues 46c147a1b2 TUN-6166: Fix mocked QUIC transport for UDP proxy manager to return expected error 2022-05-04 21:39:51 +00:00
Sudarsan Reddy 1e71202c89 TUN-6054: Create and upload deb packages to R2
This PR does the following:
   1. Creates packages.gz, signed InRelease files for debs in
      built_artifacts for configured debian releases.
   2. Uploads them to Cloudflare R2.
   3. Adds a Workers KV entry that talks about where these assets are
      uploaded.
2022-05-04 08:59:05 +00:00
Nuno Diegues 8250708b37 TUN-6161: Set git user/email for brew core release 2022-05-03 09:20:26 +01:00
Sudarsan Reddy 7499e5fa00 Release 2022.5.0 2022-05-02 17:29:34 +01:00
Nuno Diegues e8fe34773c TUN-6158: Update golang.org/x/crypto 2022-05-02 09:34:39 +01:00
João Oliveirinha 3254d08173 TUN-6014: Add remote config flag as default feature 2022-04-28 12:00:47 +01:00
João Oliveirinha d68ad89159 TUN-6142: Add tunnel details support to RPC
This commit adds the tunnel details to RPC register connection response
so we can have access to some of the details associacted to the tunnel
that only the edge knows.

Currently this is limited to knowing if the tunnel is remotely managed
or not. In the future we could extend this with more information.
2022-04-27 18:47:27 +01:00
João Oliveirinha f3244db861 TUN-6130: Fix vendoring due to case sensitive typo in package 2022-04-21 22:37:16 +01:00
João Oliveirinha d22cb4a6ca TUN-6015: Add RPC method for pushing local config 2022-04-21 19:12:09 +00:00
cthuang 8f0498f66a TUN-6123: For a given connection with edge, close all datagram sessions through this connection when it's closed 2022-04-21 16:33:28 +00:00
Devin Carr a97233bb3e TUN-6030: Add ttfb span for origin http request 2022-04-21 15:49:22 +00:00
Jasmit Tarang 775c2bc93e VULN-8383 Bump yaml.v2 to yaml.v3 2022-04-20 20:29:34 +00:00
cthuang e4278bab97 TUN-6070: First connection retries other edge IPs if the error is quic timeout(likely due to firewall blocking UDP) 2022-04-19 12:37:32 +01:00
Devin Carr f81b0ee9e8 TUN-5990: Add otlp span export to response header 2022-04-18 16:25:56 +00:00
Nuno Diegues 8a07a900fd TUN-6000: Another fix for publishing to brew core 2022-04-12 17:38:13 +01:00
Nuno Diegues d727d3ade6 Release 2022.4.1 2022-04-12 15:00:51 +01:00
Nuno Diegues 7a6ab54fcb TUN-6043: Allow UI-managed Tunnels to fallback from QUIC but warn about that 2022-04-12 09:46:07 +01:00
Devin Carr def8f57dbc TUN-5989: Add in-memory otlp exporter 2022-04-11 19:38:01 +00:00
João Oliveirinha 9cde11f8e0 TUN-6038: Reduce buffer size used for proxying data
The buffer size was big to support a compression feature that we don't
use anymore.
As such, we can now reduce this and be more efficient with memory usage.
2022-04-11 18:01:16 +01:00
João Oliveirinha d1a4710aa2 TUN-6035: Reduce buffer size when proxying data 2022-04-11 14:41:33 +00:00
Piper McCorkle 0dc3428424 TUN-6000 add version argument to bump-formula-pr 2022-04-07 16:52:21 -05:00
Sudarsan Reddy b07b8b4d4b Release 2022.4.0 2022-04-07 14:27:31 +01:00
Sudarsan Reddy d433a0fa54 TUN-4130: cloudflared docker images now have a latest tag 2022-04-07 13:13:57 +00:00
Nuno Diegues 2f05f969e2 TUN-5995: Force prometheus v1.12.1 usage 2022-04-07 10:51:52 +01:00
Silver 14002e44e1 Merge pull request #613 from misakano7545/master
Add s390x architecture support
2022-04-07 02:16:55 -05:00
Nuno Diegues b12272529f TUN-5995: Update prometheus to 1.12.1 to avoid vulnerabilities 2022-04-06 11:13:12 +00:00
Misaka No 377a9a8d27 Update Makefile 2022-04-06 17:43:58 +08:00
Nuno Diegues a0f6eb9d5e TUN-5992: Use QUIC protocol for remotely managed tunnels when protocol is unspecified 2022-04-05 23:07:10 +01:00
Sudarsan Reddy 12302ba1bf TUN-5973: Add backoff for non-recoverable errors as well
Errors that are non-recoverable can lead to one of two things happening:
1. That connection lying dead and cloudflared not retrying to make that
   connection.
2. cloudflared resolving to a different edge addr to retry connection.

We should subject these errors to a backoff as well. This will result in
us introducing a backoff for 1. When we are going to let the connection
become stale anyway and 2. When we are about to try a different edge
addr.
2022-04-01 15:58:51 +01:00
Silver 317a7ea7e5 Merge pull request #608 from alrs/carrier-websocket-errs
carrier: fix dropped errors
2022-03-30 11:03:26 -05:00
Lars Lehtonen 636ec75010 carrier: fix dropped errors 2022-03-30 07:09:09 -07:00
cthuang 98deb95eae TUN-5842: Fix flaky TestConcurrentUpdateAndRead by making sure resources are released 2022-03-30 11:09:27 +01:00
cthuang c0f85ab85b TUN-5956: Add timeout to session manager APIs 2022-03-28 13:39:03 +00:00
Nuno Diegues c5d1662244 TUN-5960: Do not log the tunnel token or json credentials 2022-03-28 10:54:39 +01:00
Nuno Diegues 8fd6074d67 TUN-5958: Fix release to homebrew core 2022-03-28 07:59:49 +00:00
Devin Carr 7e6fc49979 TUN-5959: tidy go.mod 2022-03-25 12:18:49 -07:00
Nuno Diegues eb6697ae98 Release 2022.3.4 2022-03-25 16:32:15 +00:00
Nuno Diegues 092e76eb55 TUN-5954: Start cloudflared service in Linux too similarly to other OSs 2022-03-25 13:57:47 +00:00
Nuno Diegues 62e1330e45 TUN-5933: Better messaging to help user when installing service if it is already installed 2022-03-24 17:19:23 +00:00
Nuno Diegues 98736a03e1 TUN-5915: New cloudflared command to allow to retrieve the token credentials for a Tunnel 2022-03-23 10:35:16 +00:00
Piper McCorkle 4836216a9b TUN-5895 run brew bump-formula-pr on release 2022-03-22 19:45:25 +00:00
Nuno Diegues 470e6c35c5 TUN-5918: Clean up text in cloudflared tunnel --help 2022-03-22 18:52:28 +00:00
Devin Carr e2a8302bbc TUN-5869: Add configuration endpoint in metrics server 2022-03-22 08:11:59 -07:00
Nuno Diegues 6eeaf4be4b Release 2022.3.3 2022-03-18 18:30:38 +00:00
Nuno Diegues 5e2e757403 TUN-5907: Change notes for 2022.3.3 2022-03-18 18:24:44 +00:00
João Oliveirinha 9422ea8ed8 CC-796: Remove dependency on unsupported version of go-oidc 2022-03-18 18:16:10 +00:00
Nuno Diegues 1b511b2d25 TUN-5881: Clarify success (or lack thereof) of (un)installing cloudflared service 2022-03-18 18:10:02 +00:00
João Oliveirinha 05b903a32e Revert "CC-796: Remove dependency on unsupported version of go-oidc"
This reverts commit 0899d6a136.
2022-03-18 10:03:58 +00:00
Igor Postelnik 398cc8b134 TUN-5893: Start windows service on install, stop on uninstall. Previously user had to manually start the service after running 'cloudflared tunnel install' and stop the service before running uninstall command. 2022-03-17 17:52:39 -05:00
Nuno Diegues e1a9e98cca Release 2022.3.2 2022-03-16 14:24:18 +00:00
Nuno Diegues 057a0cc758 TUN-5833: Send feature allow_remote_config if Tunnel is run with --token 2022-03-16 14:07:03 +00:00
cthuang ca43b0357f TUN-5867: Return error if service was already installed 2022-03-16 11:33:44 +00:00
Devin Carr 8cbd222e10 TUN-5703: Add prometheus metric for current configuration version 2022-03-14 16:50:23 +00:00
João Oliveirinha a50c0ca9ad TUN-5833: Create constant for allow-remote-config 2022-03-10 10:48:03 +00:00
João Oliveirinha 5352b3cf04 TUN-5801: Add custom wrapper for OriginConfig for JSON serde 2022-03-08 21:43:51 +00:00
Piper McCorkle 9552bb7bc7 TUN-5853 Add "install" make target and build package manager info into executable 2022-03-08 15:31:14 -06:00
Nuno Diegues c54e8cd8e6 TUN-5851: Update all references to point to Apache License 2.0 2022-03-08 17:35:31 +00:00
Nuno Diegues aeda35699e TUN-5850: Update CHANGES.md with latest releases 2022-03-08 17:35:11 +00:00
cthuang eee0d57ed0 TUN-5849: Remove configuration debug log 2022-03-08 11:03:46 +00:00
emmanuel 0899d6a136 CC-796: Remove dependency on unsupported version of go-oidc 2022-03-07 21:48:30 +00:00
Nuno Diegues f44e496dd9 Release 2022.3.1 2022-03-07 15:16:47 +00:00
Nuno Diegues 3aebaaad01 TUN-5836: QUIC transport no longer sets body to nil in any condition
Setting the body to nil was rendering cloudflared to crashing with
a SIGSEGV in the odd case where the hostname accessed maps to a
TCP origin (e.g. SSH/RDP/...) but the eyeball sends a plain HTTP
request that does not go through cloudflared access (thus not wrapped
in websocket as it should).

Instead, QUIC transport now sets http.noBody in that condition, which
deals with the situation gracefully.
2022-03-07 11:39:07 +00:00
Nuno Diegues 9d9627f645 TUN-5836: Avoid websocket#Stream function from crashing cloudflared with unexpected memory access 2022-03-04 18:42:41 +00:00
Sudarsan Reddy 5c6207debc TUN-5696: HTTP/2 Configuration Update 2022-03-04 12:28:55 +00:00
Nuno Diegues 7220c2c214 TUN-5837: Log panic recovery in http2 logic with debug level log 2022-03-04 11:52:45 +00:00
João Oliveirinha d17a61c15b Release 2022.3.0 2022-03-03 20:50:35 +00:00
João Oliveirinha 5431e0ca12 TUN-5680: Adapt component tests for new service install based on token 2022-03-03 19:18:59 +00:00
João Oliveirinha 706523389c TUN-5679: Add support for service install using Tunnel Token 2022-03-03 18:59:03 +00:00
Devin Carr c2a32de35f TUN-5737: Support https protocol over unix socket origin 2022-03-03 15:25:23 +00:00
Nuno Diegues a1d485eca5 TUN-5823: Warn about legacy flags that are ignored when ingress rules are used 2022-03-02 10:48:03 +00:00
Devin Carr 8a1ba1f8ca TUN-5824: Update updater no-update-in-shell link 2022-02-28 15:06:18 -06:00
João Oliveirinha b6d7076400 TUN-5681: Add support for running tunnel using Token 2022-02-23 10:09:45 +00:00
João Oliveirinha 22cd8ceb8c TUN-5682: Remove name field from credentials 2022-02-23 10:08:29 +00:00
427 changed files with 40528 additions and 6124 deletions
+2
View File
@@ -2,6 +2,8 @@ images:
- name: cloudflared
dockerfile: Dockerfile
context: .
versions:
- latest
registries:
- name: docker.io/cloudflare
user: env:DOCKER_USER
+1
View File
@@ -10,6 +10,7 @@ cscope.*
/cloudflared.exe
/cloudflared.msi
/cloudflared-x86-64*
/cloudflared.1
/packaging
.DS_Store
*-session.log
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
set -euo pipefail
if ! VERSION="$(git describe --tags --exact-match 2>/dev/null)" ; then
echo "Skipping public release for an untagged commit."
echo "##teamcity[buildStatus status='SUCCESS' text='Skipped due to lack of tag']"
exit 0
fi
if [[ "${HOMEBREW_GITHUB_API_TOKEN:-}" == "" ]] ; then
echo "Missing GITHUB_API_TOKEN"
exit 1
fi
# "install" Homebrew
git clone https://github.com/Homebrew/brew tmp/homebrew
eval "$(tmp/homebrew/bin/brew shellenv)"
brew update --force --quiet
chmod -R go-w "$(brew --prefix)/share/zsh"
git config --global user.name "cloudflare-warp-bot"
git config --global user.email "warp-bot@cloudflare.com"
# bump formula pr
brew bump-formula-pr cloudflared --version="$VERSION" --no-browse
+56 -1
View File
@@ -1,4 +1,59 @@
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
## 2022.4.0
### Bug Fixes
- `cloudflared tunnel run` no longer logs the Tunnel token or JSON credentials in clear text as those are the secret
that allows to run the Tunnel.
## 2022.3.4
### New Features
- It is now possible to retrieve the credentials that allow to run a Tunnel in case you forgot/lost them. This is
achievable with: `cloudflared tunnel token --cred-file /path/to/file.json TUNNEL`. This new feature only works for
Tunnels created with cloudflared version 2022.3.0 or more recent.
### Bug Fixes
- `cloudflared service install` now starts the underlying agent service on Linux operating system (similarly to the
behaviour in Windows and MacOS).
## 2022.3.3
### Bug Fixes
- `cloudflared service install` now starts the underlying agent service on Windows operating system (similarly to the
behaviour in MacOS).
## 2022.3.1
### Bug Fixes
- Various fixes to the reliability of `quic` protocol, including an edge case that could lead to cloudflared crashing.
## 2022.3.0
### New Features
- It is now possible to configure Ingress Rules to point to an origin served by unix socket with either HTTP or HTTPS.
If the origin starts with `unix:/` then we assume HTTP (existing behavior). Otherwise, the origin can start with
`unix+tls:/` for HTTPS.
## 2022.2.1
### New Features
- This project now has a new LICENSE that is more compliant with open source purposes.
### Bug Fixes
- Various fixes to the reliability of `quic` protocol.
## 2022.1.3
### New Features
- New `cloudflared tunnel vnet` commands to allow for private routing to be virtualized. This means that the same CIDR
can now be used to point to two different Tunnels with `cloudflared tunnel route ip` command. More information will be
made available on blog.cloudflare.com and developers.cloudflare.com/cloudflare-one once the feature is globally available.
### Bug Fixes
- Correctly handle proxying UDP datagrams with no payload.
- Bug fix for origins that use Server-Sent Events (SSE).
## 2022.1.0
### Improvements
- If a specific `protocol` property is defined (e.g. for `quic`), cloudflared no longer falls back to an older protocol
(such as `http2`) in face of connectivity errors. This is important because some features are only supported in a specific
protocol (e.g. UDP proxying only works for `quic`). Hence, if a user chooses a protocol, cloudflared now adheres to it
no matter what.
### Bug Fixes
- Stopping cloudflared running with `quic` protocol now respects graceful shutdown.
## 2021.12.2
### Bug Fixes
+169 -178
View File
@@ -1,211 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
1. Definitions.
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
1. Definitions.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
END OF TERMS AND CONDITIONS
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
APPENDIX: How to apply the Apache License to your work.
END OF TERMS AND CONDITIONS
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
APPENDIX: How to apply the Apache License to your work.
Copyright [yyyy] [name of copyright owner]
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
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
Copyright [yyyy] [name of copyright owner]
http://www.apache.org/licenses/LICENSE-2.0
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
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.
http://www.apache.org/licenses/LICENSE-2.0
## Runtime Library Exception to the Apache 2.0 License: ##
As an exception, if you use this Software to compile your source code and
portions of this Software are embedded into the binary product as a result,
you may redistribute such product without providing attribution as would
otherwise be required by Sections 4(a), 4(b) and 4(d) of the License.
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.
+33 -11
View File
@@ -23,6 +23,9 @@ endif
DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC')
VERSION_FLAGS := -X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"
ifdef PACKAGE_MANAGER
VERSION_FLAGS := $(VERSION_FLAGS) -X "github.com/cloudflare/cloudflared/cmd/cloudflared/updater.BuiltForPackageManager=$(PACKAGE_MANAGER)"
endif
LINK_FLAGS :=
ifeq ($(FIPS), true)
@@ -37,10 +40,15 @@ ifneq ($(GO_BUILD_TAGS),)
GO_BUILD_TAGS := -tags "$(GO_BUILD_TAGS)"
endif
IMPORT_PATH := github.com/cloudflare/cloudflared
PACKAGE_DIR := $(CURDIR)/packaging
INSTALL_BINDIR := /usr/bin/
MAN_DIR := /usr/share/man/man1/
ifeq ($(debug), 1)
GO_BUILD_TAGS += -gcflags="all=-N -l"
endif
IMPORT_PATH := github.com/cloudflare/cloudflared
PACKAGE_DIR := $(CURDIR)/packaging
PREFIX := /usr
INSTALL_BINDIR := $(PREFIX)/bin/
INSTALL_MANDIR := $(PREFIX)/share/man/man1/
LOCAL_ARCH ?= $(shell uname -m)
ifneq ($(GOARCH),)
@@ -59,6 +67,8 @@ else ifeq ($(LOCAL_ARCH),arm64)
TARGET_ARCH ?= arm64
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
TARGET_ARCH ?= arm
else ifeq ($(LOCAL_ARCH),s390x)
TARGET_ARCH ?= s390x
else
$(error This system's architecture $(LOCAL_ARCH) isn't supported)
endif
@@ -141,32 +151,40 @@ publish-deb: cloudflared-deb
publish-rpm: cloudflared-rpm
$(call publish_package,rpm,yum)
cloudflared.1: cloudflared_man_template
cat cloudflared_man_template | sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' > cloudflared.1
install: cloudflared cloudflared.1
mkdir -p $(DESTDIR)$(INSTALL_BINDIR) $(DESTDIR)$(INSTALL_MANDIR)
install -m755 cloudflared $(DESTDIR)$(INSTALL_BINDIR)/cloudflared
install -m644 cloudflared.1 $(DESTDIR)$(INSTALL_MANDIR)/cloudflared.1
# When we build packages, the package name will be FIPS-aware.
# But we keep the binary installed by it to be named "cloudflared" regardless.
define build_package
mkdir -p $(PACKAGE_DIR)
cp cloudflared $(PACKAGE_DIR)/cloudflared
cat cloudflared_man_template | sed -e 's/\$${VERSION}/$(VERSION)/; s/\$${DATE}/$(DATE)/' > $(PACKAGE_DIR)/cloudflared.1
cp cloudflared.1 $(PACKAGE_DIR)/cloudflared.1
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t $(1) \
--description 'Cloudflare Tunnel daemon' \
--vendor 'Cloudflare' \
--license 'Cloudflare Service Agreement' \
--license 'Apache License Version 2.0' \
--url 'https://github.com/cloudflare/cloudflared' \
-m 'Cloudflare <support@cloudflare.com>' \
-a $(TARGET_ARCH) -v $(VERSION) -n $(DEB_PACKAGE_NAME) $(NIGHTLY_FLAGS) --after-install postinst.sh --after-remove postrm.sh \
cloudflared=$(INSTALL_BINDIR) cloudflared.1=$(MAN_DIR)
cloudflared=$(INSTALL_BINDIR) cloudflared.1=$(INSTALL_MANDIR)
endef
.PHONY: cloudflared-deb
cloudflared-deb: cloudflared
cloudflared-deb: cloudflared cloudflared.1
$(call build_package,deb)
.PHONY: cloudflared-rpm
cloudflared-rpm: cloudflared
cloudflared-rpm: cloudflared cloudflared.1
$(call build_package,rpm)
.PHONY: cloudflared-pkg
cloudflared-pkg: cloudflared
cloudflared-pkg: cloudflared cloudflared.1
$(call build_package,osxpkg)
.PHONY: cloudflared-msi
@@ -247,6 +265,10 @@ github-release: cloudflared
github-release-built-pkgs:
python3 github_release.py --path $(PWD)/built_artifacts --release-version $(VERSION)
.PHONY: release-pkgs-linux
release-pkgs-linux:
python3 ./release_pkgs.py
.PHONY: github-message
github-message:
python3 github_message.py --release-version $(VERSION)
@@ -279,4 +301,4 @@ vet:
.PHONY: goimports
goimports:
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
for d in $$(go list -mod=readonly -f '{{.Dir}}' -a ./... | fgrep -v tunnelrpc) ; do goimports -format-only -local github.com/cloudflare/cloudflared -w $$d ; done
+99
View File
@@ -1,3 +1,102 @@
2022.5.2
- 2022-05-23 TUN-6270: Import gpg keys from environment variables
- 2022-05-24 TUN-6209: Improve feedback process if release_pkgs to deb and rpm fail
- 2022-05-24 TUN-6280: Don't wrap qlog connection tracer for gatethering QUIC metrics since we're not writing qlog files.
- 2022-05-25 TUN-6209: Sign RPM packages
- 2022-05-25 TUN-6285: Upload pkg assets to repos when cloudflared is released.
- 2022-05-24 TUN-6282: Upgrade golang to 1.17.10, go-boring to 1.17.9
- 2022-05-26 TUN-6292: Debug builds for cloudflared
- 2022-05-28 TUN-6304: Fixed some file permission issues
- 2022-05-11 TUN-6197: Publish to brew core should not try to open the browser
- 2022-05-12 TUN-5943: Add RPM support
- 2022-05-18 TUN-6248: Fix panic in cloudflared during tracing when origin doesn't provide header map
- 2022-05-18 TUN-6250: Add upstream response status code to tracing span attributes
2022.5.1
- 2022-05-06 TUN-6146: Release_pkgs is now a generic command line script
- 2022-05-06 TUN-6185: Fix tcpOverWSOriginService not using original scheme for String representation
- 2022-05-05 TUN-6175: Simply debian packaging by structural upload
- 2022-05-05 TUN-5945: Added support for Ubuntu releases
- 2022-05-04 TUN-6054: Create and upload deb packages to R2
- 2022-05-03 TUN-6161: Set git user/email for brew core release
- 2022-05-03 TUN-6166: Fix mocked QUIC transport for UDP proxy manager to return expected error
- 2022-04-27 TUN-6016: Push local managed tunnels configuration to the edge
2022.5.0
- 2022-05-02 TUN-6158: Update golang.org/x/crypto
- 2022-04-20 VULN-8383 Bump yaml.v2 to yaml.v3
- 2022-04-21 TUN-6123: For a given connection with edge, close all datagram sessions through this connection when it's closed
- 2022-04-20 TUN-6015: Add RPC method for pushing local config
- 2022-04-21 TUN-6130: Fix vendoring due to case sensitive typo in package
- 2022-04-27 TUN-6142: Add tunnel details support to RPC
- 2022-04-28 TUN-6014: Add remote config flag as default feature
- 2022-04-12 TUN-6000: Another fix for publishing to brew core
- 2022-04-11 TUN-5990: Add otlp span export to response header
- 2022-04-19 TUN-6070: First connection retries other edge IPs if the error is quic timeout(likely due to firewall blocking UDP)
- 2022-04-11 TUN-6030: Add ttfb span for origin http request
2022.4.1
- 2022-04-11 TUN-6035: Reduce buffer size when proxying data
- 2022-04-11 TUN-6038: Reduce buffer size used for proxying data
- 2022-04-11 TUN-6043: Allow UI-managed Tunnels to fallback from QUIC but warn about that
- 2022-04-07 TUN-6000 add version argument to bump-formula-pr
- 2022-04-06 TUN-5989: Add in-memory otlp exporter
2022.4.0
- 2022-04-01 TUN-5973: Add backoff for non-recoverable errors as well
- 2022-04-05 TUN-5992: Use QUIC protocol for remotely managed tunnels when protocol is unspecified
- 2022-04-06 Update Makefile
- 2022-04-06 TUN-5995: Update prometheus to 1.12.1 to avoid vulnerabilities
- 2022-04-07 TUN-5995: Force prometheus v1.12.1 usage
- 2022-04-07 TUN-4130: cloudflared docker images now have a latest tag
- 2022-03-30 TUN-5842: Fix flaky TestConcurrentUpdateAndRead by making sure resources are released
- 2022-03-30 carrier: fix dropped errors
- 2022-03-25 TUN-5959: tidy go.mod
- 2022-03-25 TUN-5958: Fix release to homebrew core
- 2022-03-28 TUN-5960: Do not log the tunnel token or json credentials
- 2022-03-28 TUN-5956: Add timeout to session manager APIs
2022.3.4
- 2022-03-22 TUN-5918: Clean up text in cloudflared tunnel --help
- 2022-03-22 TUN-5895 run brew bump-formula-pr on release
- 2022-03-22 TUN-5915: New cloudflared command to allow to retrieve the token credentials for a Tunnel
- 2022-03-24 TUN-5933: Better messaging to help user when installing service if it is already installed
- 2022-03-25 TUN-5954: Start cloudflared service in Linux too similarly to other OSs
- 2022-03-14 TUN-5869: Add configuration endpoint in metrics server
2022.3.3
- 2022-03-17 TUN-5893: Start windows service on install, stop on uninstall. Previously user had to manually start the service after running 'cloudflared tunnel install' and stop the service before running uninstall command.
- 2022-03-17 Revert "CC-796: Remove dependency on unsupported version of go-oidc"
- 2022-03-18 TUN-5881: Clarify success (or lack thereof) of (un)installing cloudflared service
- 2022-03-18 CC-796: Remove dependency on unsupported version of go-oidc
- 2022-03-18 TUN-5907: Change notes for 2022.3.3
2022.3.2
- 2022-03-10 TUN-5833: Create constant for allow-remote-config
- 2022-03-15 TUN-5867: Return error if service was already installed
- 2022-03-16 TUN-5833: Send feature `allow_remote_config` if Tunnel is run with --token
- 2022-03-08 TUN-5849: Remove configuration debug log
- 2022-03-08 TUN-5850: Update CHANGES.md with latest releases
- 2022-03-08 TUN-5851: Update all references to point to Apache License 2.0
- 2022-03-07 TUN-5853 Add "install" make target and build package manager info into executable
- 2022-03-08 TUN-5801: Add custom wrapper for OriginConfig for JSON serde
- 2022-03-09 TUN-5703: Add prometheus metric for current configuration version
- 2022-02-05 CC-796: Remove dependency on unsupported version of go-oidc
2022.3.1
- 2022-03-04 TUN-5837: Log panic recovery in http2 logic with debug level log
- 2022-03-04 TUN-5696: HTTP/2 Configuration Update
- 2022-03-04 TUN-5836: Avoid websocket#Stream function from crashing cloudflared with unexpected memory access
- 2022-03-05 TUN-5836: QUIC transport no longer sets body to nil in any condition
2022.3.0
- 2022-03-02 TUN-5680: Adapt component tests for new service install based on token
- 2022-02-21 TUN-5682: Remove name field from credentials
- 2022-02-21 TUN-5681: Add support for running tunnel using Token
- 2022-02-28 TUN-5824: Update updater no-update-in-shell link
- 2022-02-28 TUN-5823: Warn about legacy flags that are ignored when ingress rules are used
- 2022-02-28 TUN-5737: Support https protocol over unix socket origin
- 2022-02-23 TUN-5679: Add support for service install using Tunnel Token
2022.2.2
- 2022-02-22 TUN-5754: Allow ingress validate to take plaintext option
- 2022-02-17 TUN-5678: Cloudflared uses typed tunnel API
+6
View File
@@ -55,6 +55,9 @@ func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebso
}
dump, err := httputil.DumpRequest(req, false)
if err != nil {
return nil, err
}
log.Debug().Msgf("Websocket request: %s", string(dump))
dialer := &websocket.Dialer{
@@ -182,6 +185,9 @@ func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*w
}
dump, err := httputil.DumpRequest(req, false)
if err != nil {
return nil, nil, err
}
log.Debug().Msgf("Access Websocket request: %s", string(dump))
conn, resp, err := clientConnect(req, nil)
+2 -1
View File
@@ -5,8 +5,9 @@ import (
)
type TunnelClient interface {
CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error)
CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error)
GetTunnel(tunnelID uuid.UUID) (*Tunnel, error)
GetTunnelToken(tunnelID uuid.UUID) (string, error)
DeleteTunnel(tunnelID uuid.UUID) error
ListTunnels(filter *TunnelFilter) ([]*Tunnel, error)
ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error)
+28 -2
View File
@@ -23,6 +23,11 @@ type Tunnel struct {
Connections []Connection `json:"connections"`
}
type TunnelWithToken struct {
Tunnel
Token string `json:"token"`
}
type Connection struct {
ColoName string `json:"colo_name"`
ID uuid.UUID `json:"id"`
@@ -63,7 +68,7 @@ func (cp CleanupParams) encode() string {
return cp.queryParams.Encode()
}
func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error) {
func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
if name == "" {
return nil, errors.New("tunnel name required")
}
@@ -83,7 +88,11 @@ func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, er
switch resp.StatusCode {
case http.StatusOK:
return unmarshalTunnel(resp.Body)
var tunnel TunnelWithToken
if serdeErr := parseResponse(resp.Body, &tunnel); err != nil {
return nil, serdeErr
}
return &tunnel, nil
case http.StatusConflict:
return nil, ErrTunnelNameConflict
}
@@ -107,6 +116,23 @@ func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
return nil, r.statusCodeToError("get tunnel", resp)
}
func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
endpoint := r.baseEndpoints.accountLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
resp, err := r.sendRequest("GET", endpoint, nil)
if err != nil {
return "", errors.Wrap(err, "REST request failed")
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
err = parseResponse(resp.Body, &token)
return token, err
}
return "", r.statusCodeToError("get tunnel token", resp)
}
func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
endpoint := r.baseEndpoints.accountLevel
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
+27
View File
@@ -0,0 +1,27 @@
package cfio
import (
"io"
"sync"
)
const defaultBufferSize = 16 * 1024
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, defaultBufferSize)
},
}
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
_, okWriteTo := src.(io.WriterTo)
_, okReadFrom := dst.(io.ReaderFrom)
var buffer []byte = nil
if !(okWriteTo || okReadFrom) {
buffer = bufferPool.Get().([]byte)
defer bufferPool.Put(buffer)
}
return io.CopyBuffer(dst, src, buffer)
}
+14 -5
View File
@@ -1,5 +1,5 @@
pinned_go: &pinned_go go=1.17.5-1
pinned_go_fips: &pinned_go_fips go-boring=1.17.5-1
pinned_go: &pinned_go go=1.17.10-1
pinned_go_fips: &pinned_go_fips go-boring=1.17.9-1
build_dir: &build_dir /cfsetup_build
default-flavor: buster
@@ -40,16 +40,22 @@ stretch: &stretch
- libffi-dev
- python3-setuptools
- python3-pip
- reprepro
- createrepo
pre-cache: &github_release_pkgs_pre_cache
- wget https://github.com/sudarshan-reddy/msitools/releases/download/v0.101b/wixl -P /usr/local/bin
- chmod a+x /usr/local/bin/wixl
- pip3 install pynacl==1.4.0
- pip3 install pygithub==1.55
- pip3 install boto3==1.22.9
- pip3 install python-gnupg==0.4.9
post-cache:
# build all packages (except macos and FIPS) and move them to /cfsetup/built_artifacts
- ./build-packages.sh
# release the packages built and moved to /cfsetup/built_artifacts
- make github-release-built-pkgs
# publish packages to linux repos
- make release-pkgs-linux
# handle FIPS separately so that we built with gofips compiler
github-fips-release-pkgs:
build_dir: *build_dir
@@ -192,8 +198,11 @@ stretch: &stretch
builddeps:
- openssh-client
- s3cmd
- jq
- build-essential
post-cache:
- .teamcity/update-homebrew.sh
- .teamcity/update-homebrew-core.sh
github-message-release:
build_dir: *build_dir
builddeps:
@@ -246,10 +255,10 @@ centos-7:
pre-cache:
- yum install -y fakeroot
- yum upgrade -y binutils-2.27-44.base.el7.x86_64
- wget https://go.dev/dl/go1.17.5.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.17.5.linux-amd64.tar.gz
- wget https://go.dev/dl/go1.17.10.linux-amd64.tar.gz -P /tmp/
- tar -C /usr/local -xzf /tmp/go1.17.10.linux-amd64.tar.gz
post-cache:
- export PATH=$PATH:/usr/local/go/bin
- export GOOS=linux
- export GOARCH=amd64
- make publish-rpm
- make publish-rpm
+30
View File
@@ -0,0 +1,30 @@
package main
import (
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
)
func buildArgsForToken(c *cli.Context, log *zerolog.Logger) ([]string, error) {
token := c.Args().First()
if _, err := tunnel.ParseToken(token); err != nil {
return nil, cliutil.UsageError("Provided tunnel token is not valid (%s).", err)
}
return []string{
"tunnel", "run", "--token", token,
}, nil
}
func getServiceExtraArgsFromCliArgs(c *cli.Context, log *zerolog.Logger) ([]string, error) {
if c.NArg() > 0 {
// currently, we only support extra args for token
return buildArgsForToken(c, log)
} else {
// empty extra args
return make([]string, 0), nil
}
}
+96 -96
View File
@@ -6,7 +6,6 @@ package main
import (
"fmt"
"os"
"path/filepath"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
@@ -20,22 +19,16 @@ import (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Cloudflare Tunnel system service",
Usage: "Manages the cloudflared system service",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Cloudflare Tunnel as a system service",
Usage: "Install cloudflared as a system service",
Action: cliutil.ConfiguredAction(installLinuxService),
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "legacy",
Usage: "Generate service file for non-named tunnels",
},
},
},
{
Name: "uninstall",
Usage: "Uninstall the Cloudflare Tunnel service",
Usage: "Uninstall the cloudflared service",
Action: cliutil.ConfiguredAction(uninstallLinuxService),
},
},
@@ -50,19 +43,20 @@ const (
serviceConfigFile = "config.yml"
serviceCredentialFile = "cert.pem"
serviceConfigPath = serviceConfigDir + "/" + serviceConfigFile
cloudflaredService = "cloudflared.service"
)
var systemdTemplates = []ServiceTemplate{
{
Path: "/etc/systemd/system/cloudflared.service",
Path: fmt.Sprintf("/etc/systemd/system/%s", cloudflaredService),
Content: `[Unit]
Description=Cloudflare Tunnel
Description=cloudflared
After=network.target
[Service]
TimeoutStartSec=0
Type=notify
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
ExecStart={{ .Path }} --no-autoupdate{{ range .ExtraArgs }} {{ . }}{{ end }}
Restart=on-failure
RestartSec=5s
@@ -73,7 +67,7 @@ WantedBy=multi-user.target
{
Path: "/etc/systemd/system/cloudflared-update.service",
Content: `[Unit]
Description=Update Cloudflare Tunnel
Description=Update cloudflared
After=network.target
[Service]
@@ -83,7 +77,7 @@ ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 11 ]; then s
{
Path: "/etc/systemd/system/cloudflared-update.timer",
Content: `[Unit]
Description=Update Cloudflare Tunnel
Description=Update cloudflared
[Timer]
OnCalendar=daily
@@ -100,7 +94,7 @@ var sysvTemplate = ServiceTemplate{
Content: `#!/bin/sh
# For RedHat and cousins:
# chkconfig: 2345 99 01
# description: Cloudflare Tunnel agent
# description: cloudflared
# processname: {{.Path}}
### BEGIN INIT INFO
# Provides: {{.Path}}
@@ -108,11 +102,11 @@ var sysvTemplate = ServiceTemplate{
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Cloudflare Tunnel
# Description: Cloudflare Tunnel agent
# Short-Description: cloudflared
# Description: cloudflared agent
### END INIT INFO
name=$(basename $(readlink -f $0))
cmd="{{.Path}} --config /etc/cloudflared/config.yml --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
cmd="{{.Path}} --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s{{ range .ExtraArgs }} {{ . }}{{ end }}"
pid_file="/var/run/$name.pid"
stdout_log="/var/log/$name.log"
stderr_log="/var/log/$name.err"
@@ -191,27 +185,6 @@ func isSystemd() bool {
return false
}
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string, log *zerolog.Logger) error {
srcCredentialPath := filepath.Join(userConfigDir, userCredentialFile)
destCredentialPath := filepath.Join(serviceConfigDir, serviceCredentialFile)
if srcCredentialPath != destCredentialPath {
if err := copyCredential(srcCredentialPath, destCredentialPath); err != nil {
return err
}
}
srcConfigPath := filepath.Join(userConfigDir, userConfigFile)
destConfigPath := filepath.Join(serviceConfigDir, serviceConfigFile)
if srcConfigPath != destConfigPath {
if err := copyConfig(srcConfigPath, destConfigPath); err != nil {
return err
}
log.Info().Msgf("Copied %s to %s", srcConfigPath, destConfigPath)
}
return nil
}
func installLinuxService(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
@@ -223,61 +196,69 @@ func installLinuxService(c *cli.Context) error {
Path: etPath,
}
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
var extraArgsFunc func(c *cli.Context, log *zerolog.Logger) ([]string, error)
if c.NArg() == 0 {
extraArgsFunc = buildArgsForConfig
} else {
extraArgsFunc = buildArgsForToken
}
extraArgs, err := extraArgsFunc(c, log)
if err != nil {
return err
}
if c.Bool("legacy") {
userConfigDir := filepath.Dir(c.String("config"))
userConfigFile := filepath.Base(c.String("config"))
userCredentialFile := config.DefaultCredentialFile
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile, log); err != nil {
log.Err(err).Msgf("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s",
serviceConfigDir, serviceCredentialFile, serviceConfigFile)
return err
}
templateArgs.ExtraArgs = []string{
"--origincert", serviceConfigDir + "/" + serviceCredentialFile,
}
} else {
src, _, err := config.ReadConfigFile(c, log)
if err != nil {
return err
}
// can't use context because this command doesn't define "credentials-file" flag
configPresent := func(s string) bool {
val, err := src.String(s)
return err == nil && val != ""
}
if src.TunnelID == "" || !configPresent(tunnel.CredFileFlag) {
return fmt.Errorf(`Configuration file %s must contain entries for the tunnel to run and its associated credentials:
tunnel: TUNNEL-UUID
credentials-file: CREDENTIALS-FILE
`, src.Source())
}
if src.Source() != serviceConfigPath {
if exists, err := config.FileExists(serviceConfigPath); err != nil || exists {
return fmt.Errorf("Possible conflicting configuration in %[1]s and %[2]s. Either remove %[2]s or run `cloudflared --config %[2]s service install`", src.Source(), serviceConfigPath)
}
if err := copyFile(src.Source(), serviceConfigPath); err != nil {
return fmt.Errorf("failed to copy %s to %s: %w", src.Source(), serviceConfigPath, err)
}
}
templateArgs.ExtraArgs = []string{
"tunnel", "run",
}
}
templateArgs.ExtraArgs = extraArgs
switch {
case isSystemd():
log.Info().Msgf("Using Systemd")
return installSystemd(&templateArgs, log)
err = installSystemd(&templateArgs, log)
default:
log.Info().Msgf("Using SysV")
return installSysv(&templateArgs, log)
err = installSysv(&templateArgs, log)
}
if err == nil {
log.Info().Msg("Linux service for cloudflared installed successfully")
}
return err
}
func buildArgsForConfig(c *cli.Context, log *zerolog.Logger) ([]string, error) {
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
return nil, err
}
src, _, err := config.ReadConfigFile(c, log)
if err != nil {
return nil, err
}
// can't use context because this command doesn't define "credentials-file" flag
configPresent := func(s string) bool {
val, err := src.String(s)
return err == nil && val != ""
}
if src.TunnelID == "" || !configPresent(tunnel.CredFileFlag) {
return nil, fmt.Errorf(`Configuration file %s must contain entries for the tunnel to run and its associated credentials:
tunnel: TUNNEL-UUID
credentials-file: CREDENTIALS-FILE
`, src.Source())
}
if src.Source() != serviceConfigPath {
if exists, err := config.FileExists(serviceConfigPath); err != nil || exists {
return nil, fmt.Errorf("Possible conflicting configuration in %[1]s and %[2]s. Either remove %[2]s or run `cloudflared --config %[2]s service install`", src.Source(), serviceConfigPath)
}
if err := copyFile(src.Source(), serviceConfigPath); err != nil {
return nil, fmt.Errorf("failed to copy %s to %s: %w", src.Source(), serviceConfigPath, err)
}
}
return []string{
"--config", "/etc/cloudflared/config.yml", "tunnel", "run",
}, nil
}
func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
@@ -288,16 +269,19 @@ func installSystemd(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) erro
return err
}
}
if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil {
log.Err(err).Msg("systemctl enable cloudflared.service error")
if err := runCommand("systemctl", "enable", cloudflaredService); err != nil {
log.Err(err).Msgf("systemctl enable %s error", cloudflaredService)
return err
}
if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil {
log.Err(err).Msg("systemctl start cloudflared-update.timer error")
return err
}
log.Info().Msg("systemctl daemon-reload")
return runCommand("systemctl", "daemon-reload")
if err := runCommand("systemctl", "daemon-reload"); err != nil {
log.Err(err).Msg("systemctl daemon-reload error")
return err
}
return runCommand("systemctl", "start", cloudflaredService)
}
func installSysv(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
@@ -320,25 +304,35 @@ func installSysv(templateArgs *ServiceTemplateArgs, log *zerolog.Logger) error {
continue
}
}
return nil
return runCommand("service", "cloudflared", "start")
}
func uninstallLinuxService(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
var err error
switch {
case isSystemd():
log.Info().Msg("Using Systemd")
return uninstallSystemd(log)
err = uninstallSystemd(log)
default:
log.Info().Msg("Using SysV")
return uninstallSysv(log)
err = uninstallSysv(log)
}
if err == nil {
log.Info().Msg("Linux service for cloudflared uninstalled successfully")
}
return err
}
func uninstallSystemd(log *zerolog.Logger) error {
if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil {
log.Err(err).Msg("systemctl disable cloudflared.service error")
if err := runCommand("systemctl", "disable", cloudflaredService); err != nil {
log.Err(err).Msgf("systemctl disable %s error", cloudflaredService)
return err
}
if err := runCommand("systemctl", "stop", cloudflaredService); err != nil {
log.Err(err).Msgf("systemctl stop %s error", cloudflaredService)
return err
}
if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil {
@@ -351,11 +345,18 @@ func uninstallSystemd(log *zerolog.Logger) error {
return err
}
}
log.Info().Msgf("Successfully uninstalled cloudflared service from systemd")
if err := runCommand("systemctl", "daemon-reload"); err != nil {
log.Err(err).Msg("systemctl daemon-reload error")
return err
}
return nil
}
func uninstallSysv(log *zerolog.Logger) error {
if err := runCommand("service", "cloudflared", "stop"); err != nil {
log.Err(err).Msg("service cloudflared stop error")
return err
}
if err := sysvTemplate.Remove(); err != nil {
log.Err(err).Msg("error removing service template")
return err
@@ -370,6 +371,5 @@ func uninstallSysv(log *zerolog.Logger) error {
continue
}
}
log.Info().Msgf("Successfully uninstalled cloudflared service from sysv")
return nil
}
+32 -15
View File
@@ -21,16 +21,16 @@ const (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Cloudflare Tunnel launch agent",
Usage: "Manages the cloudflared launch agent",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Cloudflare Tunnel as an user launch agent",
Usage: "Install cloudflared as an user launch agent",
Action: cliutil.ConfiguredAction(installLaunchd),
},
{
Name: "uninstall",
Usage: "Uninstall the Cloudflare Tunnel launch agent",
Usage: "Uninstall the cloudflared launch agent",
Action: cliutil.ConfiguredAction(uninstallLaunchd),
},
},
@@ -50,6 +50,9 @@ func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemp
<key>ProgramArguments</key>
<array>
<string>{{ .Path }}</string>
{{- range $i, $item := .ExtraArgs}}
<string>{{ $item }}</string>
{{- end}}
</array>
<key>RunAtLoad</key>
<true/>
@@ -111,12 +114,12 @@ func installLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
log.Info().Msg("Installing Cloudflare Tunnel client as a system launch daemon. " +
"Cloudflare Tunnel client will run at boot")
log.Info().Msg("Installing cloudflared client as a system launch daemon. " +
"cloudflared client will run at boot")
} else {
log.Info().Msg("Installing Cloudflare Tunnel client as an user launch agent. " +
"Note that Cloudflare Tunnel client will only run when the user is logged in. " +
"If you want to run Cloudflare Tunnel client at boot, install with root permission. " +
log.Info().Msg("Installing cloudflared client as an user launch agent. " +
"Note that cloudflared client will only run when the user is logged in. " +
"If you want to run cloudflared client at boot, install with root permission. " +
"For more information, visit https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service")
}
etPath, err := os.Executable()
@@ -129,6 +132,13 @@ func installLaunchd(c *cli.Context) error {
log.Err(err).Msg("Error determining install path")
return errors.Wrap(err, "Error determining install path")
}
extraArgs, err := getServiceExtraArgsFromCliArgs(c, log)
if err != nil {
errMsg := "Unable to determine extra arguments for launch daemon"
log.Err(err).Msg(errMsg)
return errors.Wrap(err, errMsg)
}
stdoutPath, err := stdoutPath()
if err != nil {
log.Err(err).Msg("error determining stdout path")
@@ -140,7 +150,7 @@ func installLaunchd(c *cli.Context) error {
return errors.Wrap(err, "error determining stderr path")
}
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
templateArgs := ServiceTemplateArgs{Path: etPath}
templateArgs := ServiceTemplateArgs{Path: etPath, ExtraArgs: extraArgs}
err = launchdTemplate.Generate(&templateArgs)
if err != nil {
log.Err(err).Msg("error generating launchd template")
@@ -153,16 +163,20 @@ func installLaunchd(c *cli.Context) error {
}
log.Info().Msgf("Outputs are logged to %s and %s", stderrPath, stdoutPath)
return runCommand("launchctl", "load", plistPath)
err = runCommand("launchctl", "load", plistPath)
if err == nil {
log.Info().Msg("MacOS service for cloudflared installed successfully")
}
return err
}
func uninstallLaunchd(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if isRootUser() {
log.Info().Msg("Uninstalling Cloudflare Tunnel as a system launch daemon")
log.Info().Msg("Uninstalling cloudflared as a system launch daemon")
} else {
log.Info().Msg("Uninstalling Cloudflare Tunnel as an user launch agent")
log.Info().Msg("Uninstalling cloudflared as a user launch agent")
}
installPath, err := installPath()
if err != nil {
@@ -184,10 +198,13 @@ func uninstallLaunchd(c *cli.Context) error {
}
err = runCommand("launchctl", "unload", plistPath)
if err != nil {
log.Err(err).Msg("error unloading")
log.Err(err).Msg("error unloading launchd")
return err
}
log.Info().Msgf("Outputs are logged to %s and %s", stderrPath, stdoutPath)
return launchdTemplate.Remove()
err = launchdTemplate.Remove()
if err == nil {
log.Info().Msg("Launchd for cloudflared was uninstalled successfully")
}
return err
}
+3 -1
View File
@@ -21,6 +21,7 @@ import (
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/overwatch"
"github.com/cloudflare/cloudflared/tracing"
"github.com/cloudflare/cloudflared/watcher"
)
@@ -69,7 +70,7 @@ func main() {
app.Copyright = fmt.Sprintf(
`(c) %d Cloudflare Inc.
Your installation of cloudflared software constitutes a symbol of your signature indicating that you accept
the terms of the Cloudflare License (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/license),
the terms of the Apache License Version 2.0 (https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/license),
Terms (https://www.cloudflare.com/terms/) and Privacy Policy (https://www.cloudflare.com/privacypolicy/).`,
time.Now().Year(),
)
@@ -86,6 +87,7 @@ func main() {
tunnel.Init(bInfo, graceShutdownC) // we need this to support the tunnel sub command...
access.Init(graceShutdownC)
updater.Init(Version)
tracing.Init(Version)
runApp(app, graceShutdownC)
}
+1 -1
View File
@@ -73,7 +73,7 @@ func Run(c *cli.Context) error {
log.Fatal().Err(err).Msg("Failed to open the metrics listener")
}
go metrics.ServeMetrics(metricsListener, nil, nil, "", log)
go metrics.ServeMetrics(metricsListener, nil, nil, "", nil, log)
listener, err := tunneldns.CreateListener(
c.String("address"),
+15 -2
View File
@@ -43,6 +43,10 @@ func (st *ServiceTemplate) Generate(args *ServiceTemplateArgs) error {
if err != nil {
return err
}
if _, err = os.Stat(resolvedPath); err == nil {
return fmt.Errorf(serviceAlreadyExistsWarn(resolvedPath))
}
var buffer bytes.Buffer
err = tmpl.Execute(&buffer, args)
if err != nil {
@@ -71,6 +75,15 @@ func (st *ServiceTemplate) Remove() error {
return nil
}
func serviceAlreadyExistsWarn(service string) string {
return fmt.Sprintf("cloudflared service is already installed at %s; if you are running a cloudflared tunnel, you "+
"can point it to multiple origins, avoiding the need to run more than one cloudflared service in the "+
"same machine; otherwise if you are really sure, you can do `cloudflared service uninstall` to clean "+
"up the existing service and then try again this command",
service,
)
}
func runCommand(command string, args ...string) error {
cmd := exec.Command(command, args...)
stderr, err := cmd.StderrPipe()
@@ -82,10 +95,10 @@ func runCommand(command string, args ...string) error {
return fmt.Errorf("error starting %s: %v", command, err)
}
_, _ = ioutil.ReadAll(stderr)
output, _ := ioutil.ReadAll(stderr)
err = cmd.Wait()
if err != nil {
return fmt.Errorf("%s returned with error: %v", command, err)
return fmt.Errorf("%s %v returned with error code %v due to: %v", command, args, err, string(output))
}
return nil
}
+51 -33
View File
@@ -109,6 +109,7 @@ func Commands() []*cli.Command {
buildIngressSubcommand(),
buildDeleteCommand(),
buildCleanupCommand(),
buildTokenCommand(),
// for compatibility, allow following as tunnel subcommands
proxydns.Command(true),
cliutil.RemovedCommand("db-connect"),
@@ -127,26 +128,34 @@ func buildTunnelCommand(subcommands []*cli.Command) *cli.Command {
Name: "tunnel",
Action: cliutil.ConfiguredAction(TunnelCommand),
Category: "Tunnel",
Usage: "Make a locally-running web service accessible over the internet using Cloudflare Tunnel.",
Usage: "Use Cloudflare Tunnel to expose private services to the Internet or to Cloudflare connected private users.",
ArgsUsage: " ",
Description: `Cloudflare Tunnel asks you to specify a hostname on a Cloudflare-powered
domain you control and a local address. Traffic from that hostname is routed
(optionally via a Cloudflare Load Balancer) to this machine and appears on the
specified port where it can be served.
Description: ` Cloudflare Tunnel allows to expose private services without opening any ingress port on this machine. It can expose:
A) Locally reachable HTTP-based private services to the Internet on DNS with Cloudflare as authority (which you can
then protect with Cloudflare Access).
B) Locally reachable TCP/UDP-based private services to Cloudflare connected private users in the same account, e.g.,
those enrolled to a Zero Trust WARP Client.
This feature requires your Cloudflare account be subscribed to the Cloudflare Smart Routing feature.
You can manage your Tunnels via dash.teams.cloudflare.com. This approach will only require you to run a single command
later in each machine where you wish to run a Tunnel.
To use, begin by calling login to download a certificate:
Alternatively, you can manage your Tunnels via the command line. Begin by obtaining a certificate to be able to do so:
$ cloudflared tunnel login
$ cloudflared tunnel login
With your certificate installed you can then launch your first tunnel,
replacing my.site.com with a subdomain of your site:
With your certificate installed you can then get started with Tunnels:
$ cloudflared tunnel --hostname my.site.com --url http://localhost:8080
$ cloudflared tunnel create my-first-tunnel
$ cloudflared tunnel route dns my-first-tunnel my-first-tunnel.mydomain.com
$ cloudflared tunnel run --hello-world my-first-tunnel
If you have a web server running on port 8080 (in this example), it will be available on
the internet!`,
You can now access my-first-tunnel.mydomain.com and be served an example page by your local cloudflared process.
For exposing local TCP/UDP services by IP to your privately connected users, check out:
$ cloudflared tunnel route ip --help
See https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/ for more info.`,
Subcommands: subcommands,
Flags: tunnelFlags(false),
}
@@ -334,12 +343,17 @@ func StartServer(
observer.SendURL(quickTunnelURL)
}
tunnelConfig, dynamicConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
tunnelConfig, orchestratorConfig, err := prepareTunnelConfig(c, info, log, logTransport, observer, namedTunnel)
if err != nil {
log.Err(err).Msg("Couldn't start tunnel")
return err
}
orchestrator, err := orchestration.NewOrchestrator(ctx, orchestratorConfig, tunnelConfig.Tags, tunnelConfig.Log)
if err != nil {
return err
}
metricsListener, err := listeners.Listen("tcp", c.String("metrics"))
if err != nil {
log.Err(err).Msg("Error opening metrics server listener")
@@ -351,14 +365,9 @@ func StartServer(
defer wg.Done()
readinessServer := metrics.NewReadyServer(log)
observer.RegisterSink(readinessServer)
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, log)
errC <- metrics.ServeMetrics(metricsListener, ctx.Done(), readinessServer, quickTunnelURL, orchestrator, log)
}()
orchestrator, err := orchestration.NewOrchestrator(ctx, dynamicConfig, tunnelConfig.Tags, tunnelConfig.Log)
if err != nil {
return err
}
reconnectCh := make(chan supervisor.ReconnectSignal, 1)
if c.IsSet("stdin-control") {
log.Info().Msg("Enabling control through stdin")
@@ -379,7 +388,7 @@ func StartServer(
info.Version(),
hostname,
metricsListener.Addr().String(),
dynamicConfig.Ingress,
orchestratorConfig.Ingress,
tunnelConfig.HAConnections,
)
app := tunnelUI.Launch(ctx, log, logTransport)
@@ -724,43 +733,43 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: ingress.Socks5Flag,
Usage: "specify if this tunnel is running as a SOCK5 Server",
Usage: legacyTunnelFlag("specify if this tunnel is running as a SOCK5 Server"),
EnvVars: []string{"TUNNEL_SOCKS"},
Value: false,
Hidden: shouldHide,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: ingress.ProxyConnectTimeoutFlag,
Usage: "HTTP proxy timeout for establishing a new connection",
Usage: legacyTunnelFlag("HTTP proxy timeout for establishing a new connection"),
Value: time.Second * 30,
Hidden: shouldHide,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: ingress.ProxyTLSTimeoutFlag,
Usage: "HTTP proxy timeout for completing a TLS handshake",
Usage: legacyTunnelFlag("HTTP proxy timeout for completing a TLS handshake"),
Value: time.Second * 10,
Hidden: shouldHide,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: ingress.ProxyTCPKeepAliveFlag,
Usage: "HTTP proxy TCP keepalive duration",
Usage: legacyTunnelFlag("HTTP proxy TCP keepalive duration"),
Value: time.Second * 30,
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: ingress.ProxyNoHappyEyeballsFlag,
Usage: "HTTP proxy should disable \"happy eyeballs\" for IPv4/v6 fallback",
Usage: legacyTunnelFlag("HTTP proxy should disable \"happy eyeballs\" for IPv4/v6 fallback"),
Hidden: shouldHide,
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: ingress.ProxyKeepAliveConnectionsFlag,
Usage: "HTTP proxy maximum keepalive connection pool size",
Usage: legacyTunnelFlag("HTTP proxy maximum keepalive connection pool size"),
Value: 100,
Hidden: shouldHide,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: ingress.ProxyKeepAliveTimeoutFlag,
Usage: "HTTP proxy timeout for closing an idle connection",
Usage: legacyTunnelFlag("HTTP proxy timeout for closing an idle connection"),
Value: time.Second * 90,
Hidden: shouldHide,
}),
@@ -778,13 +787,13 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: ingress.HTTPHostHeaderFlag,
Usage: "Sets the HTTP Host header for the local webserver.",
Usage: legacyTunnelFlag("Sets the HTTP Host header for the local webserver."),
EnvVars: []string{"TUNNEL_HTTP_HOST_HEADER"},
Hidden: shouldHide,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: ingress.OriginServerNameFlag,
Usage: "Hostname on the origin server certificate.",
Usage: legacyTunnelFlag("Hostname on the origin server certificate."),
EnvVars: []string{"TUNNEL_ORIGIN_SERVER_NAME"},
Hidden: shouldHide,
}),
@@ -796,19 +805,19 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: tlsconfig.OriginCAPoolFlag,
Usage: "Path to the CA for the certificate of your origin. This option should be used only if your certificate is not signed by Cloudflare.",
Usage: legacyTunnelFlag("Path to the CA for the certificate of your origin. This option should be used only if your certificate is not signed by Cloudflare."),
EnvVars: []string{"TUNNEL_ORIGIN_CA_POOL"},
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: ingress.NoTLSVerifyFlag,
Usage: "Disables TLS verification of the certificate presented by your origin. Will allow any certificate from the origin to be accepted. Note: The connection from your machine to Cloudflare's Edge is still encrypted.",
Usage: legacyTunnelFlag("Disables TLS verification of the certificate presented by your origin. Will allow any certificate from the origin to be accepted. Note: The connection from your machine to Cloudflare's Edge is still encrypted."),
EnvVars: []string{"NO_TLS_VERIFY"},
Hidden: shouldHide,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: ingress.NoChunkedEncodingFlag,
Usage: "Disables chunked transfer encoding; useful if you are running a WSGI server.",
Usage: legacyTunnelFlag("Disables chunked transfer encoding; useful if you are running a WSGI server."),
EnvVars: []string{"TUNNEL_NO_CHUNKED_ENCODING"},
Hidden: shouldHide,
}),
@@ -816,6 +825,15 @@ func configureProxyFlags(shouldHide bool) []cli.Flag {
return append(flags, sshFlags(shouldHide)...)
}
func legacyTunnelFlag(msg string) string {
return fmt.Sprintf(
"%s This flag only takes effect if you define your origin with `--url` and if you do not use ingress rules."+
" The recommended way is to rely on ingress rules and define this property under `originRequest` as per"+
" https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/configuration-file/ingress",
msg,
)
}
func sshFlags(shouldHide bool) []cli.Flag {
return []cli.Flag{
altsrc.NewStringFlag(&cli.StringFlag{
+77 -7
View File
@@ -14,6 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
"golang.org/x/crypto/ssh/terminal"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
@@ -31,14 +32,19 @@ import (
)
const LogFieldOriginCertPath = "originCertPath"
const secretValue = "*****"
var (
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
quickStartUrl = developerPortal + "/quickstart/quickstart/"
serviceUrl = developerPortal + "/reference/service/"
argumentsUrl = developerPortal + "/reference/arguments/"
LogFieldHostname = "hostname"
secretFlags = [2]*altsrc.StringFlag{credentialsContentsFlag, tunnelTokenFlag}
defaultFeatures = []string{supervisor.FeatureAllowRemoteConfig, supervisor.FeatureSerializedHeaders}
configFlags = []string{"autoupdate-freq", "no-autoupdate", "retries", "protocol", "loglevel", "transport-loglevel", "origincert", "metrics", "metrics-update-freq"}
)
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
@@ -65,7 +71,11 @@ func generateRandomClientID(log *zerolog.Logger) (string, error) {
func logClientOptions(c *cli.Context, log *zerolog.Logger) {
flags := make(map[string]interface{})
for _, flag := range c.FlagNames() {
flags[flag] = c.Generic(flag)
if isSecretFlag(flag) {
flags[flag] = secretValue
} else {
flags[flag] = c.Generic(flag)
}
}
if len(flags) > 0 {
@@ -79,7 +89,11 @@ func logClientOptions(c *cli.Context, log *zerolog.Logger) {
if strings.Contains(env, "TUNNEL_") {
vars := strings.Split(env, "=")
if len(vars) == 2 {
envs[vars[0]] = vars[1]
if isSecretEnvVar(vars[0]) {
envs[vars[0]] = secretValue
} else {
envs[vars[0]] = vars[1]
}
}
}
}
@@ -88,6 +102,26 @@ func logClientOptions(c *cli.Context, log *zerolog.Logger) {
}
}
func isSecretFlag(key string) bool {
for _, flag := range secretFlags {
if flag.Name == key {
return true
}
}
return false
}
func isSecretEnvVar(key string) bool {
for _, flag := range secretFlags {
for _, secretEnvVar := range flag.EnvVars {
if secretEnvVar == key {
return true
}
}
}
return false
}
func dnsProxyStandAlone(c *cli.Context, namedTunnel *connection.NamedTunnelProperties) bool {
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world") && namedTunnel == nil)
}
@@ -183,6 +217,10 @@ func prepareTunnelConfig(
ingressRules ingress.Ingress
classicTunnel *connection.ClassicTunnelProperties
)
transportProtocol := c.String("protocol")
protocolFetcher := edgediscovery.ProtocolPercentage
cfg := config.GetConfiguration()
if isNamedTunnel {
clientUUID, err := uuid.NewRandom()
@@ -190,7 +228,26 @@ func prepareTunnelConfig(
return nil, nil, errors.Wrap(err, "can't generate connector UUID")
}
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
features := append(c.StringSlice("features"), supervisor.FeatureSerializedHeaders)
features := append(c.StringSlice("features"), defaultFeatures...)
if c.IsSet(TunnelTokenFlag) {
if transportProtocol == connection.AutoSelectFlag {
protocolFetcher = func() (edgediscovery.ProtocolPercents, error) {
// If the Tunnel is remotely managed and no protocol is set, we prefer QUIC, but still allow fall-back.
preferQuic := []edgediscovery.ProtocolPercent{
{
Protocol: connection.QUIC.String(),
Percentage: 100,
},
{
Protocol: connection.HTTP2.String(),
Percentage: 100,
},
}
return preferQuic, nil
}
}
log.Info().Msg("Will be fetching remotely managed configuration from Cloudflare API. Defaulting to protocol: quic")
}
namedTunnel.Client = tunnelpogs.ClientInfo{
ClientID: clientUUID[:],
Features: dedup(features),
@@ -233,7 +290,7 @@ func prepareTunnelConfig(
}
warpRoutingEnabled := isWarpRoutingEnabled(cfg.WarpRouting, isNamedTunnel)
protocolSelector, err := connection.NewProtocolSelector(c.String("protocol"), warpRoutingEnabled, namedTunnel, edgediscovery.ProtocolPercentage, supervisor.ResolveTTL, log)
protocolSelector, err := connection.NewProtocolSelector(transportProtocol, warpRoutingEnabled, namedTunnel, protocolFetcher, supervisor.ResolveTTL, log)
if err != nil {
return nil, nil, err
}
@@ -293,11 +350,24 @@ func prepareTunnelConfig(
ProtocolSelector: protocolSelector,
EdgeTLSConfigs: edgeTLSConfigs,
}
dynamicConfig := &orchestration.Config{
orchestratorConfig := &orchestration.Config{
Ingress: &ingressRules,
WarpRoutingEnabled: warpRoutingEnabled,
ConfigurationFlags: parseConfigFlags(c),
}
return tunnelConfig, dynamicConfig, nil
return tunnelConfig, orchestratorConfig, nil
}
func parseConfigFlags(c *cli.Context) map[string]string {
result := make(map[string]string)
for _, flag := range configFlags {
if v := c.String(flag); c.IsSet(flag) && v != "" {
result[flag] = v
}
}
return result
}
func gracePeriod(c *cli.Context) (time.Duration, error) {
-1
View File
@@ -55,7 +55,6 @@ func RunQuickTunnel(sc *subcommandContext) error {
AccountTag: data.Result.AccountTag,
TunnelSecret: data.Result.Secret,
TunnelID: tunnelID,
TunnelName: data.Result.Name,
}
url := data.Result.Hostname
+24 -3
View File
@@ -185,7 +185,6 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec
AccountTag: credential.cert.AccountID,
TunnelSecret: tunnelSecret,
TunnelID: tunnel.ID,
TunnelName: name,
}
usedCertPath := false
if credentialsFilePath == "" {
@@ -221,7 +220,8 @@ func (sc *subcommandContext) create(name string, credentialsFilePath string, sec
}
fmt.Println(" Keep this file secret. To revoke these credentials, delete the tunnel.")
fmt.Printf("\nCreated tunnel %s with id %s\n", tunnel.Name, tunnel.ID)
return tunnel, nil
return &tunnel.Tunnel, nil
}
func (sc *subcommandContext) list(filter *cfapi.TunnelFilter) ([]*cfapi.Tunnel, error) {
@@ -301,6 +301,12 @@ func (sc *subcommandContext) run(tunnelID uuid.UUID) error {
return err
}
return sc.runWithCredentials(credentials)
}
func (sc *subcommandContext) runWithCredentials(credentials connection.Credentials) error {
sc.log.Info().Str(LogFieldTunnelID, credentials.TunnelID.String()).Msg("Starting tunnel")
return StartServer(
sc.c,
buildInfo,
@@ -335,6 +341,21 @@ func (sc *subcommandContext) cleanupConnections(tunnelIDs []uuid.UUID) error {
return nil
}
func (sc *subcommandContext) getTunnelTokenCredentials(tunnelID uuid.UUID) (*connection.TunnelToken, error) {
client, err := sc.client()
if err != nil {
return nil, err
}
token, err := client.GetTunnelToken(tunnelID)
if err != nil {
sc.log.Err(err).Msgf("Could not get the Token for the given Tunnel %v", tunnelID)
return nil, err
}
return ParseToken(token)
}
func (sc *subcommandContext) route(tunnelID uuid.UUID, r cfapi.HostnameRoute) (cfapi.HostnameRouteResult, error) {
client, err := sc.client()
if err != nil {
@@ -370,7 +391,7 @@ func (sc *subcommandContext) findID(input string) (uuid.UUID, error) {
// Look up name in the credentials file.
credFinder := newStaticPath(sc.c.String(CredFileFlag), sc.fs)
if credentials, err := sc.readTunnelCredentials(credFinder); err == nil {
if credentials.TunnelID != uuid.Nil && input == credentials.TunnelName {
if credentials.TunnelID != uuid.Nil {
return credentials.TunnelID, nil
}
}
@@ -116,7 +116,6 @@ func Test_subcommandContext_findCredentials(t *testing.T) {
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
TunnelName: name,
},
},
{
@@ -161,7 +160,6 @@ func Test_subcommandContext_findCredentials(t *testing.T) {
AccountTag: accountTag,
TunnelID: tunnelID,
TunnelSecret: secret,
TunnelName: name,
},
},
}
@@ -218,6 +216,10 @@ func (d *deleteMockTunnelStore) GetTunnel(tunnelID uuid.UUID) (*cfapi.Tunnel, er
return &tunnel.tunnel, nil
}
func (d *deleteMockTunnelStore) GetTunnelToken(tunnelID uuid.UUID) (string, error) {
return "token", nil
}
func (d *deleteMockTunnelStore) DeleteTunnel(tunnelID uuid.UUID) error {
tunnel, ok := d.mockTunnels[tunnelID]
if !ok {
+94 -13
View File
@@ -2,6 +2,7 @@ package tunnel
import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
@@ -34,6 +35,7 @@ const (
CredFileFlagAlias = "cred-file"
CredFileFlag = "credentials-file"
CredContentsFlag = "credentials-contents"
TunnelTokenFlag = "token"
overwriteDNSFlagName = "overwrite-dns"
LogFieldTunnelID = "tunnelID"
@@ -118,6 +120,11 @@ var (
Usage: "Contents of the tunnel credentials JSON file to use. When provided along with credentials-file, this will take precedence.",
EnvVars: []string{"TUNNEL_CRED_CONTENTS"},
})
tunnelTokenFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: TunnelTokenFlag,
Usage: "The Tunnel token. When provided along with credentials, this will take precedence.",
EnvVars: []string{"TUNNEL_TOKEN"},
})
forceDeleteFlag = &cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
@@ -127,7 +134,7 @@ var (
}
selectProtocolFlag = altsrc.NewStringFlag(&cli.StringFlag{
Name: "protocol",
Value: "auto",
Value: connection.AutoSelectFlag,
Aliases: []string{"p"},
Usage: fmt.Sprintf("Protocol implementation to connect with Cloudflare's edge network. %s", connection.AvailableProtocolFlagMessage),
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
@@ -597,6 +604,7 @@ func buildRunCommand() *cli.Command {
credentialsContentsFlag,
selectProtocolFlag,
featuresFlag,
tunnelTokenFlag,
}
flags = append(flags, configureProxyFlags(false)...)
return &cli.Command{
@@ -627,14 +635,6 @@ func runCommand(c *cli.Context) error {
if c.NArg() > 1 {
return cliutil.UsageError(`"cloudflared tunnel run" accepts only one argument, the ID or name of the tunnel to run.`)
}
tunnelRef := c.Args().First()
if tunnelRef == "" {
// see if tunnel id was in the config file
tunnelRef = config.GetConfiguration().TunnelID
if tunnelRef == "" {
return cliutil.UsageError(`"cloudflared tunnel run" requires the ID or name of the tunnel to run as the last command line argument or in the configuration file.`)
}
}
if c.String("hostname") != "" {
sc.log.Warn().Msg("The property `hostname` in your configuration is ignored because you configured a Named Tunnel " +
@@ -642,7 +642,38 @@ func runCommand(c *cli.Context) error {
"your origin will not be reachable. You should remove the `hostname` property to avoid this warning.")
}
return runNamedTunnel(sc, tunnelRef)
// Check if token is provided and if not use default tunnelID flag method
if tokenStr := c.String(TunnelTokenFlag); tokenStr != "" {
if token, err := ParseToken(tokenStr); err == nil {
return sc.runWithCredentials(token.Credentials())
}
return cliutil.UsageError("Provided Tunnel token is not valid.")
} else {
tunnelRef := c.Args().First()
if tunnelRef == "" {
// see if tunnel id was in the config file
tunnelRef = config.GetConfiguration().TunnelID
if tunnelRef == "" {
return cliutil.UsageError(`"cloudflared tunnel run" requires the ID or name of the tunnel to run as the last command line argument or in the configuration file.`)
}
}
return runNamedTunnel(sc, tunnelRef)
}
}
func ParseToken(tokenStr string) (*connection.TunnelToken, error) {
content, err := base64.StdEncoding.DecodeString(tokenStr)
if err != nil {
return nil, err
}
var token connection.TunnelToken
if err := json.Unmarshal(content, &token); err != nil {
return nil, err
}
return &token, nil
}
func runNamedTunnel(sc *subcommandContext, tunnelRef string) error {
@@ -650,9 +681,6 @@ func runNamedTunnel(sc *subcommandContext, tunnelRef string) error {
if err != nil {
return errors.Wrap(err, "error parsing tunnel ID")
}
sc.log.Info().Str(LogFieldTunnelID, tunnelID.String()).Msg("Starting tunnel")
return sc.run(tunnelID)
}
@@ -686,6 +714,59 @@ func cleanupCommand(c *cli.Context) error {
return sc.cleanupConnections(tunnelIDs)
}
func buildTokenCommand() *cli.Command {
return &cli.Command{
Name: "token",
Action: cliutil.ConfiguredAction(tokenCommand),
Usage: "Fetch the credentials token for an existing tunnel (by name or UUID) that allows to run it",
UsageText: "cloudflared tunnel [tunnel command options] token [subcommand options] TUNNEL",
Description: "cloudflared tunnel token will fetch the credentials token for a given tunnel (by its name or UUID), which is then used to run the tunnel. This command fails if the tunnel does not exist or has been deleted. Use the flag `cloudflared tunnel token --cred-file /my/path/file.json TUNNEL` to output the token to the credentials JSON file. Note: this command only works for Tunnels created since cloudflared version 2022.3.0",
Flags: []cli.Flag{credentialsFileFlagCLIOnly},
CustomHelpTemplate: commandHelpTemplate(),
}
}
func tokenCommand(c *cli.Context) error {
sc, err := newSubcommandContext(c)
if err != nil {
return errors.Wrap(err, "error setting up logger")
}
warningChecker := updater.StartWarningCheck(c)
defer warningChecker.LogWarningIfAny(sc.log)
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel token" requires exactly 1 argument, the name or UUID of tunnel to fetch the credentials token for.`)
}
tunnelID, err := sc.findID(c.Args().First())
if err != nil {
return errors.Wrap(err, "error parsing tunnel ID")
}
token, err := sc.getTunnelTokenCredentials(tunnelID)
if err != nil {
return err
}
if path := c.String(CredFileFlag); path != "" {
credentials := token.Credentials()
err := writeTunnelCredentials(path, &credentials)
if err != nil {
return errors.Wrapf(err, "error writing token credentials to JSON file in path %s", path)
}
return nil
}
encodedToken, err := token.Encode()
if err != nil {
return err
}
fmt.Printf("%s", encodedToken)
return nil
}
func buildRouteCommand() *cli.Command {
return &cli.Command{
Name: "route",
@@ -1,14 +1,18 @@
package tunnel
import (
"encoding/base64"
"encoding/json"
"path/filepath"
"testing"
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/cfapi"
"github.com/cloudflare/cloudflared/connection"
)
func Test_fmtConnections(t *testing.T) {
@@ -177,3 +181,24 @@ func Test_validateHostname(t *testing.T) {
})
}
}
func Test_TunnelToken(t *testing.T) {
token, err := ParseToken("aabc")
require.Error(t, err)
require.Nil(t, token)
expectedToken := &connection.TunnelToken{
AccountTag: "abc",
TunnelSecret: []byte("secret"),
TunnelID: uuid.New(),
}
tokenJsonStr, err := json.Marshal(expectedToken)
require.NoError(t, err)
token64 := base64.StdEncoding.EncodeToString(tokenJsonStr)
token, err = ParseToken(token64)
require.NoError(t, err)
require.Equal(t, token, expectedToken)
}
+9 -4
View File
@@ -19,7 +19,7 @@ import (
const (
DefaultCheckUpdateFreq = time.Hour * 24
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service"
noUpdateInShellMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/as-a-service/"
noUpdateOnWindowsMessage = "cloudflared will not automatically update on Windows systems."
noUpdateManagedPackageMessage = "cloudflared will not automatically update if installed by a package manager."
isManagedInstallFile = ".installedFromPackageManager"
@@ -30,7 +30,8 @@ const (
)
var (
version string
version string
BuiltForPackageManager = ""
)
// BinaryUpdated implements ExitCoder interface, the app will exit with status code 11
@@ -118,7 +119,11 @@ func Update(c *cli.Context) error {
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
if wasInstalledFromPackageManager() {
log.Error().Msg("cloudflared was installed by a package manager. Please update using the same method.")
packageManagerName := "a package manager"
if BuiltForPackageManager != "" {
packageManagerName = BuiltForPackageManager
}
log.Error().Msg(fmt.Sprintf("cloudflared was installed by %s. Please update using the same method.", packageManagerName))
return nil
}
@@ -281,7 +286,7 @@ func supportAutoUpdate(log *zerolog.Logger) bool {
func wasInstalledFromPackageManager() bool {
ok, _ := config.FileExists(filepath.Join(config.DefaultUnixConfigLocation, isManagedInstallFile))
return ok
return len(BuiltForPackageManager) != 0 || ok
}
func isRunningFromTerminal() bool {
+35 -14
View File
@@ -26,8 +26,8 @@ import (
const (
windowsServiceName = "Cloudflared"
windowsServiceDescription = "Cloudflare Tunnel agent"
windowsServiceUrl = "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/run-as-service#windows"
windowsServiceDescription = "Cloudflared agent"
windowsServiceUrl = "https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/run-tunnel/as-a-service/windows/"
recoverActionDelay = time.Second * 20
failureCountResetPeriod = time.Hour * 24
@@ -46,16 +46,16 @@ const (
func runApp(app *cli.App, graceShutdownC chan struct{}) {
app.Commands = append(app.Commands, &cli.Command{
Name: "service",
Usage: "Manages the Cloudflare Tunnel Windows service",
Usage: "Manages the cloudflared Windows service",
Subcommands: []*cli.Command{
{
Name: "install",
Usage: "Install Cloudflare Tunnel as a Windows service",
Usage: "Install cloudflared as a Windows service",
Action: cliutil.ConfiguredAction(installWindowsService),
},
{
Name: "uninstall",
Usage: "Uninstall the Cloudflare Tunnel service",
Usage: "Uninstall the cloudflared service",
Action: cliutil.ConfiguredAction(uninstallWindowsService),
},
},
@@ -177,7 +177,7 @@ func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeReques
func installWindowsService(c *cli.Context) error {
zeroLogger := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
zeroLogger.Info().Msg("Installing Cloudflare Tunnel Windows service")
zeroLogger.Info().Msg("Installing cloudflared Windows service")
exepath, err := os.Executable()
if err != nil {
return errors.Wrap(err, "Cannot find path name that start the process")
@@ -191,26 +191,39 @@ func installWindowsService(c *cli.Context) error {
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
if err == nil {
s.Close()
return fmt.Errorf("Service %s already exists", windowsServiceName)
return fmt.Errorf(serviceAlreadyExistsWarn(windowsServiceName))
}
extraArgs, err := getServiceExtraArgsFromCliArgs(c, &log)
if err != nil {
errMsg := "Unable to determine extra arguments for windows service"
log.Err(err).Msg(errMsg)
return errors.Wrap(err, errMsg)
}
config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription}
s, err = m.CreateService(windowsServiceName, exepath, config)
s, err = m.CreateService(windowsServiceName, exepath, config, extraArgs...)
if err != nil {
return errors.Wrap(err, "Cannot install service")
}
defer s.Close()
log.Info().Msg("Cloudflare Tunnel agent service is installed")
log.Info().Msg("cloudflared agent service is installed")
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return errors.Wrap(err, "Cannot install event logger")
}
err = configRecoveryOption(s.Handle)
if err != nil {
log.Err(err).Msg("Cannot set service recovery actions")
log.Info().Msgf("See %s to manually configure service recovery actions", windowsServiceUrl)
}
return nil
err = s.Start()
if err == nil {
log.Info().Msg("Agent service for cloudflared installed successfully")
}
return err
}
func uninstallWindowsService(c *cli.Context) error {
@@ -218,7 +231,7 @@ func uninstallWindowsService(c *cli.Context) error {
With().
Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
log.Info().Msg("Uninstalling Cloudflare Tunnel Windows Service")
log.Info().Msg("Uninstalling cloudflared agent service")
m, err := mgr.Connect()
if err != nil {
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
@@ -226,14 +239,22 @@ func uninstallWindowsService(c *cli.Context) error {
defer m.Disconnect()
s, err := m.OpenService(windowsServiceName)
if err != nil {
return fmt.Errorf("Service %s is not installed", windowsServiceName)
return fmt.Errorf("Agent service %s is not installed, so it could not be uninstalled", windowsServiceName)
}
defer s.Close()
if status, err := s.Query(); err == nil && status.State == svc.Running {
log.Info().Msg("Stopping cloudflared agent service")
if _, err := s.Control(svc.Stop); err != nil {
log.Info().Err(err).Msg("Failed to stop cloudflared agent service, you may need to stop it manually to complete uninstall.")
}
}
err = s.Delete()
if err != nil {
return errors.Wrap(err, "Cannot delete service")
return errors.Wrap(err, "Cannot delete agent service")
}
log.Info().Msg("Cloudflare Tunnel agent service is uninstalled")
log.Info().Msg("Agent service for cloudflared was uninstalled successfully")
err = eventlog.Remove(windowsServiceName)
if err != nil {
return errors.Wrap(err, "Cannot remove event logger")
+1 -1
View File
@@ -14,7 +14,7 @@ classic_hostname: "classic-tunnel-component-tests.example.com"
origincert: "/Users/tunnel/.cloudflared/cert.pem"
ingress:
- hostname: named-tunnel-component-tests.example.com
service: http_status:200
service: hello_world
- service: http_status:404
```
+24
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env python
import copy
import json
import base64
from dataclasses import dataclass, InitVar
@@ -61,6 +63,28 @@ class NamedTunnelConfig(NamedTunnelBaseConfig):
def get_url(self):
return "https://" + self.ingress[0]['hostname']
def base_config(self):
config = self.full_config.copy()
# removes the tunnel reference
del(config["tunnel"])
del(config["credentials-file"])
return config
def get_tunnel_id(self):
return self.full_config["tunnel"]
def get_token(self):
creds = self.get_credentials_json()
token_dict = {"a": creds["AccountTag"], "t": creds["TunnelID"], "s": creds["TunnelSecret"]}
token_json_str = json.dumps(token_dict)
return base64.b64encode(token_json_str.encode('utf-8'))
def get_credentials_json(self):
with open(self.credentials_file) as json_file:
return json.load(json_file)
@dataclass(frozen=True)
class ClassicTunnelBaseConfig(BaseConfig):
+50 -12
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python
import os
import pathlib
import platform
import subprocess
from contextlib import contextmanager
@@ -9,7 +10,7 @@ import pytest
import test_logging
from conftest import CfdModes
from util import start_cloudflared, wait_tunnel_ready
from util import start_cloudflared, wait_tunnel_ready, write_config
def select_platform(plat):
@@ -43,6 +44,21 @@ class TestServiceMode:
self.launchd_service_scenario(config, assert_log_file)
@select_platform("Darwin")
@pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
def test_launchd_service_with_token(self, tmp_path, component_tests_config):
log_file = tmp_path / test_logging.default_log_file
additional_config = {
"logfile": str(log_file),
}
config = component_tests_config(additional_config=additional_config)
# service install doesn't install the config file but in this case we want to use some default settings
# so we write the base config without the tunnel credentials and ID
write_config(pathlib.Path(default_config_dir()), config.base_config())
self.launchd_service_scenario(config, use_token=True)
@select_platform("Darwin")
@pytest.mark.skipif(os.path.exists(default_config_file()), reason=f"There is already a config file in default path")
def test_launchd_service_rotating_log(self, tmp_path, component_tests_config):
@@ -60,12 +76,13 @@ class TestServiceMode:
self.launchd_service_scenario(config, assert_rotating_log)
def launchd_service_scenario(self, config, extra_assertions):
with self.run_service(Path(default_config_dir()), config):
def launchd_service_scenario(self, config, extra_assertions=None, use_token=False):
with self.run_service(Path(default_config_dir()), config, use_token=use_token):
self.launchctl_cmd("list")
self.launchctl_cmd("start")
wait_tunnel_ready(tunnel_url=config.get_url())
extra_assertions()
if extra_assertions is not None:
extra_assertions()
self.launchctl_cmd("stop")
os.remove(default_config_file())
@@ -105,27 +122,48 @@ class TestServiceMode:
self.sysv_service_scenario(config, tmp_path, assert_rotating_log)
def sysv_service_scenario(self, config, tmp_path, extra_assertions):
with self.run_service(tmp_path, config, root=True):
self.sysv_cmd("start")
@select_platform("Linux")
@pytest.mark.skipif(os.path.exists("/etc/cloudflared/config.yml"),
reason=f"There is already a config file in default path")
def test_sysv_service_with_token(self, tmp_path, component_tests_config):
additional_config = {
"loglevel": "debug",
}
config = component_tests_config(additional_config=additional_config)
# service install doesn't install the config file but in this case we want to use some default settings
# so we write the base config without the tunnel credentials and ID
config_path = write_config(tmp_path, config.base_config())
subprocess.run(["sudo", "cp", config_path, "/etc/cloudflared/config.yml"], check=True)
self.sysv_service_scenario(config, tmp_path, use_token=True)
def sysv_service_scenario(self, config, tmp_path, extra_assertions=None, use_token=False):
with self.run_service(tmp_path, config, root=True, use_token=use_token):
self.sysv_cmd("status")
wait_tunnel_ready(tunnel_url=config.get_url())
extra_assertions()
self.sysv_cmd("stop")
if extra_assertions is not None:
extra_assertions()
# Service install copies config file to /etc/cloudflared/config.yml
subprocess.run(["sudo", "rm", "/etc/cloudflared/config.yml"])
self.sysv_cmd("status", success=False)
@contextmanager
def run_service(self, tmp_path, config, root=False):
def run_service(self, tmp_path, config, root=False, use_token=False):
args = ["service", "install"]
if use_token:
args.append(config.get_token())
try:
service = start_cloudflared(
tmp_path, config, cfd_args=["service", "install"], cfd_pre_args=[], capture_output=False, root=root)
tmp_path, config, cfd_args=args, cfd_pre_args=[], capture_output=False, root=root, skip_config_flag=use_token)
yield service
finally:
start_cloudflared(
tmp_path, config, cfd_args=["service", "uninstall"], cfd_pre_args=[], capture_output=False, root=root)
tmp_path, config, cfd_args=["service", "uninstall"], cfd_pre_args=[], capture_output=False, root=root, skip_config_flag=use_token)
def launchctl_cmd(self, action, success=True):
cmd = subprocess.run(
+35
View File
@@ -0,0 +1,35 @@
import base64
import json
from setup import get_config_from_file, persist_origin_cert
from util import start_cloudflared
class TestToken:
def test_get_token(self, tmp_path, component_tests_config):
config = component_tests_config()
tunnel_id = config.get_tunnel_id()
token_args = ["--origincert", cert_path(), "token", tunnel_id]
output = start_cloudflared(tmp_path, config, token_args)
assert parse_token(config.get_token()) == parse_token(output.stdout)
def test_get_credentials_file(self, tmp_path, component_tests_config):
config = component_tests_config()
tunnel_id = config.get_tunnel_id()
cred_file = tmp_path / "test_get_credentials_file.json"
token_args = ["--origincert", cert_path(), "token", "--cred-file", cred_file, tunnel_id]
start_cloudflared(tmp_path, config, token_args)
with open(cred_file) as json_file:
assert config.get_credentials_json() == json.load(json_file)
def cert_path():
return get_config_from_file()["origincert"]
def parse_token(token):
return json.loads(base64.b64decode(token))
+11 -3
View File
@@ -21,9 +21,14 @@ def write_config(directory, config):
def start_cloudflared(directory, config, cfd_args=["run"], cfd_pre_args=["tunnel"], new_process=False,
allow_input=False, capture_output=True, root=False):
config_path = write_config(directory, config.full_config)
allow_input=False, capture_output=True, root=False, skip_config_flag=False):
config_path = None
if not skip_config_flag:
config_path = write_config(directory, config.full_config)
cmd = cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root)
if new_process:
return run_cloudflared_background(cmd, allow_input, capture_output)
# By setting check=True, it will raise an exception if the process exits with non-zero exit code
@@ -36,7 +41,10 @@ def cloudflared_cmd(config, config_path, cfd_args, cfd_pre_args, root):
cmd += ["sudo"]
cmd += [config.cloudflared_binary]
cmd += cfd_pre_args
cmd += ["--config", str(config_path)]
if config_path is not None:
cmd += ["--config", str(config_path)]
cmd += cfd_args
LOGGER.info(f"Run cmd {cmd} with config {config}")
return cmd
+56 -23
View File
@@ -1,19 +1,21 @@
package config
import (
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
"time"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
yaml "gopkg.in/yaml.v2"
yaml "gopkg.in/yaml.v3"
"github.com/cloudflare/cloudflared/validation"
)
@@ -49,7 +51,7 @@ func DefaultConfigDirectory() string {
path := os.Getenv("CFDPATH")
if path == "" {
path = filepath.Join(os.Getenv("ProgramFiles(x86)"), "cloudflared")
if _, err := os.Stat(path); os.IsNotExist(err) { //doesn't exist, so return an empty failure string
if _, err := os.Stat(path); os.IsNotExist(err) { // doesn't exist, so return an empty failure string
return ""
}
}
@@ -138,7 +140,7 @@ func FindOrCreateConfigPath() string {
defer file.Close()
logDir := DefaultLogDirectory()
_ = os.MkdirAll(logDir, os.ModePerm) //try and create it. Doesn't matter if it succeed or not, only byproduct will be no logs
_ = os.MkdirAll(logDir, os.ModePerm) // try and create it. Doesn't matter if it succeed or not, only byproduct will be no logs
c := Root{
LogDirectory: logDir,
@@ -175,9 +177,9 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
}
type UnvalidatedIngressRule struct {
Hostname string `json:"hostname"`
Path string `json:"path"`
Service string `json:"service"`
Hostname string `json:"hostname,omitempty"`
Path string `json:"path,omitempty"`
Service string `json:"service,omitempty"`
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
}
@@ -190,41 +192,41 @@ type UnvalidatedIngressRule struct {
// - To specify a time.Duration in json, use int64 of the nanoseconds
type OriginRequestConfig struct {
// HTTP proxy timeout for establishing a new connection
ConnectTimeout *time.Duration `yaml:"connectTimeout" json:"connectTimeout"`
ConnectTimeout *CustomDuration `yaml:"connectTimeout" json:"connectTimeout,omitempty"`
// HTTP proxy timeout for completing a TLS handshake
TLSTimeout *time.Duration `yaml:"tlsTimeout" json:"tlsTimeout"`
TLSTimeout *CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout,omitempty"`
// HTTP proxy TCP keepalive duration
TCPKeepAlive *time.Duration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
TCPKeepAlive *CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive,omitempty"`
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
NoHappyEyeballs *bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs,omitempty"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
KeepAliveConnections *int `yaml:"keepAliveConnections" json:"keepAliveConnections,omitempty"`
// HTTP proxy timeout for closing an idle connection
KeepAliveTimeout *time.Duration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
KeepAliveTimeout *CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout,omitempty"`
// Sets the HTTP Host header for the local webserver.
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader"`
HTTPHostHeader *string `yaml:"httpHostHeader" json:"httpHostHeader,omitempty"`
// Hostname on the origin server certificate.
OriginServerName *string `yaml:"originServerName" json:"originServerName"`
OriginServerName *string `yaml:"originServerName" json:"originServerName,omitempty"`
// Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare.
CAPool *string `yaml:"caPool" json:"caPool"`
CAPool *string `yaml:"caPool" json:"caPool,omitempty"`
// Disables TLS verification of the certificate presented by your origin.
// Will allow any certificate from the origin to be accepted.
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify"`
NoTLSVerify *bool `yaml:"noTLSVerify" json:"noTLSVerify,omitempty"`
// Disables chunked transfer encoding.
// Useful if you are running a WSGI server.
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
DisableChunkedEncoding *bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding,omitempty"`
// Runs as jump host
BastionMode *bool `yaml:"bastionMode" json:"bastionMode"`
BastionMode *bool `yaml:"bastionMode" json:"bastionMode,omitempty"`
// Listen address for the proxy.
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress"`
ProxyAddress *string `yaml:"proxyAddress" json:"proxyAddress,omitempty"`
// Listen port for the proxy.
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort"`
ProxyPort *uint `yaml:"proxyPort" json:"proxyPort,omitempty"`
// Valid options are 'socks' or empty.
ProxyType *string `yaml:"proxyType" json:"proxyType"`
ProxyType *string `yaml:"proxyType" json:"proxyType,omitempty"`
// IP rules for the proxy service
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules"`
IPRules []IngressIPRule `yaml:"ipRules" json:"ipRules,omitempty"`
}
type IngressIPRule struct {
@@ -390,7 +392,7 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe
// Parse it again, with strict mode, to find warnings.
if file, err := os.Open(configFile); err == nil {
decoder := yaml.NewDecoder(file)
decoder.SetStrict(true)
decoder.KnownFields(true)
var unusedConfig configFileSettings
if err := decoder.Decode(&unusedConfig); err != nil {
warnings = err.Error()
@@ -399,3 +401,34 @@ func ReadConfigFile(c *cli.Context, log *zerolog.Logger) (settings *configFileSe
return &configuration, warnings, nil
}
// A CustomDuration is a Duration that has custom serialization for JSON.
// JSON in Javascript assumes that int fields are 32 bits and Duration fields are deserialized assuming that numbers
// are in nanoseconds, which in 32bit integers limits to just 2 seconds.
// This type assumes that when serializing/deserializing from JSON, that the number is in seconds, while it maintains
// the YAML serde assumptions.
type CustomDuration struct {
time.Duration
}
func (s CustomDuration) MarshalJSON() ([]byte, error) {
return json.Marshal(s.Duration.Seconds())
}
func (s *CustomDuration) UnmarshalJSON(data []byte) error {
seconds, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
s.Duration = time.Duration(seconds * int64(time.Second))
return nil
}
func (s *CustomDuration) MarshalYAML() (interface{}, error) {
return s.Duration.String(), nil
}
func (s *CustomDuration) UnmarshalYAML(unmarshal func(interface{}) error) error {
return unmarshal(&s.Duration)
}
+47 -11
View File
@@ -6,6 +6,7 @@ import (
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
yaml "gopkg.in/yaml.v2"
)
@@ -110,14 +111,13 @@ counters:
}
func TestUnmarshalOriginRequestConfig(t *testing.T) {
raw := []byte(`
var rawConfig = []byte(`
{
"connectTimeout": 10000000000,
"tlsTimeout": 30000000000,
"tcpKeepAlive": 30000000000,
"connectTimeout": 10,
"tlsTimeout": 30,
"tcpKeepAlive": 30,
"noHappyEyeballs": true,
"keepAliveTimeout": 60000000000,
"keepAliveTimeout": 60,
"keepAliveConnections": 10,
"httpHostHeader": "app.tunnel.com",
"originServerName": "app.tunnel.com",
@@ -142,13 +142,41 @@ func TestUnmarshalOriginRequestConfig(t *testing.T) {
]
}
`)
func TestMarshalUnmarshalOriginRequest(t *testing.T) {
testCases := []struct {
name string
marshalFunc func(in interface{}) (out []byte, err error)
unMarshalFunc func(in []byte, out interface{}) (err error)
baseUnit time.Duration
}{
{"json", json.Marshal, json.Unmarshal, time.Second},
{"yaml", yaml.Marshal, yaml.Unmarshal, time.Nanosecond},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
assertConfig(t, tc.marshalFunc, tc.unMarshalFunc, tc.baseUnit)
})
}
}
func assertConfig(
t *testing.T,
marshalFunc func(in interface{}) (out []byte, err error),
unMarshalFunc func(in []byte, out interface{}) (err error),
baseUnit time.Duration,
) {
var config OriginRequestConfig
assert.NoError(t, json.Unmarshal(raw, &config))
assert.Equal(t, time.Second*10, *config.ConnectTimeout)
assert.Equal(t, time.Second*30, *config.TLSTimeout)
assert.Equal(t, time.Second*30, *config.TCPKeepAlive)
var config2 OriginRequestConfig
assert.NoError(t, unMarshalFunc(rawConfig, &config))
assert.Equal(t, baseUnit*10, config.ConnectTimeout.Duration)
assert.Equal(t, baseUnit*30, config.TLSTimeout.Duration)
assert.Equal(t, baseUnit*30, config.TCPKeepAlive.Duration)
assert.Equal(t, true, *config.NoHappyEyeballs)
assert.Equal(t, time.Second*60, *config.KeepAliveTimeout)
assert.Equal(t, baseUnit*60, config.KeepAliveTimeout.Duration)
assert.Equal(t, 10, *config.KeepAliveConnections)
assert.Equal(t, "app.tunnel.com", *config.HTTPHostHeader)
assert.Equal(t, "app.tunnel.com", *config.OriginServerName)
@@ -176,4 +204,12 @@ func TestUnmarshalOriginRequestConfig(t *testing.T) {
},
}
assert.Equal(t, ipRules, config.IPRules)
// validate that serializing and deserializing again matches the deserialization from raw string
result, err := marshalFunc(config)
require.NoError(t, err)
err = unMarshalFunc(result, &config2)
require.NoError(t, err)
require.Equal(t, config2, config)
}
+30 -2
View File
@@ -2,6 +2,7 @@ package connection
import (
"context"
"encoding/base64"
"fmt"
"io"
"math"
@@ -11,7 +12,9 @@ import (
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/cloudflare/cloudflared/tracing"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
)
@@ -27,6 +30,7 @@ var switchingProtocolText = fmt.Sprintf("%d %s", http.StatusSwitchingProtocols,
type Orchestrator interface {
UpdateConfig(version int32, config []byte) *pogs.UpdateConfigurationResponse
GetConfigJSON() ([]byte, error)
GetOriginProxy() (OriginProxy, error)
}
@@ -41,7 +45,6 @@ type Credentials struct {
AccountTag string
TunnelSecret []byte
TunnelID uuid.UUID
TunnelName string
}
func (c *Credentials) Auth() pogs.TunnelAuth {
@@ -51,6 +54,30 @@ func (c *Credentials) Auth() pogs.TunnelAuth {
}
}
// TunnelToken are Credentials but encoded with custom fields namings.
type TunnelToken struct {
AccountTag string `json:"a"`
TunnelSecret []byte `json:"s"`
TunnelID uuid.UUID `json:"t"`
}
func (t TunnelToken) Credentials() Credentials {
return Credentials{
AccountTag: t.AccountTag,
TunnelSecret: t.TunnelSecret,
TunnelID: t.TunnelID,
}
}
func (t TunnelToken) Encode() (string, error) {
val, err := json.Marshal(t)
if err != nil {
return "", errors.Wrap(err, "could not JSON encode token")
}
return base64.StdEncoding.EncodeToString(val), nil
}
type ClassicTunnelProperties struct {
Hostname string
OriginCert []byte
@@ -66,6 +93,7 @@ const (
TypeTCP
TypeControlStream
TypeHTTP
TypeConfiguration
)
// ShouldFlush returns whether this kind of connection should actively flush data
@@ -95,7 +123,7 @@ func (t Type) String() string {
// OriginProxy is how data flows from cloudflared to the origin services running behind it.
type OriginProxy interface {
ProxyHTTP(w ResponseWriter, req *http.Request, isWebsocket bool) error
ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error
ProxyTCP(ctx context.Context, rwa ReadWriteAcker, req *TCPRequest) error
}
+7 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
)
@@ -41,6 +42,10 @@ type mockOrchestrator struct {
originProxy OriginProxy
}
func (mcr *mockOrchestrator) GetConfigJSON() ([]byte, error) {
return nil, fmt.Errorf("not implemented")
}
func (*mockOrchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
return &tunnelpogs.UpdateConfigurationResponse{
LastAppliedVersion: version,
@@ -55,9 +60,10 @@ type mockOriginProxy struct{}
func (moc *mockOriginProxy) ProxyHTTP(
w ResponseWriter,
req *http.Request,
tr *tracing.TracedRequest,
isWebsocket bool,
) error {
req := tr.Request
if isWebsocket {
switch req.URL.Path {
case "/ws/echo":
+19 -2
View File
@@ -30,11 +30,15 @@ type controlStream struct {
// ControlStreamHandler registers connections with origintunneld and initiates graceful shutdown.
type ControlStreamHandler interface {
// ServeControlStream handles the control plane of the transport in the current goroutine calling this
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error
ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error
// IsStopped tells whether the method above has finished
IsStopped() bool
}
type TunnelConfigJSONGetter interface {
GetConfigJSON() ([]byte, error)
}
// NewControlStream returns a new instance of ControlStreamHandler
func NewControlStream(
observer *Observer,
@@ -63,15 +67,28 @@ func (c *controlStream) ServeControlStream(
ctx context.Context,
rw io.ReadWriteCloser,
connOptions *tunnelpogs.ConnectionOptions,
tunnelConfigGetter TunnelConfigJSONGetter,
) error {
rpcClient := c.newRPCClientFunc(ctx, rw, c.observer.log)
if err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer); err != nil {
registrationDetails, err := rpcClient.RegisterConnection(ctx, c.namedTunnelProperties, connOptions, c.connIndex, c.observer)
if err != nil {
rpcClient.Close()
return err
}
c.connectedFuse.Connected()
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration
if c.connIndex == 0 && !registrationDetails.TunnelIsRemotelyManaged {
if tunnelConfig, err := tunnelConfigGetter.GetConfigJSON(); err == nil {
if err := rpcClient.SendLocalConfiguration(ctx, tunnelConfig, c.observer); err != nil {
c.observer.log.Err(err).Msg("unable to send local configuration")
}
} else {
c.observer.log.Err(err).Msg("failed to obtain current configuration")
}
}
c.waitForUnregister(ctx, rpcClient)
return nil
}
+2 -1
View File
@@ -12,6 +12,7 @@ import (
"golang.org/x/sync/errgroup"
"github.com/cloudflare/cloudflared/h2mux"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
)
@@ -233,7 +234,7 @@ func (h *h2muxConnection) ServeStream(stream *h2mux.MuxedStream) error {
return err
}
err = originProxy.ProxyHTTP(respWriter, req, sourceConnectionType == TypeWebsocket)
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), sourceConnectionType == TypeWebsocket)
if err != nil {
respWriter.WriteErrorResponse()
}
+53 -7
View File
@@ -2,6 +2,7 @@ package connection
import (
"context"
gojson "encoding/json"
"fmt"
"io"
"net"
@@ -14,6 +15,7 @@ import (
"github.com/rs/zerolog"
"golang.org/x/net/http2"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -23,6 +25,7 @@ const (
InternalTCPProxySrcHeader = "Cf-Cloudflared-Proxy-Src"
WebsocketUpgrade = "websocket"
ControlStreamUpgrade = "control-stream"
ConfigurationUpdate = "update-configuration"
)
var errEdgeConnectionClosed = fmt.Errorf("connection with edge closed")
@@ -100,7 +103,7 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
connType := determineHTTP2Type(r)
handleMissingRequestParts(connType, r)
respWriter, err := NewHTTP2RespWriter(r, w, connType)
respWriter, err := NewHTTP2RespWriter(r, w, connType, c.log)
if err != nil {
c.observer.log.Error().Msg(err.Error())
return
@@ -114,15 +117,23 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch connType {
case TypeControlStream:
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions); err != nil {
if err := c.controlStreamHandler.ServeControlStream(r.Context(), respWriter, c.connOptions, c.orchestrator); err != nil {
c.controlStreamErr = err
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
case TypeConfiguration:
if err := c.handleConfigurationUpdate(respWriter, r); err != nil {
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
}
case TypeWebsocket, TypeHTTP:
stripWebsocketUpgradeHeader(r)
if err := originProxy.ProxyHTTP(respWriter, r, connType == TypeWebsocket); err != nil {
// Check for tracing on request
tr := tracing.NewTracedRequest(r)
if err := originProxy.ProxyHTTP(respWriter, tr, connType == TypeWebsocket); err != nil {
err := fmt.Errorf("Failed to proxy HTTP: %w", err)
c.log.Error().Err(err)
respWriter.WriteErrorResponse()
@@ -152,6 +163,26 @@ func (c *HTTP2Connection) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// ConfigurationUpdateBody is the representation followed by the edge to send updates to cloudflared.
type ConfigurationUpdateBody struct {
Version int32 `json:"version"`
Config gojson.RawMessage `json:"config"`
}
func (c *HTTP2Connection) handleConfigurationUpdate(respWriter *http2RespWriter, r *http.Request) error {
var configBody ConfigurationUpdateBody
if err := json.NewDecoder(r.Body).Decode(&configBody); err != nil {
return err
}
resp := c.orchestrator.UpdateConfig(configBody.Version, configBody.Config)
bdy, err := json.Marshal(resp)
if err != nil {
return err
}
_, err = respWriter.Write(bdy)
return err
}
func (c *HTTP2Connection) close() {
// Wait for all serve HTTP handlers to return
c.activeRequestsWG.Wait()
@@ -163,14 +194,16 @@ type http2RespWriter struct {
w http.ResponseWriter
flusher http.Flusher
shouldFlush bool
log *zerolog.Logger
}
func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type) (*http2RespWriter, error) {
func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type, log *zerolog.Logger) (*http2RespWriter, error) {
flusher, isFlusher := w.(http.Flusher)
if !isFlusher {
respWriter := &http2RespWriter{
r: r.Body,
w: w,
r: r.Body,
w: w,
log: log,
}
respWriter.WriteErrorResponse()
return nil, fmt.Errorf("%T doesn't implement http.Flusher", w)
@@ -181,6 +214,7 @@ func NewHTTP2RespWriter(r *http.Request, w http.ResponseWriter, connType Type) (
w: w,
flusher: flusher,
shouldFlush: connType.shouldFlush(),
log: log,
}, nil
}
@@ -197,6 +231,12 @@ func (rp *http2RespWriter) WriteRespHeaders(status int, header http.Header) erro
dest[name] = values
}
if h2name == tracing.IntCloudflaredTracingHeader {
// Add cf-int-cloudflared-tracing header outside of serialized userHeaders
rp.w.Header()[tracing.CanonicalCloudflaredTracingHeader] = values
continue
}
if !IsControlResponseHeader(h2name) || IsWebsocketClientHeader(h2name) {
// User headers, on the other hand, must all be serialized so that
// HTTP/2 header validation won't be applied to HTTP/1 header values
@@ -239,7 +279,7 @@ func (rp *http2RespWriter) Write(p []byte) (n int, err error) {
// Implementer of OriginClient should make sure it doesn't write to the connection after Proxy returns
// Register a recover routine just in case.
if r := recover(); r != nil {
println(fmt.Sprintf("Recover from http2 response writer panic, error %s", debug.Stack()))
rp.log.Debug().Msgf("Recover from http2 response writer panic, error %s", debug.Stack())
}
}()
n, err = rp.w.Write(p)
@@ -255,6 +295,8 @@ func (rp *http2RespWriter) Close() error {
func determineHTTP2Type(r *http.Request) Type {
switch {
case isConfigurationUpdate(r):
return TypeConfiguration
case isWebsocketUpgrade(r):
return TypeWebsocket
case IsTCPStream(r):
@@ -288,6 +330,10 @@ func isWebsocketUpgrade(r *http.Request) bool {
return r.Header.Get(InternalUpgradeHeader) == WebsocketUpgrade
}
func isConfigurationUpdate(r *http.Request) bool {
return r.Header.Get(InternalUpgradeHeader) == ConfigurationUpdate
}
// IsTCPStream discerns if the connection request needs a tcp stream proxy.
func IsTCPStream(r *http.Request) bool {
return r.Header.Get(InternalTCPProxySrcHeader) != ""
+50 -5
View File
@@ -1,6 +1,7 @@
package connection
import (
"bytes"
"context"
"errors"
"fmt"
@@ -14,6 +15,7 @@ import (
"time"
"github.com/gobwas/ws/wsutil"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -53,6 +55,41 @@ func newTestHTTP2Connection() (*HTTP2Connection, net.Conn) {
), edgeConn
}
func TestHTTP2ConfigurationSet(t *testing.T) {
http2Conn, edgeConn := newTestHTTP2Connection()
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
http2Conn.Serve(ctx)
}()
edgeHTTP2Conn, err := testTransport.NewClientConn(edgeConn)
require.NoError(t, err)
endpoint := fmt.Sprintf("http://localhost:8080/ok")
reqBody := []byte(`{
"version": 2,
"config": {"warp-routing": {"enabled": true}, "originRequest" : {"connectTimeout": 10}, "ingress" : [ {"hostname": "test", "service": "https://localhost:8000" } , {"service": "http_status:404"} ]}}
`)
reader := bytes.NewReader(reqBody)
req, err := http.NewRequestWithContext(ctx, http.MethodPut, endpoint, reader)
require.NoError(t, err)
req.Header.Set(InternalUpgradeHeader, ConfigurationUpdate)
resp, err := edgeHTTP2Conn.RoundTrip(req)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
bdy, err := ioutil.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, `{"lastAppliedVersion":2,"err":null}`, string(bdy))
cancel()
wg.Wait()
}
func TestServeHTTP(t *testing.T) {
tests := []testRequest{
{
@@ -130,18 +167,26 @@ type mockNamedTunnelRPCClient struct {
unregistered chan struct{}
}
func (mc mockNamedTunnelRPCClient) SendLocalConfiguration(c context.Context, config []byte, observer *Observer) error {
return nil
}
func (mc mockNamedTunnelRPCClient) RegisterConnection(
c context.Context,
properties *NamedTunnelProperties,
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) error {
) (*tunnelpogs.ConnectionDetails, error) {
if mc.shouldFail != nil {
return mc.shouldFail
return nil, mc.shouldFail
}
close(mc.registered)
return nil
return &tunnelpogs.ConnectionDetails{
Location: "LIS",
UUID: uuid.New(),
TunnelIsRemotelyManaged: false,
}, nil
}
func (mc mockNamedTunnelRPCClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
@@ -441,7 +486,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
select {
case <-rpcClientFactory.registered:
break //ok
break // ok
case <-time.Tick(time.Second):
t.Fatal("timeout out waiting for registration")
}
@@ -451,7 +496,7 @@ func TestGracefulShutdownHTTP2(t *testing.T) {
select {
case <-rpcClientFactory.unregistered:
break //ok
break // ok
case <-time.Tick(time.Second):
t.Fatal("timeout out waiting for unregistered signal")
}
+40
View File
@@ -13,6 +13,7 @@ const (
MetricsNamespace = "cloudflared"
TunnelSubsystem = "tunnel"
muxerSubsystem = "muxer"
configSubsystem = "config"
)
type muxerMetrics struct {
@@ -36,6 +37,11 @@ type muxerMetrics struct {
compRateAve *prometheus.GaugeVec
}
type localConfigMetrics struct {
pushes prometheus.Counter
pushesErrors prometheus.Counter
}
type tunnelMetrics struct {
timerRetries prometheus.Gauge
serverLocations *prometheus.GaugeVec
@@ -51,6 +57,39 @@ type tunnelMetrics struct {
muxerMetrics *muxerMetrics
tunnelsHA tunnelsForHA
userHostnamesCounts *prometheus.CounterVec
localConfigMetrics *localConfigMetrics
}
func newLocalConfigMetrics() *localConfigMetrics {
pushesMetric := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: configSubsystem,
Name: "local_config_pushes",
Help: "Number of local configuration pushes to the edge",
},
)
pushesErrorsMetric := prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: configSubsystem,
Name: "local_config_pushes_errors",
Help: "Number of errors occurred during local configuration pushes",
},
)
prometheus.MustRegister(
pushesMetric,
pushesErrorsMetric,
)
return &localConfigMetrics{
pushes: pushesMetric,
pushesErrors: pushesErrorsMetric,
}
}
func newMuxerMetrics() *muxerMetrics {
@@ -386,6 +425,7 @@ func initTunnelMetrics() *tunnelMetrics {
regFail: registerFail,
rpcFail: rpcFail,
userHostnamesCounts: userHostnamesCounts,
localConfigMetrics: newLocalConfigMetrics(),
}
}
+3 -3
View File
@@ -19,7 +19,7 @@ const (
edgeH2TLSServerName = "h2.cftunnel.com"
// edgeQUICServerName is the server name to establish quic connection with edge.
edgeQUICServerName = "quic.cftunnel.com"
autoSelectFlag = "auto"
AutoSelectFlag = "auto"
)
var (
@@ -247,7 +247,7 @@ func selectNamedTunnelProtocols(
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
// fallback on failures.
if protocolFlag == autoSelectFlag {
if protocolFlag == AutoSelectFlag {
return newAutoProtocolSelector(protocol, []Protocol{QUIC, HTTP2, H2mux}, threshold, fetchFunc, ttl, log), nil
}
@@ -272,7 +272,7 @@ func selectWarpRoutingProtocols(
// If the user does not pick (hopefully the majority) then we use the one derived from the TXT DNS record and
// fallback on failures.
if protocolFlag == autoSelectFlag {
if protocolFlag == AutoSelectFlag {
return newAutoProtocolSelector(protocol, []Protocol{QUICWarp, HTTP2Warp}, threshold, fetchFunc, ttl, log), nil
}
+12 -12
View File
@@ -91,14 +91,14 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "named tunnel quic and http2 disabled",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: H2mux,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}, edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: -1}),
namedTunnelConfig: testNamedTunnelProperties,
},
{
name: "named tunnel quic disabled",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: HTTP2,
// Hasfallback true is because if http2 fails, then we further fallback to h2mux.
hasFallback: true,
@@ -108,21 +108,21 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "named tunnel auto all http2 disabled",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: H2mux,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: -1}),
namedTunnelConfig: testNamedTunnelProperties,
},
{
name: "named tunnel auto to h2mux",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: H2mux,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 0}),
namedTunnelConfig: testNamedTunnelProperties,
},
{
name: "named tunnel auto to http2",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: HTTP2,
hasFallback: true,
expectedFallback: H2mux,
@@ -131,7 +131,7 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "named tunnel auto to quic",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: QUIC,
hasFallback: true,
expectedFallback: HTTP2,
@@ -167,7 +167,7 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "warp routing quic",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: QUICWarp,
hasFallback: true,
expectedFallback: HTTP2Warp,
@@ -177,7 +177,7 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "warp routing auto",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: HTTP2Warp,
hasFallback: false,
fetchFunc: mockFetcher(false, edgediscovery.ProtocolPercent{Protocol: "http2", Percentage: 100}),
@@ -186,7 +186,7 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "warp routing auto- quic",
protocol: "auto",
protocol: AutoSelectFlag,
expectedProtocol: QUICWarp,
hasFallback: true,
expectedFallback: HTTP2Warp,
@@ -209,7 +209,7 @@ func TestNewProtocolSelector(t *testing.T) {
},
{
name: "named tunnel fetch error",
protocol: "auto",
protocol: AutoSelectFlag,
fetchFunc: mockFetcher(true),
namedTunnelConfig: testNamedTunnelProperties,
expectedProtocol: HTTP2,
@@ -237,7 +237,7 @@ func TestNewProtocolSelector(t *testing.T) {
func TestAutoProtocolSelectorRefresh(t *testing.T) {
fetcher := dynamicMockFetcher{}
selector, err := NewProtocolSelector("auto", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), testNoTTL, &log)
assert.NoError(t, err)
assert.Equal(t, H2mux, selector.Current())
@@ -297,7 +297,7 @@ func TestHTTP2ProtocolSelectorRefresh(t *testing.T) {
func TestProtocolSelectorRefreshTTL(t *testing.T) {
fetcher := dynamicMockFetcher{}
fetcher.protocolPercents = edgediscovery.ProtocolPercents{edgediscovery.ProtocolPercent{Protocol: "quic", Percentage: 100}}
selector, err := NewProtocolSelector("auto", noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
selector, err := NewProtocolSelector(AutoSelectFlag, noWarpRoutingEnabled, testNamedTunnelProperties, fetcher.fetch(), time.Hour, &log)
assert.NoError(t, err)
assert.Equal(t, QUIC, selector.Current())
+10 -6
View File
@@ -20,6 +20,7 @@ import (
"github.com/cloudflare/cloudflared/datagramsession"
"github.com/cloudflare/cloudflared/ingress"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -110,7 +111,7 @@ func (q *QUICConnection) Serve(ctx context.Context) error {
func (q *QUICConnection) serveControlStream(ctx context.Context, controlStream quic.Stream) error {
// This blocks until the control plane is done.
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions)
err := q.controlStreamHandler.ServeControlStream(ctx, controlStream, q.connOptions, q.orchestrator)
if err != nil {
// Not wrapping error here to be consistent with the http2 message.
return err
@@ -181,13 +182,13 @@ func (q *QUICConnection) handleDataStream(stream *quicpogs.RequestServerStream)
}
switch connectRequest.Type {
case quicpogs.ConnectionTypeHTTP, quicpogs.ConnectionTypeWebsocket:
req, err := buildHTTPRequest(connectRequest, stream)
tracedReq, err := buildHTTPRequest(connectRequest, stream)
if err != nil {
return err
}
w := newHTTPResponseAdapter(stream)
return originProxy.ProxyHTTP(w, req, connectRequest.Type == quicpogs.ConnectionTypeWebsocket)
return originProxy.ProxyHTTP(w, tracedReq, connectRequest.Type == quicpogs.ConnectionTypeWebsocket)
case quicpogs.ConnectionTypeTCP:
rwa := &streamReadWriteAcker{stream}
return originProxy.ProxyTCP(context.Background(), rwa, &TCPRequest{Dest: connectRequest.Dest})
@@ -305,7 +306,7 @@ func (hrw httpResponseAdapter) WriteErrorResponse(err error) {
hrw.WriteConnectResponseData(err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
}
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*http.Request, error) {
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*tracing.TracedRequest, error) {
metadata := connectRequest.MetadataMap()
dest := connectRequest.Dest
method := metadata[HTTPMethodKey]
@@ -342,10 +343,13 @@ func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadClose
// * there is no transfer-encoding=chunked already set.
// So, if transfer cannot be chunked and content length is 0, we dont set a request body.
if !isWebsocket && !isTransferEncodingChunked(req) && req.ContentLength == 0 {
req.Body = nil
req.Body = http.NoBody
}
stripWebsocketUpgradeHeader(req)
return req, err
// Check for tracing on request
tracedReq := tracing.NewTracedRequest(req)
return tracedReq, err
}
func setContentLength(req *http.Request) error {
+6 -4
View File
@@ -24,6 +24,7 @@ import (
"github.com/cloudflare/cloudflared/datagramsession"
quicpogs "github.com/cloudflare/cloudflared/quic"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -162,7 +163,7 @@ type fakeControlStream struct {
ControlStreamHandler
}
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions) error {
func (fakeControlStream) ServeControlStream(ctx context.Context, rw io.ReadWriteCloser, connOptions *tunnelpogs.ConnectionOptions, tunnelConfigGetter TunnelConfigJSONGetter) error {
<-ctx.Done()
return nil
}
@@ -219,9 +220,10 @@ func quicServer(
type mockOriginProxyWithRequest struct{}
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, r *http.Request, isWebsocket bool) error {
func (moc *mockOriginProxyWithRequest) ProxyHTTP(w ResponseWriter, tr *tracing.TracedRequest, isWebsocket bool) error {
// These are a series of crude tests to ensure the headers and http related data is transferred from
// metadata.
r := tr.Request
if r.Method == "" {
return errors.New("method not sent")
}
@@ -345,7 +347,7 @@ func TestBuildHTTPRequest(t *testing.T) {
},
ContentLength: 0,
Host: "cf.host",
Body: nil,
Body: http.NoBody,
},
body: io.NopCloser(&bytes.Buffer{}),
},
@@ -478,7 +480,7 @@ func TestBuildHTTPRequest(t *testing.T) {
req, err := buildHTTPRequest(test.connectRequest, test.body)
assert.NoError(t, err)
test.req = test.req.WithContext(req.Context())
assert.Equal(t, test.req, req)
assert.Equal(t, test.req, req.Request)
})
}
}
+21 -5
View File
@@ -58,6 +58,11 @@ type NamedTunnelRPCClient interface {
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) (*tunnelpogs.ConnectionDetails, error)
SendLocalConfiguration(
c context.Context,
config []byte,
observer *Observer,
) error
GracefulShutdown(ctx context.Context, gracePeriod time.Duration)
Close()
@@ -90,7 +95,7 @@ func (rsc *registrationServerClient) RegisterConnection(
options *tunnelpogs.ConnectionOptions,
connIndex uint8,
observer *Observer,
) error {
) (*tunnelpogs.ConnectionDetails, error) {
conn, err := rsc.client.RegisterConnection(
ctx,
properties.Credentials.Auth(),
@@ -101,10 +106,10 @@ func (rsc *registrationServerClient) RegisterConnection(
if err != nil {
if err.Error() == DuplicateConnectionError {
observer.metrics.regFail.WithLabelValues("dup_edge_conn", "registerConnection").Inc()
return errDuplicationConnection
return nil, errDuplicationConnection
}
observer.metrics.regFail.WithLabelValues("server_error", "registerConnection").Inc()
return serverRegistrationErrorFromRPC(err)
return nil, serverRegistrationErrorFromRPC(err)
}
observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
@@ -112,7 +117,18 @@ func (rsc *registrationServerClient) RegisterConnection(
observer.logServerInfo(connIndex, conn.Location, fmt.Sprintf("Connection %s registered", conn.UUID))
observer.sendConnectedEvent(connIndex, conn.Location)
return nil
return conn, nil
}
func (rsc *registrationServerClient) SendLocalConfiguration(ctx context.Context, config []byte, observer *Observer) (err error) {
observer.metrics.localConfigMetrics.pushes.Inc()
defer func() {
if err != nil {
observer.metrics.localConfigMetrics.pushesErrors.Inc()
}
}()
return rsc.client.SendLocalConfiguration(ctx, config)
}
func (rsc *registrationServerClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
@@ -274,7 +290,7 @@ func (h *h2muxConnection) registerNamedTunnel(
rpcClient := h.newRPCClientFunc(ctx, stream, h.observer.log)
defer rpcClient.Close()
if err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
if _, err = rpcClient.RegisterConnection(ctx, namedTunnel, connOptions, h.connIndex, h.observer); err != nil {
return err
}
return nil
+58 -3
View File
@@ -2,7 +2,9 @@ package datagramsession
import (
"context"
"fmt"
"io"
"time"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
@@ -12,6 +14,11 @@ import (
const (
requestChanCapacity = 16
defaultReqTimeout = time.Second * 5
)
var (
errSessionManagerClosed = fmt.Errorf("session manager closed")
)
// Manager defines the APIs to manage sessions from the same transport.
@@ -28,20 +35,25 @@ type manager struct {
registrationChan chan *registerSessionEvent
unregistrationChan chan *unregisterSessionEvent
datagramChan chan *newDatagram
closedChan chan struct{}
transport transport
sessions map[uuid.UUID]*Session
log *zerolog.Logger
// timeout waiting for an API to finish. This can be overriden in test
timeout time.Duration
}
func NewManager(transport transport, log *zerolog.Logger) Manager {
func NewManager(transport transport, log *zerolog.Logger) *manager {
return &manager{
registrationChan: make(chan *registerSessionEvent),
unregistrationChan: make(chan *unregisterSessionEvent),
// datagramChan is buffered, so it can read more datagrams from transport while the event loop is processing other events
datagramChan: make(chan *newDatagram, requestChanCapacity),
closedChan: make(chan struct{}),
transport: transport,
sessions: make(map[uuid.UUID]*Session),
log: log,
timeout: defaultReqTimeout,
}
}
@@ -85,27 +97,67 @@ func (m *manager) Serve(ctx context.Context) error {
}
}
})
return errGroup.Wait()
err := errGroup.Wait()
close(m.closedChan)
m.shutdownSessions(err)
return err
}
func (m *manager) shutdownSessions(err error) {
if err == nil {
err = errSessionManagerClosed
}
closeSessionErr := &errClosedSession{
message: err.Error(),
// Usually connection with remote has been closed, so set this to true to skip unregistering from remote
byRemote: true,
}
for _, s := range m.sessions {
s.close(closeSessionErr)
}
}
func (m *manager) RegisterSession(ctx context.Context, sessionID uuid.UUID, originProxy io.ReadWriteCloser) (*Session, error) {
ctx, cancel := context.WithTimeout(ctx, m.timeout)
defer cancel()
event := newRegisterSessionEvent(sessionID, originProxy)
select {
case <-ctx.Done():
m.log.Error().Msg("Datagram session registration timeout")
return nil, ctx.Err()
case m.registrationChan <- event:
session := <-event.resultChan
return session, nil
// Once closedChan is closed, manager won't accept more registration because nothing is
// reading from registrationChan and it's an unbuffered channel
case <-m.closedChan:
return nil, errSessionManagerClosed
}
}
func (m *manager) registerSession(ctx context.Context, registration *registerSessionEvent) {
session := newSession(registration.sessionID, m.transport, registration.originProxy, m.log)
session := m.newSession(registration.sessionID, registration.originProxy)
m.sessions[registration.sessionID] = session
registration.resultChan <- session
}
func (m *manager) newSession(id uuid.UUID, dstConn io.ReadWriteCloser) *Session {
return &Session{
ID: id,
transport: m.transport,
dstConn: dstConn,
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
// drop instead of blocking because last active time only needs to be an approximation
activeAtChan: make(chan time.Time, 2),
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
closeChan: make(chan error, 2),
log: m.log,
}
}
func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, message string, byRemote bool) error {
ctx, cancel := context.WithTimeout(ctx, m.timeout)
defer cancel()
event := &unregisterSessionEvent{
sessionID: sessionID,
err: &errClosedSession{
@@ -115,9 +167,12 @@ func (m *manager) UnregisterSession(ctx context.Context, sessionID uuid.UUID, me
}
select {
case <-ctx.Done():
m.log.Error().Msg("Datagram session unregistration timeout")
return ctx.Err()
case m.unregistrationChan <- event:
return nil
case <-m.closedChan:
return errSessionManagerClosed
}
}
+71 -8
View File
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net"
"sync"
"testing"
"time"
@@ -21,12 +22,8 @@ func TestManagerServe(t *testing.T) {
msgs = 50
remoteUnregisterMsg = "eyeball closed connection"
)
log := zerolog.Nop()
transport := &mockQUICTransport{
reqChan: newDatagramChannel(1),
respChan: newDatagramChannel(1),
}
mg := NewManager(transport, &log)
mg, transport := newTestManager(1)
eyeballTracker := make(map[uuid.UUID]*datagramChannel)
for i := 0; i < sessions; i++ {
@@ -120,6 +117,65 @@ func TestManagerServe(t *testing.T) {
<-serveDone
}
func TestTimeout(t *testing.T) {
const (
testTimeout = time.Millisecond * 50
)
mg, _ := newTestManager(1)
mg.timeout = testTimeout
ctx := context.Background()
sessionID := uuid.New()
// session manager is not running, so event loop is not running and therefore calling the APIs should timeout
session, err := mg.RegisterSession(ctx, sessionID, nil)
require.ErrorIs(t, err, context.DeadlineExceeded)
require.Nil(t, session)
err = mg.UnregisterSession(ctx, sessionID, "session gone", true)
require.ErrorIs(t, err, context.DeadlineExceeded)
}
func TestCloseTransportCloseSessions(t *testing.T) {
mg, transport := newTestManager(1)
ctx := context.Background()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
err := mg.Serve(ctx)
require.Error(t, err)
}()
cfdConn, eyeballConn := net.Pipe()
session, err := mg.RegisterSession(ctx, uuid.New(), cfdConn)
require.NoError(t, err)
require.NotNil(t, session)
wg.Add(1)
go func() {
defer wg.Done()
_, err := eyeballConn.Write([]byte(t.Name()))
require.NoError(t, err)
transport.close()
}()
closedByRemote, err := session.Serve(ctx, time.Minute)
require.True(t, closedByRemote)
require.Error(t, err)
wg.Wait()
}
func newTestManager(capacity uint) (*manager, *mockQUICTransport) {
log := zerolog.Nop()
transport := &mockQUICTransport{
reqChan: newDatagramChannel(capacity),
respChan: newDatagramChannel(capacity),
}
return NewManager(transport, &log), transport
}
type mockOrigin struct {
expectMsgCount int
expectedMsg []byte
@@ -199,7 +255,10 @@ func (rc *datagramChannel) Send(ctx context.Context, sessionID uuid.UUID, payloa
case <-ctx.Done():
return ctx.Err()
case <-rc.closedChan:
return fmt.Errorf("datagram channel closed")
return &errClosedSession{
message: fmt.Errorf("datagram channel closed").Error(),
byRemote: true,
}
case rc.datagramChan <- &newDatagram{sessionID: sessionID, payload: payload}:
return nil
}
@@ -210,7 +269,11 @@ func (rc *datagramChannel) Receive(ctx context.Context) (uuid.UUID, []byte, erro
case <-ctx.Done():
return uuid.Nil, nil, ctx.Err()
case <-rc.closedChan:
return uuid.Nil, nil, fmt.Errorf("datagram channel closed")
err := &errClosedSession{
message: fmt.Errorf("datagram channel closed").Error(),
byRemote: true,
}
return uuid.Nil, nil, err
case msg := <-rc.datagramChan:
return msg.sessionID, msg.payload, nil
}
-14
View File
@@ -39,20 +39,6 @@ type Session struct {
log *zerolog.Logger
}
func newSession(id uuid.UUID, transport transport, dstConn io.ReadWriteCloser, log *zerolog.Logger) *Session {
return &Session{
ID: id,
transport: transport,
dstConn: dstConn,
// activeAtChan has low capacity. It can be full when there are many concurrent read/write. markActive() will
// drop instead of blocking because last active time only needs to be an approximation
activeAtChan: make(chan time.Time, 2),
// capacity is 2 because close() and dstToTransport routine in Serve() can write to this channel
closeChan: make(chan error, 2),
log: log,
}
}
func (s *Session) Serve(ctx context.Context, closeAfterIdle time.Duration) (closedByRemote bool, err error) {
go func() {
// QUIC implementation copies data to another buffer before returning https://github.com/lucas-clemente/quic-go/blob/v0.24.0/session.go#L1967-L1975
+11 -20
View File
@@ -11,7 +11,6 @@ import (
"time"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
)
@@ -41,12 +40,9 @@ func testSessionReturns(t *testing.T, closeBy closeMethod, closeAfterIdle time.D
sessionID := uuid.New()
cfdConn, originConn := net.Pipe()
payload := testPayload(sessionID)
transport := &mockQUICTransport{
reqChan: newDatagramChannel(1),
respChan: newDatagramChannel(1),
}
log := zerolog.Nop()
session := newSession(sessionID, transport, cfdConn, &log)
mg, _ := newTestManager(1)
session := mg.newSession(sessionID, cfdConn)
ctx, cancel := context.WithCancel(context.Background())
sessionDone := make(chan struct{})
@@ -117,12 +113,9 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
sessionID := uuid.New()
cfdConn, originConn := net.Pipe()
payload := testPayload(sessionID)
transport := &mockQUICTransport{
reqChan: newDatagramChannel(100),
respChan: newDatagramChannel(100),
}
log := zerolog.Nop()
session := newSession(sessionID, transport, cfdConn, &log)
mg, _ := newTestManager(100)
session := mg.newSession(sessionID, cfdConn)
startTime := time.Now()
activeUntil := startTime.Add(activeTime)
@@ -184,7 +177,8 @@ func testActiveSessionNotClosed(t *testing.T, readFromDst bool, writeToDst bool)
func TestMarkActiveNotBlocking(t *testing.T) {
const concurrentCalls = 50
session := newSession(uuid.New(), nil, nil, nil)
mg, _ := newTestManager(1)
session := mg.newSession(uuid.New(), nil)
var wg sync.WaitGroup
wg.Add(concurrentCalls)
for i := 0; i < concurrentCalls; i++ {
@@ -199,12 +193,9 @@ func TestMarkActiveNotBlocking(t *testing.T) {
func TestZeroBytePayload(t *testing.T) {
sessionID := uuid.New()
cfdConn, originConn := net.Pipe()
transport := &mockQUICTransport{
reqChan: newDatagramChannel(1),
respChan: newDatagramChannel(1),
}
log := zerolog.Nop()
session := newSession(sessionID, transport, cfdConn, &log)
mg, transport := newTestManager(1)
session := mg.newSession(sessionID, cfdConn)
ctx, cancel := context.WithCancel(context.Background())
errGroup, ctx := errgroup.WithContext(ctx)
+2 -2
View File
@@ -1,4 +1,4 @@
FROM golang:1.17.5 as builder
FROM golang:1.17.10 as builder
ENV GO111MODULE=on \
CGO_ENABLED=0
WORKDIR /go/src/github.com/cloudflare/cloudflared/
@@ -6,4 +6,4 @@ RUN apt-get update
COPY . .
# compile cloudflared
RUN make cloudflared
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
RUN cp /go/src/github.com/cloudflare/cloudflared/cloudflared /usr/local/bin/
+2 -1
View File
@@ -114,7 +114,8 @@ func (ed *Edge) GetDifferentAddr(connIndex int) (*allregions.EdgeAddr, error) {
// note: if oldAddr were not nil, it will become available on the next iteration
return nil, errNoAddressesLeft
}
log.Debug().Msg("edgediscovery - GetDifferentAddr: Giving connection its new address")
log.Debug().Msgf("edgediscovery - GetDifferentAddr: Giving connection its new address: %v from the address list: %v",
addr, ed.regions.AvailableAddrs())
return addr, nil
}
+38 -27
View File
@@ -3,59 +3,49 @@ module github.com/cloudflare/cloudflared
go 1.17
require (
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
github.com/coredns/coredns v1.8.7
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/fsnotify/fsnotify v1.4.9
github.com/gdamore/tcell v1.3.0
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.0.4
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/json-iterator/go v1.1.12
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucas-clemente/quic-go v0.24.0
github.com/mattn/go-colorable v0.1.8
github.com/miekg/dns v1.1.45
github.com/mitchellh/go-homedir v1.1.0
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.11.0
github.com/prometheus/client_golang v1.12.1
github.com/prometheus/client_model v0.2.0
github.com/prometheus/common v0.32.1 // indirect
github.com/rivo/tview v0.0.0-20200712113419-c65badfc3d92
github.com/rs/zerolog v1.20.0
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.1
github.com/urfave/cli/v2 v2.3.0
go.opentelemetry.io/contrib/propagators v0.22.0
go.opentelemetry.io/otel v1.6.3
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3
go.opentelemetry.io/otel/sdk v1.6.3
go.opentelemetry.io/otel/trace v1.6.3
go.opentelemetry.io/proto/otlp v0.15.0
go.uber.org/automaxprocs v1.4.0
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
google.golang.org/grpc v1.43.0 // indirect
gopkg.in/coreos/go-oidc.v2 v2.1.0
google.golang.org/protobuf v1.28.0
gopkg.in/coreos/go-oidc.v2 v2.2.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
zombiezen.com/go/capnproto2 v2.18.0+incompatible
)
@@ -63,16 +53,28 @@ require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/coredns/caddy v1.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 // indirect
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/gobwas/httphead v0.0.0-20200921212729-da3d93bc3c58 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
@@ -84,18 +86,27 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.1.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
google.golang.org/grpc v1.45.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)
replace github.com/urfave/cli/v2 => github.com/ipostelnik/cli/v2 v2.3.1-0.20210324024421-b6ea8234fe3d
replace github.com/lucas-clemente/quic-go => github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62
// Avoid 'CVE-2022-21698'
replace github.com/prometheus/golang_client => github.com/prometheus/golang_client v1.12.1
+51 -15
View File
@@ -100,13 +100,15 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
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 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE=
github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ=
github.com/chungthuang/quic-go v0.24.1-0.20220110095058-981dc498cb62 h1:PLTB4iA6sOgAItzQY642tYdcGKfG/7i2gu93JQGgUcM=
@@ -131,8 +133,6 @@ github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/coredns/coredns v1.8.7 h1:wVMjAnyFnY7Mc18AFO+9qbGD6ODPtdVUIlzoWrHr3hk=
github.com/coredns/coredns v1.8.7/go.mod h1:bFmbgEfeRz5aizL2VsQ5LRlsvJuXWkgG/MWG9zxqjVM=
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73 h1:7CNPV0LWRCa1FNmqg700pbXhzvmoaXKyfxWRkjRym7Q=
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
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=
@@ -212,6 +212,11 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
@@ -234,6 +239,8 @@ github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -284,8 +291,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -336,7 +344,10 @@ github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
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.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -480,8 +491,9 @@ github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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=
@@ -497,8 +509,9 @@ github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@@ -560,8 +573,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ=
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -586,7 +600,22 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/contrib/propagators v0.22.0 h1:KGdv58M2//veiYLIhb31mofaI2LgkIPXXAZVeYVyfd8=
go.opentelemetry.io/contrib/propagators v0.22.0/go.mod h1:xGOuXr6lLIF9BXipA4pm6UuOSI0M98U6tsI3khbOiwU=
go.opentelemetry.io/otel v1.0.0-RC2/go.mod h1:w1thVQ7qbAy8MHb0IFj8a5Q2QU0l2ksf8u/CN8m3NOM=
go.opentelemetry.io/otel v1.6.3 h1:FLOfo8f9JzFVFVyU+MSRJc2HdEAXQgm7pIv2uFKRSZE=
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.3/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3 h1:4/UjHWMVVc5VwX/KAtqJOHErKigMCH8NexChMuanb/o=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3/go.mod h1:UJmXdiVVBaZ63umRUTwJuCMAV//GCMvDiQwn703/GoY=
go.opentelemetry.io/otel/sdk v1.6.3 h1:prSHYdwCQOX5DrsEzxowH3nLhoAzEBdZhvrR79scfLs=
go.opentelemetry.io/otel/sdk v1.6.3/go.mod h1:A4iWF7HTXa+GWL/AaqESz28VuSBIcZ+0CV+IzJ5NMiQ=
go.opentelemetry.io/otel/trace v1.0.0-RC2/go.mod h1:JPQ+z6nNw9mqEGT8o3eoPTdnNI+Aj5JcxEsVGREIAy4=
go.opentelemetry.io/otel/trace v1.6.3 h1:IqN4L+5b0mPNjdXIiZ90Ni4Bl5BRkDQywePLWemd9bc=
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.15.0 h1:h0bKrvdrT/9sBwEJ6iWUqT/N/xPcS66bL4u3isneJ6w=
go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
@@ -607,8 +636,9 @@ golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@@ -694,6 +724,7 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
@@ -788,6 +819,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -800,8 +832,9 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1027,8 +1060,10 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.43.0 h1:Eeu7bZtDZ2DpRCsLhUlcrLnvYaMK1Gz86a+hMVvELmM=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
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=
@@ -1042,8 +1077,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/DataDog/dd-trace-go.v1 v1.34.0/go.mod h1:HtrC65fyJ6lWazShCC9rlOeiTSZJ0XtZhkwjZM2WpC4=
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=
@@ -1052,15 +1088,15 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/coreos/go-oidc.v2 v2.1.0 h1:E8PjVFdj/SLDKB0hvb70KTbMbYVHjqztiQdSkIg8E+I=
gopkg.in/coreos/go-oidc.v2 v2.1.0/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
gopkg.in/coreos/go-oidc.v2 v2.2.1 h1:MY5SZClJ7vhjKfr64a4nHAOV/c3WH2gB9BMrR64J1Mc=
gopkg.in/coreos/go-oidc.v2 v2.2.1/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
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/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.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/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.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+133 -35
View File
@@ -11,14 +11,16 @@ import (
"github.com/cloudflare/cloudflared/tlsconfig"
)
const (
defaultConnectTimeout = 30 * time.Second
defaultTLSTimeout = 10 * time.Second
defaultTCPKeepAlive = 30 * time.Second
defaultKeepAliveConnections = 100
defaultKeepAliveTimeout = 90 * time.Second
defaultProxyAddress = "127.0.0.1"
var (
defaultConnectTimeout = config.CustomDuration{Duration: 30 * time.Second}
defaultTLSTimeout = config.CustomDuration{Duration: 10 * time.Second}
defaultTCPKeepAlive = config.CustomDuration{Duration: 30 * time.Second}
defaultKeepAliveTimeout = config.CustomDuration{Duration: 90 * time.Second}
)
const (
defaultProxyAddress = "127.0.0.1"
defaultKeepAliveConnections = 100
SSHServerFlag = "ssh-server"
Socks5Flag = "socks5"
ProxyConnectTimeoutFlag = "proxy-connect-timeout"
@@ -45,18 +47,26 @@ type RemoteConfig struct {
WarpRouting config.WarpRoutingConfig
}
type remoteConfigJSON struct {
GlobalOriginRequest config.OriginRequestConfig `json:"originRequest"`
type RemoteConfigJSON struct {
GlobalOriginRequest *config.OriginRequestConfig `json:"originRequest,omitempty"`
IngressRules []config.UnvalidatedIngressRule `json:"ingress"`
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
}
func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
var rawConfig remoteConfigJSON
var rawConfig RemoteConfigJSON
if err := json.Unmarshal(b, &rawConfig); err != nil {
return err
}
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(rawConfig.GlobalOriginRequest))
// if nil, just assume the default values.
globalOriginRequestConfig := rawConfig.GlobalOriginRequest
if globalOriginRequestConfig == nil {
globalOriginRequestConfig = &config.OriginRequestConfig{}
}
ingress, err := validateIngress(rawConfig.IngressRules, originRequestFromConfig(*globalOriginRequestConfig))
if err != nil {
return err
}
@@ -68,12 +78,12 @@ func (rc *RemoteConfig) UnmarshalJSON(b []byte) error {
}
func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
var connectTimeout time.Duration = defaultConnectTimeout
var tlsTimeout time.Duration = defaultTLSTimeout
var tcpKeepAlive time.Duration = defaultTCPKeepAlive
var connectTimeout config.CustomDuration = defaultConnectTimeout
var tlsTimeout config.CustomDuration = defaultTLSTimeout
var tcpKeepAlive config.CustomDuration = defaultTCPKeepAlive
var noHappyEyeballs bool
var keepAliveConnections int = defaultKeepAliveConnections
var keepAliveTimeout time.Duration = defaultKeepAliveTimeout
var keepAliveTimeout config.CustomDuration = defaultKeepAliveTimeout
var httpHostHeader string
var originServerName string
var caPool string
@@ -84,13 +94,13 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
var proxyPort uint
var proxyType string
if flag := ProxyConnectTimeoutFlag; c.IsSet(flag) {
connectTimeout = c.Duration(flag)
connectTimeout = config.CustomDuration{Duration: c.Duration(flag)}
}
if flag := ProxyTLSTimeoutFlag; c.IsSet(flag) {
tlsTimeout = c.Duration(flag)
tlsTimeout = config.CustomDuration{Duration: c.Duration(flag)}
}
if flag := ProxyTCPKeepAliveFlag; c.IsSet(flag) {
tcpKeepAlive = c.Duration(flag)
tcpKeepAlive = config.CustomDuration{Duration: c.Duration(flag)}
}
if flag := ProxyNoHappyEyeballsFlag; c.IsSet(flag) {
noHappyEyeballs = c.Bool(flag)
@@ -99,7 +109,7 @@ func originRequestFromSingeRule(c *cli.Context) OriginRequestConfig {
keepAliveConnections = c.Int(flag)
}
if flag := ProxyKeepAliveTimeoutFlag; c.IsSet(flag) {
keepAliveTimeout = c.Duration(flag)
keepAliveTimeout = config.CustomDuration{Duration: c.Duration(flag)}
}
if flag := HTTPHostHeaderFlag; c.IsSet(flag) {
httpHostHeader = c.String(flag)
@@ -218,41 +228,41 @@ func originRequestFromConfig(c config.OriginRequestConfig) OriginRequestConfig {
// Note: To specify a time.Duration in go-yaml, use e.g. "3s" or "24h".
type OriginRequestConfig struct {
// HTTP proxy timeout for establishing a new connection
ConnectTimeout time.Duration `yaml:"connectTimeout"`
ConnectTimeout config.CustomDuration `yaml:"connectTimeout" json:"connectTimeout"`
// HTTP proxy timeout for completing a TLS handshake
TLSTimeout time.Duration `yaml:"tlsTimeout"`
TLSTimeout config.CustomDuration `yaml:"tlsTimeout" json:"tlsTimeout"`
// HTTP proxy TCP keepalive duration
TCPKeepAlive time.Duration `yaml:"tcpKeepAlive"`
TCPKeepAlive config.CustomDuration `yaml:"tcpKeepAlive" json:"tcpKeepAlive"`
// HTTP proxy should disable "happy eyeballs" for IPv4/v6 fallback
NoHappyEyeballs bool `yaml:"noHappyEyeballs"`
NoHappyEyeballs bool `yaml:"noHappyEyeballs" json:"noHappyEyeballs"`
// HTTP proxy timeout for closing an idle connection
KeepAliveTimeout time.Duration `yaml:"keepAliveTimeout"`
KeepAliveTimeout config.CustomDuration `yaml:"keepAliveTimeout" json:"keepAliveTimeout"`
// HTTP proxy maximum keepalive connection pool size
KeepAliveConnections int `yaml:"keepAliveConnections"`
KeepAliveConnections int `yaml:"keepAliveConnections" json:"keepAliveConnections"`
// Sets the HTTP Host header for the local webserver.
HTTPHostHeader string `yaml:"httpHostHeader"`
HTTPHostHeader string `yaml:"httpHostHeader" json:"httpHostHeader"`
// Hostname on the origin server certificate.
OriginServerName string `yaml:"originServerName"`
OriginServerName string `yaml:"originServerName" json:"originServerName"`
// Path to the CA for the certificate of your origin.
// This option should be used only if your certificate is not signed by Cloudflare.
CAPool string `yaml:"caPool"`
CAPool string `yaml:"caPool" json:"caPool"`
// Disables TLS verification of the certificate presented by your origin.
// Will allow any certificate from the origin to be accepted.
// Note: The connection from your machine to Cloudflare's Edge is still encrypted.
NoTLSVerify bool `yaml:"noTLSVerify"`
NoTLSVerify bool `yaml:"noTLSVerify" json:"noTLSVerify"`
// Disables chunked transfer encoding.
// Useful if you are running a WSGI server.
DisableChunkedEncoding bool `yaml:"disableChunkedEncoding"`
DisableChunkedEncoding bool `yaml:"disableChunkedEncoding" json:"disableChunkedEncoding"`
// Runs as jump host
BastionMode bool `yaml:"bastionMode"`
BastionMode bool `yaml:"bastionMode" json:"bastionMode"`
// Listen address for the proxy.
ProxyAddress string `yaml:"proxyAddress"`
ProxyAddress string `yaml:"proxyAddress" json:"proxyAddress"`
// Listen port for the proxy.
ProxyPort uint `yaml:"proxyPort"`
ProxyPort uint `yaml:"proxyPort" json:"proxyPort"`
// What sort of proxy should be started
ProxyType string `yaml:"proxyType"`
ProxyType string `yaml:"proxyType" json:"proxyType"`
// IP rules for the proxy service
IPRules []ipaccess.Rule `yaml:"ipRules"`
IPRules []ipaccess.Rule `yaml:"ipRules" json:"ipRules"`
}
func (defaults *OriginRequestConfig) setConnectTimeout(overrides config.OriginRequestConfig) {
@@ -385,3 +395,91 @@ func setConfig(defaults OriginRequestConfig, overrides config.OriginRequestConfi
cfg.setIPRules(overrides)
return cfg
}
func ConvertToRawOriginConfig(c OriginRequestConfig) config.OriginRequestConfig {
var connectTimeout *config.CustomDuration
var tlsTimeout *config.CustomDuration
var tcpKeepAlive *config.CustomDuration
var keepAliveConnections *int
var keepAliveTimeout *config.CustomDuration
var proxyAddress *string
if c.ConnectTimeout != defaultConnectTimeout {
connectTimeout = &c.ConnectTimeout
}
if c.TLSTimeout != defaultTLSTimeout {
tlsTimeout = &c.TLSTimeout
}
if c.TCPKeepAlive != defaultTCPKeepAlive {
tcpKeepAlive = &c.TCPKeepAlive
}
if c.KeepAliveConnections != defaultKeepAliveConnections {
keepAliveConnections = &c.KeepAliveConnections
}
if c.KeepAliveTimeout != defaultKeepAliveTimeout {
keepAliveTimeout = &c.KeepAliveTimeout
}
if c.ProxyAddress != defaultProxyAddress {
proxyAddress = &c.ProxyAddress
}
return config.OriginRequestConfig{
ConnectTimeout: connectTimeout,
TLSTimeout: tlsTimeout,
TCPKeepAlive: tcpKeepAlive,
NoHappyEyeballs: defaultBoolToNil(c.NoHappyEyeballs),
KeepAliveConnections: keepAliveConnections,
KeepAliveTimeout: keepAliveTimeout,
HTTPHostHeader: emptyStringToNil(c.HTTPHostHeader),
OriginServerName: emptyStringToNil(c.OriginServerName),
CAPool: emptyStringToNil(c.CAPool),
NoTLSVerify: defaultBoolToNil(c.NoTLSVerify),
DisableChunkedEncoding: defaultBoolToNil(c.DisableChunkedEncoding),
BastionMode: defaultBoolToNil(c.BastionMode),
ProxyAddress: proxyAddress,
ProxyPort: zeroUIntToNil(c.ProxyPort),
ProxyType: emptyStringToNil(c.ProxyType),
IPRules: convertToRawIPRules(c.IPRules),
}
}
func convertToRawIPRules(ipRules []ipaccess.Rule) []config.IngressIPRule {
result := make([]config.IngressIPRule, 0)
for _, r := range ipRules {
cidr := r.StringCIDR()
newRule := config.IngressIPRule{
Prefix: &cidr,
Ports: r.Ports(),
Allow: r.RulePolicy(),
}
result = append(result, newRule)
}
return result
}
func defaultBoolToNil(b bool) *bool {
if b == false {
return nil
}
return &b
}
func emptyStringToNil(s string) *string {
if s == "" {
return nil
}
return &s
}
func zeroUIntToNil(v uint) *uint {
if v == 0 {
return nil
}
return &v
}
+25 -25
View File
@@ -65,7 +65,7 @@ func TestUnmarshalRemoteConfigOverridesGlobal(t *testing.T) {
err := json.Unmarshal(rawConfig, &remoteConfig)
require.NoError(t, err)
require.True(t, remoteConfig.Ingress.Rules[0].Config.NoTLSVerify)
require.True(t, remoteConfig.Ingress.defaults.NoHappyEyeballs)
require.True(t, remoteConfig.Ingress.Defaults.NoHappyEyeballs)
}
func TestOriginRequestConfigOverrides(t *testing.T) {
@@ -74,11 +74,11 @@ func TestOriginRequestConfigOverrides(t *testing.T) {
// root-level configuration.
actual0 := ing.Rules[0].Config
expected0 := OriginRequestConfig{
ConnectTimeout: 1 * time.Minute,
TLSTimeout: 1 * time.Second,
TCPKeepAlive: 1 * time.Second,
ConnectTimeout: config.CustomDuration{Duration: 1 * time.Minute},
TLSTimeout: config.CustomDuration{Duration: 1 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 1 * time.Second},
NoHappyEyeballs: true,
KeepAliveTimeout: 1 * time.Second,
KeepAliveTimeout: config.CustomDuration{Duration: 1 * time.Second},
KeepAliveConnections: 1,
HTTPHostHeader: "abc",
OriginServerName: "a1",
@@ -99,11 +99,11 @@ func TestOriginRequestConfigOverrides(t *testing.T) {
// Rule 1 overrode all the root-level config.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
TCPKeepAlive: 2 * time.Second,
ConnectTimeout: config.CustomDuration{Duration: 2 * time.Minute},
TLSTimeout: config.CustomDuration{Duration: 2 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 2 * time.Second},
NoHappyEyeballs: false,
KeepAliveTimeout: 2 * time.Second,
KeepAliveTimeout: config.CustomDuration{Duration: 2 * time.Second},
KeepAliveConnections: 2,
HTTPHostHeader: "def",
OriginServerName: "b2",
@@ -191,12 +191,12 @@ ingress:
rawConfig := []byte(`
{
"originRequest": {
"connectTimeout": 60000000000,
"tlsTimeout": 1000000000,
"connectTimeout": 60,
"tlsTimeout": 1,
"noHappyEyeballs": true,
"tcpKeepAlive": 1000000000,
"tcpKeepAlive": 1,
"keepAliveConnections": 1,
"keepAliveTimeout": 1000000000,
"keepAliveTimeout": 1,
"httpHostHeader": "abc",
"originServerName": "a1",
"caPool": "/tmp/path0",
@@ -228,12 +228,12 @@ ingress:
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 120000000000,
"tlsTimeout": 2000000000,
"connectTimeout": 120,
"tlsTimeout": 2,
"noHappyEyeballs": false,
"tcpKeepAlive": 2000000000,
"tcpKeepAlive": 2,
"keepAliveConnections": 2,
"keepAliveTimeout": 2000000000,
"keepAliveTimeout": 2,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
@@ -286,11 +286,11 @@ func TestOriginRequestConfigDefaults(t *testing.T) {
// Rule 1 overrode all defaults.
actual1 := ing.Rules[1].Config
expected1 := OriginRequestConfig{
ConnectTimeout: 2 * time.Minute,
TLSTimeout: 2 * time.Second,
TCPKeepAlive: 2 * time.Second,
ConnectTimeout: config.CustomDuration{Duration: 2 * time.Minute},
TLSTimeout: config.CustomDuration{Duration: 2 * time.Second},
TCPKeepAlive: config.CustomDuration{Duration: 2 * time.Second},
NoHappyEyeballs: false,
KeepAliveTimeout: 2 * time.Second,
KeepAliveTimeout: config.CustomDuration{Duration: 2 * time.Second},
KeepAliveConnections: 2,
HTTPHostHeader: "def",
OriginServerName: "b2",
@@ -360,12 +360,12 @@ ingress:
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 120000000000,
"tlsTimeout": 2000000000,
"connectTimeout": 120,
"tlsTimeout": 2,
"noHappyEyeballs": false,
"tcpKeepAlive": 2000000000,
"tcpKeepAlive": 2,
"keepAliveConnections": 2,
"keepAliveTimeout": 2000000000,
"keepAliveTimeout": 2,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
+14 -10
View File
@@ -64,8 +64,8 @@ func matchHost(ruleHost, reqHost string) bool {
// Ingress maps eyeball requests to origins.
type Ingress struct {
Rules []Rule
defaults OriginRequestConfig
Rules []Rule `json:"ingress"`
Defaults OriginRequestConfig `json:"originRequest"`
}
// NewSingleOrigin constructs an Ingress set with only one rule, constructed from
@@ -86,7 +86,7 @@ func NewSingleOrigin(c *cli.Context, allowURLFromArgs bool) (Ingress, error) {
Config: setConfig(defaults, config.OriginRequestConfig{}),
},
},
defaults: defaults,
Defaults: defaults,
}
return ing, err
}
@@ -126,7 +126,7 @@ func parseSingleOriginService(c *cli.Context, allowURLFromArgs bool) (OriginServ
if err != nil {
return nil, errors.Wrap(err, "Error validating --unix-socket")
}
return &unixSocketPath{path: path}, nil
return &unixSocketPath{path: path, scheme: "http"}, nil
}
u, err := url.Parse("http://localhost:8080")
return &httpService{url: u}, err
@@ -169,7 +169,10 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
if prefix := "unix:"; strings.HasPrefix(r.Service, prefix) {
// No validation necessary for unix socket filepath services
path := strings.TrimPrefix(r.Service, prefix)
service = &unixSocketPath{path: path}
service = &unixSocketPath{path: path, scheme: "http"}
} else if prefix := "unix+tls:"; strings.HasPrefix(r.Service, prefix) {
path := strings.TrimPrefix(r.Service, prefix)
service = &unixSocketPath{path: path, scheme: "https"}
} else if prefix := "http_status:"; strings.HasPrefix(r.Service, prefix) {
status, err := strconv.Atoi(strings.TrimPrefix(r.Service, prefix))
if err != nil {
@@ -177,7 +180,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
}
srv := newStatusCode(status)
service = &srv
} else if r.Service == "hello_world" || r.Service == "hello-world" || r.Service == "helloworld" {
} else if r.Service == HelloWorldService || r.Service == "hello-world" || r.Service == "helloworld" {
service = new(helloWorld)
} else if r.Service == ServiceSocksProxy {
rules := make([]ipaccess.Rule, len(r.OriginRequest.IPRules))
@@ -227,23 +230,24 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
return Ingress{}, err
}
var pathRegex *regexp.Regexp
var pathRegexp *Regexp
if r.Path != "" {
var err error
pathRegex, err = regexp.Compile(r.Path)
regex, err := regexp.Compile(r.Path)
if err != nil {
return Ingress{}, errors.Wrapf(err, "Rule #%d has an invalid regex", i+1)
}
pathRegexp = &Regexp{Regexp: regex}
}
rules[i] = Rule{
Hostname: r.Hostname,
Service: service,
Path: pathRegex,
Path: pathRegexp,
Config: cfg,
}
}
return Ingress{Rules: rules, defaults: defaults}, nil
return Ingress{Rules: rules, Defaults: defaults}, nil
}
func validateHostname(r config.UnvalidatedIngressRule, ruleIndex, totalRules int) error {
+21 -8
View File
@@ -26,8 +26,21 @@ ingress:
`
ing, err := ParseIngress(MustReadIngress(rawYAML))
require.NoError(t, err)
_, ok := ing.Rules[0].Service.(*unixSocketPath)
s, ok := ing.Rules[0].Service.(*unixSocketPath)
require.True(t, ok)
require.Equal(t, "http", s.scheme)
}
func TestParseUnixSocketTLS(t *testing.T) {
rawYAML := `
ingress:
- service: unix+tls:/tmp/echo.sock
`
ing, err := ParseIngress(MustReadIngress(rawYAML))
require.NoError(t, err)
s, ok := ing.Rules[0].Service.(*unixSocketPath)
require.True(t, ok)
require.Equal(t, "https", s.scheme)
}
func Test_parseIngress(t *testing.T) {
@@ -469,12 +482,12 @@ func TestSingleOriginSetsConfig(t *testing.T) {
ingress, err := NewSingleOrigin(cliCtx, allowURLFromArgs)
require.NoError(t, err)
assert.Equal(t, time.Second, ingress.Rules[0].Config.ConnectTimeout)
assert.Equal(t, time.Second, ingress.Rules[0].Config.TLSTimeout)
assert.Equal(t, time.Second, ingress.Rules[0].Config.TCPKeepAlive)
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.ConnectTimeout)
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TLSTimeout)
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.TCPKeepAlive)
assert.True(t, ingress.Rules[0].Config.NoHappyEyeballs)
assert.Equal(t, 10, ingress.Rules[0].Config.KeepAliveConnections)
assert.Equal(t, time.Second, ingress.Rules[0].Config.KeepAliveTimeout)
assert.Equal(t, config.CustomDuration{Duration: time.Second}, ingress.Rules[0].Config.KeepAliveTimeout)
assert.Equal(t, "example.com:8080", ingress.Rules[0].Config.HTTPHostHeader)
assert.Equal(t, "example.com", ingress.Rules[0].Config.OriginServerName)
assert.Equal(t, "/etc/certs/ca.pem", ingress.Rules[0].Config.CAPool)
@@ -495,7 +508,7 @@ func TestFindMatchingRule(t *testing.T) {
},
{
Hostname: "tunnel-b.example.com",
Path: mustParsePath(t, "/health"),
Path: MustParsePath(t, "/health"),
},
{
Hostname: "*",
@@ -578,10 +591,10 @@ func TestIsHTTPService(t *testing.T) {
}
}
func mustParsePath(t *testing.T, path string) *regexp.Regexp {
func MustParsePath(t *testing.T, path string) *Regexp {
regexp, err := regexp.Compile(path)
assert.NoError(t, err)
return regexp
return &Regexp{Regexp: regexp}
}
func MustParseURL(t *testing.T, rawURL string) *url.URL {
+9 -2
View File
@@ -1,6 +1,7 @@
package ingress
import (
"fmt"
"net"
"net/http"
@@ -23,7 +24,7 @@ type StreamBasedOriginProxy interface {
}
func (o *unixSocketPath) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = "http"
req.URL.Scheme = o.scheme
return o.transport.RoundTrip(req)
}
@@ -49,7 +50,13 @@ func (o *httpService) RoundTrip(req *http.Request) (*http.Response, error) {
}
func (o *statusCode) RoundTrip(_ *http.Request) (*http.Response, error) {
return o.resp, nil
resp := &http.Response{
StatusCode: o.code,
Status: fmt.Sprintf("%d %s", o.code, http.StatusText(o.code)),
Body: new(NopReadCloser),
}
return resp, nil
}
func (o *rawTCPService) EstablishConnection(dest string) (OriginConnection, error) {
+59 -17
View File
@@ -3,6 +3,7 @@ package ingress
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
@@ -20,7 +21,8 @@ import (
)
const (
HelloWorldService = "Hello World test origin"
HelloWorldService = "hello_world"
HttpStatusService = "http_status"
)
// OriginService is something a tunnel can proxy traffic to.
@@ -31,16 +33,22 @@ type OriginService interface {
// starting the origin service.
// Implementor of services managed by cloudflared should terminate the service if shutdownC is closed
start(log *zerolog.Logger, shutdownC <-chan struct{}, cfg OriginRequestConfig) error
MarshalJSON() ([]byte, error)
}
// unixSocketPath is an OriginService representing a unix socket (which accepts HTTP)
// unixSocketPath is an OriginService representing a unix socket (which accepts HTTP or HTTPS)
type unixSocketPath struct {
path string
scheme string
transport *http.Transport
}
func (o *unixSocketPath) String() string {
return "unix socket: " + o.path
scheme := ""
if o.scheme == "https" {
scheme = "+tls"
}
return fmt.Sprintf("unix%s:%s", scheme, o.path)
}
func (o *unixSocketPath) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
@@ -52,6 +60,10 @@ func (o *unixSocketPath) start(log *zerolog.Logger, _ <-chan struct{}, cfg Origi
return nil
}
func (o unixSocketPath) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
type httpService struct {
url *url.URL
hostHeader string
@@ -72,6 +84,10 @@ func (o *httpService) String() string {
return o.url.String()
}
func (o httpService) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
// rawTCPService dials TCP to the destination specified by the client
// It's used by warp routing
type rawTCPService struct {
@@ -86,9 +102,14 @@ func (o *rawTCPService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Origin
return nil
}
func (o rawTCPService) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
// tcpOverWSService models TCP origins serving eyeballs connecting over websocket, such as
// cloudflared access commands.
type tcpOverWSService struct {
scheme string
dest string
isBastion bool
streamHandler streamHandlerFunc
@@ -110,7 +131,8 @@ func newTCPOverWSService(url *url.URL) *tcpOverWSService {
addPortIfMissing(url, 7864) // just a random port since there isn't a default in this case
}
return &tcpOverWSService{
dest: url.Host,
scheme: url.Scheme,
dest: url.Host,
}
}
@@ -140,7 +162,12 @@ func (o *tcpOverWSService) String() string {
if o.isBastion {
return ServiceBastion
}
return o.dest
if o.scheme != "" {
return fmt.Sprintf("%s://%s", o.scheme, o.dest)
} else {
return o.dest
}
}
func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
@@ -152,6 +179,10 @@ func (o *tcpOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg Ori
return nil
}
func (o tcpOverWSService) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
func (o *socksProxyOverWSService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
return nil
}
@@ -160,6 +191,10 @@ func (o *socksProxyOverWSService) String() string {
return ServiceSocksProxy
}
func (o socksProxyOverWSService) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
// HelloWorld is an OriginService for the built-in Hello World server.
// Users only use this for testing and experimenting with cloudflared.
type helloWorld struct {
@@ -196,23 +231,22 @@ func (o *helloWorld) start(
return nil
}
func (o helloWorld) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
// statusCode is an OriginService that just responds with a given HTTP status.
// Typical use-case is "user wants the catch-all rule to just respond 404".
type statusCode struct {
resp *http.Response
code int
}
func newStatusCode(status int) statusCode {
resp := &http.Response{
StatusCode: status,
Status: fmt.Sprintf("%d %s", status, http.StatusText(status)),
Body: new(NopReadCloser),
}
return statusCode{resp: resp}
return statusCode{code: status}
}
func (o *statusCode) String() string {
return fmt.Sprintf("HTTP %d", o.resp.StatusCode)
return fmt.Sprintf("http_status:%d", o.code)
}
func (o *statusCode) start(
@@ -223,6 +257,10 @@ func (o *statusCode) start(
return nil
}
func (o statusCode) MarshalJSON() ([]byte, error) {
return json.Marshal(o.String())
}
type NopReadCloser struct{}
// Read always returns EOF to signal end of input
@@ -244,8 +282,8 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
Proxy: http.ProxyFromEnvironment,
MaxIdleConns: cfg.KeepAliveConnections,
MaxIdleConnsPerHost: cfg.KeepAliveConnections,
IdleConnTimeout: cfg.KeepAliveTimeout,
TLSHandshakeTimeout: cfg.TLSTimeout,
IdleConnTimeout: cfg.KeepAliveTimeout.Duration,
TLSHandshakeTimeout: cfg.TLSTimeout.Duration,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: cfg.NoTLSVerify},
}
@@ -254,8 +292,8 @@ func newHTTPTransport(service OriginService, cfg OriginRequestConfig, log *zerol
}
dialer := &net.Dialer{
Timeout: cfg.ConnectTimeout,
KeepAlive: cfg.TCPKeepAlive,
Timeout: cfg.ConnectTimeout.Duration,
KeepAlive: cfg.TCPKeepAlive.Duration,
}
if cfg.NoHappyEyeballs {
dialer.FallbackDelay = -1 // As of Golang 1.12, a negative delay disables "happy eyeballs"
@@ -295,3 +333,7 @@ func (mos MockOriginHTTPService) String() string {
func (mos MockOriginHTTPService) start(log *zerolog.Logger, _ <-chan struct{}, cfg OriginRequestConfig) error {
return nil
}
func (mos MockOriginHTTPService) MarshalJSON() ([]byte, error) {
return json.Marshal(mos.String())
}
+20 -7
View File
@@ -1,6 +1,7 @@
package ingress
import (
"encoding/json"
"regexp"
"strings"
)
@@ -9,18 +10,18 @@ import (
// service running on the given URL.
type Rule struct {
// Requests for this hostname will be proxied to this rule's service.
Hostname string
Hostname string `json:"hostname"`
// Path is an optional regex that can specify path-driven ingress rules.
Path *regexp.Regexp
Path *Regexp `json:"path"`
// A (probably local) address. Requests for a hostname which matches this
// rule's hostname pattern will be proxied to the service running on this
// address.
Service OriginService
Service OriginService `json:"service"`
// Configure the request cloudflared sends to this specific origin.
Config OriginRequestConfig
Config OriginRequestConfig `json:"originRequest"`
}
// MultiLineString is for outputting rules in a human-friendly way when Cloudflared
@@ -32,9 +33,9 @@ func (r Rule) MultiLineString() string {
out.WriteString(r.Hostname)
out.WriteRune('\n')
}
if r.Path != nil {
if r.Path != nil && r.Path.Regexp != nil {
out.WriteString("\tpath: ")
out.WriteString(r.Path.String())
out.WriteString(r.Path.Regexp.String())
out.WriteRune('\n')
}
out.WriteString("\tservice: ")
@@ -45,6 +46,18 @@ func (r Rule) MultiLineString() string {
// Matches checks if the rule matches a given hostname/path combination.
func (r *Rule) Matches(hostname, path string) bool {
hostMatch := r.Hostname == "" || r.Hostname == "*" || matchHost(r.Hostname, hostname)
pathMatch := r.Path == nil || r.Path.MatchString(path)
pathMatch := r.Path == nil || r.Path.Regexp == nil || r.Path.Regexp.MatchString(path)
return hostMatch && pathMatch
}
// Regexp adds unmarshalling from json for regexp.Regexp
type Regexp struct {
*regexp.Regexp
}
func (r *Regexp) MarshalJSON() ([]byte, error) {
if r.Regexp == nil {
return json.Marshal(nil)
}
return json.Marshal(r.Regexp.String())
}
+76 -2
View File
@@ -1,6 +1,7 @@
package ingress
import (
"encoding/json"
"io"
"net/http/httptest"
"net/url"
@@ -8,12 +9,14 @@ import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/config"
)
func Test_rule_matches(t *testing.T) {
type fields struct {
Hostname string
Path *regexp.Regexp
Path *Regexp
Service OriginService
}
type args struct {
@@ -99,13 +102,35 @@ func Test_rule_matches(t *testing.T) {
name: "Hostname and path",
fields: fields{
Hostname: "*.example.com",
Path: regexp.MustCompile("/static/.*\\.html"),
Path: &Regexp{Regexp: regexp.MustCompile("/static/.*\\.html")},
},
args: args{
requestURL: MustParseURL(t, "https://www.example.com/static/index.html"),
},
want: true,
},
{
name: "Hostname and empty Regex",
fields: fields{
Hostname: "example.com",
Path: &Regexp{},
},
args: args{
requestURL: MustParseURL(t, "https://example.com/"),
},
want: true,
},
{
name: "Hostname and nil path",
fields: fields{
Hostname: "example.com",
Path: nil,
},
args: args{
requestURL: MustParseURL(t, "https://example.com/"),
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -144,3 +169,52 @@ func TestStaticHTTPStatus(t *testing.T) {
sendReq()
sendReq()
}
func TestMarshalJSON(t *testing.T) {
localhost8000 := MustParseURL(t, "https://localhost:8000")
defaultConfig := setConfig(originRequestFromConfig(config.OriginRequestConfig{}), config.OriginRequestConfig{})
tests := []struct {
name string
path *Regexp
expected string
want bool
}{
{
name: "Nil",
path: nil,
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
want: true,
},
{
name: "Nil regex",
path: &Regexp{Regexp: nil},
expected: `{"hostname":"example.com","path":null,"service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
want: true,
},
{
name: "Empty",
path: &Regexp{Regexp: regexp.MustCompile("")},
expected: `{"hostname":"example.com","path":"","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
want: true,
},
{
name: "Basic",
path: &Regexp{Regexp: regexp.MustCompile("/echo")},
expected: `{"hostname":"example.com","path":"/echo","service":"https://localhost:8000","originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null}}`,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := Rule{
Hostname: "example.com",
Service: &httpService{url: localhost8000},
Path: tt.path,
Config: defaultConfig,
}
bytes, err := json.Marshal(r)
require.NoError(t, err)
require.Equal(t, tt.expected, string(bytes))
})
}
}
+12
View File
@@ -99,3 +99,15 @@ func (ipr *Rule) PortsString() string {
}
return "all"
}
func (ipr *Rule) Ports() []int {
return ipr.ports
}
func (ipr *Rule) RulePolicy() bool {
return ipr.allow
}
func (ipr *Rule) StringCIDR() string {
return ipr.ipNet.String()
}
+24 -2
View File
@@ -22,7 +22,16 @@ const (
startupTime = time.Millisecond * 500
)
func newMetricsHandler(readyServer *ReadyServer, quickTunnelHostname string) *mux.Router {
type orchestrator interface {
GetVersionedConfigJSON() ([]byte, error)
}
func newMetricsHandler(
readyServer *ReadyServer,
quickTunnelHostname string,
orchestrator orchestrator,
log *zerolog.Logger,
) *mux.Router {
router := mux.NewRouter()
router.PathPrefix("/debug/").Handler(http.DefaultServeMux)
@@ -36,6 +45,18 @@ func newMetricsHandler(readyServer *ReadyServer, quickTunnelHostname string) *mu
router.HandleFunc("/quicktunnel", func(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, `{"hostname":"%s"}`, quickTunnelHostname)
})
if orchestrator != nil {
router.HandleFunc("/config", func(w http.ResponseWriter, r *http.Request) {
json, err := orchestrator.GetVersionedConfigJSON()
if err != nil {
w.WriteHeader(500)
_, _ = fmt.Fprintf(w, "ERR: %v", err)
log.Err(err).Msg("Failed to serve config")
return
}
_, _ = w.Write(json)
})
}
return router
}
@@ -45,6 +66,7 @@ func ServeMetrics(
shutdownC <-chan struct{},
readyServer *ReadyServer,
quickTunnelHostname string,
orchestrator orchestrator,
log *zerolog.Logger,
) (err error) {
var wg sync.WaitGroup
@@ -52,7 +74,7 @@ func ServeMetrics(
trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true }
// TODO: parameterize ReadTimeout and WriteTimeout. The maximum time we can
// profile CPU usage depends on WriteTimeout
h := newMetricsHandler(readyServer, quickTunnelHostname)
h := newMetricsHandler(readyServer, quickTunnelHostname, orchestrator, log)
server := &http.Server{
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
+52 -1
View File
@@ -1,15 +1,66 @@
package orchestration
import (
"encoding/json"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/ingress"
)
type newConfig struct {
type newRemoteConfig struct {
ingress.RemoteConfig
// Add more fields when we support other settings in tunnel orchestration
}
type newLocalConfig struct {
RemoteConfig ingress.RemoteConfig
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
}
// Config is the original config as read and parsed by cloudflared.
type Config struct {
Ingress *ingress.Ingress
WarpRoutingEnabled bool
// Extra settings used to configure this instance but that are not eligible for remotely management
// ie. (--protocol, --loglevel, ...)
ConfigurationFlags map[string]string
}
func (rc *newLocalConfig) MarshalJSON() ([]byte, error) {
var r = struct {
ConfigurationFlags map[string]string `json:"__configuration_flags,omitempty"`
ingress.RemoteConfigJSON
}{
ConfigurationFlags: rc.ConfigurationFlags,
RemoteConfigJSON: ingress.RemoteConfigJSON{
// UI doesn't support top level configs, so we reconcile to individual ingress configs.
GlobalOriginRequest: nil,
IngressRules: convertToUnvalidatedIngressRules(rc.RemoteConfig.Ingress),
WarpRouting: rc.RemoteConfig.WarpRouting,
},
}
return json.Marshal(r)
}
func convertToUnvalidatedIngressRules(i ingress.Ingress) []config.UnvalidatedIngressRule {
result := make([]config.UnvalidatedIngressRule, 0)
for _, rule := range i.Rules {
var path string
if rule.Path != nil {
path = rule.Path.String()
}
newRule := config.UnvalidatedIngressRule{
Hostname: rule.Hostname,
Path: path,
Service: rule.Service.String(),
OriginRequest: ingress.ConvertToRawOriginConfig(rule.Config),
}
result = append(result, newRule)
}
return result
}
+82
View File
@@ -0,0 +1,82 @@
package orchestration
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/ingress"
)
// TestNewLocalConfig_MarshalJSON tests that we are able to converte a compiled and validated config back
// into an "unvalidated" format which is compatible with Remote Managed configurations.
func TestNewLocalConfig_MarshalJSON(t *testing.T) {
rawConfig := []byte(`
{
"originRequest": {
"connectTimeout": 160,
"httpHostHeader": "default"
},
"ingress": [
{
"hostname": "tun.example.com",
"service": "https://localhost:8000"
},
{
"hostname": "*",
"service": "https://localhost:8001",
"originRequest": {
"connectTimeout": 121,
"tlsTimeout": 2,
"noHappyEyeballs": false,
"tcpKeepAlive": 2,
"keepAliveConnections": 2,
"keepAliveTimeout": 2,
"httpHostHeader": "def",
"originServerName": "b2",
"caPool": "/tmp/path1",
"noTLSVerify": false,
"disableChunkedEncoding": false,
"bastionMode": false,
"proxyAddress": "interface",
"proxyPort": 200,
"proxyType": "",
"ipRules": [
{
"prefix": "10.0.0.0/16",
"ports": [3000, 3030],
"allow": false
},
{
"prefix": "192.16.0.0/24",
"ports": [5000, 5050],
"allow": true
}
]
}
}
]
}
`)
var expectedConfig ingress.RemoteConfig
err := json.Unmarshal(rawConfig, &expectedConfig)
require.NoError(t, err)
c := &newLocalConfig{
RemoteConfig: expectedConfig,
ConfigurationFlags: nil,
}
jsonSerde, err := json.Marshal(c)
require.NoError(t, err)
var config ingress.RemoteConfig
err = json.Unmarshal(jsonSerde, &config)
require.NoError(t, err)
require.Equal(t, config.WarpRouting.Enabled, false)
require.Equal(t, config.Ingress.Rules, expectedConfig.Ingress.Rules)
}
+25
View File
@@ -0,0 +1,25 @@
package orchestration
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
MetricsNamespace = "cloudflared"
MetricsSubsystem = "orchestration"
)
var (
configVersion = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystem,
Name: "config_version",
Help: "Configuration Version",
},
)
)
func init() {
prometheus.MustRegister(configVersion)
}
+47 -3
View File
@@ -10,6 +10,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/proxy"
@@ -53,7 +54,7 @@ func NewOrchestrator(ctx context.Context, config *Config, tags []tunnelpogs.Tag,
return o, nil
}
// Update creates a new proxy with the new ingress rules
// UpdateConfig creates a new proxy with the new ingress rules
func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.UpdateConfigurationResponse {
o.lock.Lock()
defer o.lock.Unlock()
@@ -62,12 +63,12 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
o.log.Debug().
Int32("current_version", o.currentVersion).
Int32("received_version", version).
Msg("Current version is equal or newer than receivied version")
Msg("Current version is equal or newer than received version")
return &tunnelpogs.UpdateConfigurationResponse{
LastAppliedVersion: o.currentVersion,
}
}
var newConf newConfig
var newConf newRemoteConfig
if err := json.Unmarshal(config, &newConf); err != nil {
o.log.Err(err).
Int32("version", version).
@@ -95,6 +96,7 @@ func (o *Orchestrator) UpdateConfig(version int32, config []byte) *tunnelpogs.Up
Int32("version", version).
Str("config", string(config)).
Msg("Updated to new configuration")
configVersion.Set(float64(version))
return &tunnelpogs.UpdateConfigurationResponse{
LastAppliedVersion: o.currentVersion,
}
@@ -129,6 +131,48 @@ func (o *Orchestrator) updateIngress(ingressRules ingress.Ingress, warpRoutingEn
return nil
}
// GetConfigJSON returns the current json serialization of the config as the edge understands it
func (o *Orchestrator) GetConfigJSON() ([]byte, error) {
o.lock.RLock()
defer o.lock.RUnlock()
c := &newLocalConfig{
RemoteConfig: ingress.RemoteConfig{
Ingress: *o.config.Ingress,
WarpRouting: config.WarpRoutingConfig{Enabled: o.config.WarpRoutingEnabled},
},
ConfigurationFlags: o.config.ConfigurationFlags,
}
return json.Marshal(c)
}
// GetVersionedConfigJSON returns the current version and configuration as JSON
func (o *Orchestrator) GetVersionedConfigJSON() ([]byte, error) {
o.lock.RLock()
defer o.lock.RUnlock()
var currentConfiguration = struct {
Version int32 `json:"version"`
Config struct {
Ingress []ingress.Rule `json:"ingress"`
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
OriginRequest ingress.OriginRequestConfig `json:"originRequest"`
} `json:"config"`
}{
Version: o.currentVersion,
Config: struct {
Ingress []ingress.Rule `json:"ingress"`
WarpRouting config.WarpRoutingConfig `json:"warp-routing"`
OriginRequest ingress.OriginRequestConfig `json:"originRequest"`
}{
Ingress: o.config.Ingress.Rules,
WarpRouting: config.WarpRoutingConfig{Enabled: o.config.WarpRoutingEnabled},
OriginRequest: o.config.Ingress.Defaults,
},
}
return json.Marshal(currentConfiguration)
}
// GetOriginProxy returns an interface to proxy to origin. It satisfies connection.ConfigManager interface
func (o *Orchestrator) GetOriginProxy() (connection.OriginProxy, error) {
val := o.proxy.Load()
+73 -35
View File
@@ -2,6 +2,7 @@ package orchestration
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
@@ -17,9 +18,11 @@ import (
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/proxy"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -58,7 +61,7 @@ func TestUpdateConfiguration(t *testing.T) {
{
"unknown_field": "not_deserialized",
"originRequest": {
"connectTimeout": 90000000000,
"connectTimeout": 90,
"noHappyEyeballs": true
},
"ingress": [
@@ -68,7 +71,7 @@ func TestUpdateConfiguration(t *testing.T) {
"service": "http://192.16.19.1:443",
"originRequest": {
"noTLSVerify": true,
"connectTimeout": 10000000000
"connectTimeout": 10
}
},
{
@@ -76,7 +79,7 @@ func TestUpdateConfiguration(t *testing.T) {
"service": "http://172.32.20.6:80",
"originRequest": {
"noTLSVerify": true,
"connectTimeout": 30000000000
"connectTimeout": 30
}
},
{
@@ -99,7 +102,7 @@ func TestUpdateConfiguration(t *testing.T) {
require.Equal(t, "http://192.16.19.1:443", configV2.Ingress.Rules[0].Service.String())
require.Len(t, configV2.Ingress.Rules, 3)
// originRequest of this ingress rule overrides global default
require.Equal(t, time.Second*10, configV2.Ingress.Rules[0].Config.ConnectTimeout)
require.Equal(t, config.CustomDuration{Duration: time.Second * 10}, configV2.Ingress.Rules[0].Config.ConnectTimeout)
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoTLSVerify)
// Inherited from global default
require.Equal(t, true, configV2.Ingress.Rules[0].Config.NoHappyEyeballs)
@@ -108,14 +111,14 @@ func TestUpdateConfiguration(t *testing.T) {
require.True(t, configV2.Ingress.Rules[1].Matches("jira.tunnel.org", "/users"))
require.Equal(t, "http://172.32.20.6:80", configV2.Ingress.Rules[1].Service.String())
// originRequest of this ingress rule overrides global default
require.Equal(t, time.Second*30, configV2.Ingress.Rules[1].Config.ConnectTimeout)
require.Equal(t, config.CustomDuration{Duration: time.Second * 30}, configV2.Ingress.Rules[1].Config.ConnectTimeout)
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoTLSVerify)
// Inherited from global default
require.Equal(t, true, configV2.Ingress.Rules[1].Config.NoHappyEyeballs)
// Validate ingress rule 2, it's the catch-all rule
require.True(t, configV2.Ingress.Rules[2].Matches("blogs.tunnel.io", "/2022/02/10"))
// Inherited from global default
require.Equal(t, time.Second*90, configV2.Ingress.Rules[2].Config.ConnectTimeout)
require.Equal(t, config.CustomDuration{Duration: time.Second * 90}, configV2.Ingress.Rules[2].Config.ConnectTimeout)
require.Equal(t, false, configV2.Ingress.Rules[2].Config.NoTLSVerify)
require.Equal(t, true, configV2.Ingress.Rules[2].Config.NoHappyEyeballs)
require.True(t, configV2.WarpRoutingEnabled)
@@ -192,7 +195,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
configJSONV1 = []byte(fmt.Sprintf(`
{
"originRequest": {
"connectTimeout": 90000000000,
"connectTimeout": 90,
"noHappyEyeballs": true
},
"ingress": [
@@ -201,7 +204,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
"service": "%s",
"originRequest": {
"httpHostHeader": "%s",
"connectTimeout": 10000000000
"connectTimeout": 10
}
},
{
@@ -248,7 +251,10 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
}
)
orchestrator, err := NewOrchestrator(context.Background(), initConfig, testTags, &testLogger)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
orchestrator, err := NewOrchestrator(ctx, initConfig, testTags, &testLogger)
require.NoError(t, err)
updateWithValidation(t, orchestrator, 1, configJSONV1)
@@ -264,8 +270,9 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
wg.Add(1)
go func(i int, originProxy connection.OriginProxy) {
defer wg.Done()
resp, err := proxyHTTP(t, originProxy, hostname)
require.NoError(t, err)
resp, err := proxyHTTP(originProxy, hostname)
require.NoError(t, err, "proxyHTTP %d failed %v", i, err)
defer resp.Body.Close()
var warpRoutingDisabled bool
// The response can be from initOrigin, http_status:204 or http_status:418
@@ -289,7 +296,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
// Once we have originProxy, it won't be changed by configuration updates.
// We can infer the version by the ProxyHTTP response code
pr, pw := io.Pipe()
// concurrentRespWriter makes sure ResponseRecorder is not read/write concurrently, and read waits for the first write
w := newRespReadWriteFlusher()
// Write TCP message and make sure it's echo back. This has to be done in a go routune since ProxyTCP doesn't
@@ -302,7 +309,14 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
tcpEyeball(t, pw, tcpBody, w)
}()
}
proxyTCP(t, originProxy, tcpOrigin.Addr().String(), w, pr, warpRoutingDisabled)
err = proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), w, pr)
if warpRoutingDisabled {
require.Error(t, err, "expect proxyTCP %d to return error", i)
} else {
require.NoError(t, err, "proxyTCP %d failed %v", i, err)
}
}(i, originProxy)
if i == concurrentRequests/4 {
@@ -318,6 +332,7 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
// Makes sure v2 is applied before v3
<-appliedV2
updateWithValidation(t, orchestrator, 3, configJSONV3)
}()
@@ -327,15 +342,20 @@ func TestConcurrentUpdateAndRead(t *testing.T) {
wg.Wait()
}
func proxyHTTP(t *testing.T, originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
func proxyHTTP(originProxy connection.OriginProxy, hostname string) (*http.Response, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", hostname), nil)
require.NoError(t, err)
if err != nil {
return nil, err
}
w := httptest.NewRecorder()
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeHTTP)
require.NoError(t, err)
log := zerolog.Nop()
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeHTTP, &log)
if err != nil {
return nil, err
}
err = originProxy.ProxyHTTP(respWriter, req, false)
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), false)
if err != nil {
return nil, err
}
@@ -354,12 +374,17 @@ func tcpEyeball(t *testing.T, reqWriter io.WriteCloser, body string, respReadWri
require.Equal(t, writeN, n)
}
func proxyTCP(t *testing.T, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser, expectErr bool) {
func proxyTCP(ctx context.Context, originProxy connection.OriginProxy, originAddr string, w http.ResponseWriter, reqBody io.ReadCloser) error {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s", originAddr), reqBody)
require.NoError(t, err)
if err != nil {
return err
}
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeTCP)
require.NoError(t, err)
log := zerolog.Nop()
respWriter, err := connection.NewHTTP2RespWriter(req, w, connection.TypeTCP, &log)
if err != nil {
return err
}
tcpReq := &connection.TCPRequest{
Dest: originAddr,
@@ -367,12 +392,8 @@ func proxyTCP(t *testing.T, originProxy connection.OriginProxy, originAddr strin
LBProbe: false,
}
rws := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
if expectErr {
require.Error(t, originProxy.ProxyTCP(context.Background(), rws, tcpReq))
return
}
require.NoError(t, originProxy.ProxyTCP(context.Background(), rws, tcpReq))
return originProxy.ProxyTCP(ctx, rws, tcpReq)
}
func serveTCPOrigin(t *testing.T, tcpOrigin net.Listener, wg *sync.WaitGroup) {
@@ -468,7 +489,7 @@ func TestClosePreviousProxies(t *testing.T) {
originProxyV1, err := orchestrator.GetOriginProxy()
require.NoError(t, err)
resp, err := proxyHTTP(t, originProxyV1, hostname)
resp, err := proxyHTTP(originProxyV1, hostname)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
@@ -476,12 +497,12 @@ func TestClosePreviousProxies(t *testing.T) {
originProxyV2, err := orchestrator.GetOriginProxy()
require.NoError(t, err)
resp, err = proxyHTTP(t, originProxyV2, hostname)
resp, err = proxyHTTP(originProxyV2, hostname)
require.NoError(t, err)
require.Equal(t, http.StatusTeapot, resp.StatusCode)
// The hello-world server in config v1 should have been stopped
resp, err = proxyHTTP(t, originProxyV1, hostname)
resp, err = proxyHTTP(originProxyV1, hostname)
require.Error(t, err)
require.Nil(t, resp)
@@ -492,7 +513,7 @@ func TestClosePreviousProxies(t *testing.T) {
require.NoError(t, err)
require.NotEqual(t, originProxyV1, originProxyV3)
resp, err = proxyHTTP(t, originProxyV3, hostname)
resp, err = proxyHTTP(originProxyV3, hostname)
require.NoError(t, err)
require.Equal(t, http.StatusOK, resp.StatusCode)
@@ -501,7 +522,7 @@ func TestClosePreviousProxies(t *testing.T) {
// Wait for proxies to shutdown
time.Sleep(time.Millisecond * 10)
resp, err = proxyHTTP(t, originProxyV3, hostname)
resp, err = proxyHTTP(originProxyV3, hostname)
require.Error(t, err)
require.Nil(t, resp)
}
@@ -550,6 +571,9 @@ func TestPersistentConnection(t *testing.T) {
tcpReqReader, tcpReqWriter := io.Pipe()
tcpRespReadWriter := newRespReadWriteFlusher()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var wg sync.WaitGroup
wg.Add(3)
// Start TCP origin
@@ -567,7 +591,7 @@ func TestPersistentConnection(t *testing.T) {
// Simulate cloudflared recieving a TCP connection
go func() {
defer wg.Done()
proxyTCP(t, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader, false)
require.NoError(t, proxyTCP(ctx, originProxy, tcpOrigin.Addr().String(), tcpRespReadWriter, tcpReqReader))
}()
// Simulate cloudflared recieving a WS connection
go func() {
@@ -578,10 +602,11 @@ func TestPersistentConnection(t *testing.T) {
// ProxyHTTP will add Connection, Upgrade and Sec-Websocket-Version headers
req.Header.Add("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket)
log := zerolog.Nop()
respWriter, err := connection.NewHTTP2RespWriter(req, wsRespReadWriter, connection.TypeWebsocket, &log)
require.NoError(t, err)
err = originProxy.ProxyHTTP(respWriter, req, true)
err = originProxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), true)
require.NoError(t, err)
}()
@@ -617,6 +642,19 @@ func TestPersistentConnection(t *testing.T) {
wg.Wait()
}
func TestSerializeLocalConfig(t *testing.T) {
c := &newLocalConfig{
RemoteConfig: ingress.RemoteConfig{
Ingress: ingress.Ingress{},
WarpRouting: config.WarpRoutingConfig{},
},
ConfigurationFlags: map[string]string{"a": "b"},
}
result, _ := json.Marshal(c)
fmt.Println(string(result))
}
func wsEcho(w http.ResponseWriter, r *http.Request) {
upgrader := gows.Upgrader{}
-29
View File
@@ -1,29 +0,0 @@
package proxy
import (
"sync"
)
type bufferPool struct {
// A bufferPool must not be copied after first use.
// https://golang.org/pkg/sync/#Pool
buffers sync.Pool
}
func newBufferPool(bufferSize int) *bufferPool {
return &bufferPool{
buffers: sync.Pool{
New: func() interface{} {
return make([]byte, bufferSize)
},
},
}
}
func (p *bufferPool) Get() []byte {
return p.buffers.Get().([]byte)
}
func (p *bufferPool) Put(buf []byte) {
p.buffers.Put(buf)
}
+29 -15
View File
@@ -10,10 +10,14 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/cloudflare/cloudflared/carrier"
"github.com/cloudflare/cloudflared/cfio"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/websocket"
)
@@ -32,7 +36,6 @@ type Proxy struct {
warpRouting *ingress.WarpRoutingService
tags []tunnelpogs.Tag
log *zerolog.Logger
bufferPool *bufferPool
}
// NewOriginProxy returns a new instance of the Proxy struct.
@@ -46,7 +49,6 @@ func NewOriginProxy(
ingressRules: ingressRules,
tags: tags,
log: log,
bufferPool: newBufferPool(512 * 1024),
}
if warpRoutingEnabled {
proxy.warpRouting = ingress.NewWarpRoutingService()
@@ -60,16 +62,19 @@ func NewOriginProxy(
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
func (p *Proxy) ProxyHTTP(
w connection.ResponseWriter,
req *http.Request,
tr *tracing.TracedRequest,
isWebsocket bool,
) error {
incrementRequests()
defer decrementConcurrentRequests()
req := tr.Request
cfRay := connection.FindCfRayHeader(req)
lbProbe := connection.IsLBProbeRequest(req)
p.appendTagHeaders(req)
_, ruleSpan := tr.Tracer().Start(req.Context(), "ingress_match",
trace.WithAttributes(attribute.String("req-host", req.Host)))
rule, ruleNum := p.ingressRules.FindMatchingRule(req.Host, req.URL.Path)
logFields := logFields{
cfRay: cfRay,
@@ -77,12 +82,14 @@ func (p *Proxy) ProxyHTTP(
rule: ruleNum,
}
p.logRequest(req, logFields)
ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum))
ruleSpan.End()
switch originProxy := rule.Service.(type) {
case ingress.HTTPOriginProxy:
if err := p.proxyHTTPRequest(
w,
req,
tr,
originProxy,
isWebsocket,
rule.Config.DisableChunkedEncoding,
@@ -93,7 +100,6 @@ func (p *Proxy) ProxyHTTP(
return err
}
return nil
case ingress.StreamBasedOriginProxy:
dest, err := getDestFromRule(rule, req)
if err != nil {
@@ -155,15 +161,15 @@ func ruleField(ing ingress.Ingress, ruleNum int) (ruleID string, srv string) {
// ProxyHTTPRequest proxies requests of underlying type http and websocket to the origin service.
func (p *Proxy) proxyHTTPRequest(
w connection.ResponseWriter,
req *http.Request,
tr *tracing.TracedRequest,
httpService ingress.HTTPOriginProxy,
isWebsocket bool,
disableChunkedEncoding bool,
fields logFields,
) error {
roundTripReq := req
roundTripReq := tr.Request
if isWebsocket {
roundTripReq = req.Clone(req.Context())
roundTripReq = tr.Clone(tr.Request.Context())
roundTripReq.Header.Set("Connection", "Upgrade")
roundTripReq.Header.Set("Upgrade", "websocket")
roundTripReq.Header.Set("Sec-Websocket-Version", "13")
@@ -173,7 +179,7 @@ func (p *Proxy) proxyHTTPRequest(
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
if disableChunkedEncoding {
roundTripReq.TransferEncoding = []string{"gzip", "deflate"}
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
cLength, err := strconv.Atoi(tr.Request.Header.Get("Content-Length"))
if err == nil {
roundTripReq.ContentLength = int64(cLength)
}
@@ -187,12 +193,24 @@ func (p *Proxy) proxyHTTPRequest(
roundTripReq.Header.Set("User-Agent", "")
}
_, ttfbSpan := tr.Tracer().Start(tr.Context(), "ttfb_origin")
resp, err := httpService.RoundTrip(roundTripReq)
if err != nil {
tracing.EndWithErrorStatus(ttfbSpan, err)
return errors.Wrap(err, "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared")
}
tracing.EndWithStatusCode(ttfbSpan, resp.StatusCode)
defer resp.Body.Close()
// resp headers can be nil
if resp.Header == nil {
resp.Header = make(http.Header)
}
// Add spans to response header (if available)
tr.AddSpans(resp.Header, p.log)
err = w.WriteRespHeaders(resp.StatusCode, resp.Header)
if err != nil {
return errors.Wrap(err, "Error writing response header")
@@ -207,7 +225,7 @@ func (p *Proxy) proxyHTTPRequest(
eyeballStream := &bidirectionalStream{
writer: w,
reader: req.Body,
reader: tr.Request.Body,
}
websocket.Stream(eyeballStream, rwc, p.log)
@@ -218,11 +236,7 @@ func (p *Proxy) proxyHTTPRequest(
p.log.Debug().Msg("Detected Server-Side Events from Origin")
p.writeEventStream(w, resp.Body)
} else {
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
// compression generates dictionary on first write
buf := p.bufferPool.Get()
defer p.bufferPool.Put(buf)
_, _ = io.CopyBuffer(w, resp.Body, buf)
_, _ = cfio.Copy(w, resp.Body)
}
p.logOriginResponse(resp, fields)
+32 -8
View File
@@ -27,6 +27,7 @@ import (
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/ingress"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/tracing"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
@@ -60,6 +61,11 @@ func (w *mockHTTPRespWriter) Read(data []byte) (int, error) {
return 0, fmt.Errorf("mockHTTPRespWriter doesn't implement io.Reader")
}
// respHeaders is a test function to read respHeaders
func (w *mockHTTPRespWriter) headers() http.Header {
return w.Header()
}
type mockWSRespWriter struct {
*mockHTTPRespWriter
writeNotification chan []byte
@@ -146,7 +152,7 @@ func testProxyHTTP(proxy connection.OriginProxy) func(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://localhost:8080", nil)
require.NoError(t, err)
err = proxy.ProxyHTTP(responseWriter, req, false)
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
require.NoError(t, err)
for _, tag := range testTags {
assert.Equal(t, tag.Value, req.Header.Get(TagHeaderNamePrefix+tag.Name))
@@ -173,7 +179,7 @@ func testProxyWebsocket(proxy connection.OriginProxy) func(t *testing.T) {
errGroup, ctx := errgroup.WithContext(ctx)
errGroup.Go(func() error {
err = proxy.ProxyHTTP(responseWriter, req, true)
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), true)
require.NoError(t, err)
require.Equal(t, http.StatusSwitchingProtocols, responseWriter.Code)
@@ -234,7 +240,7 @@ func testProxySSE(proxy connection.OriginProxy) func(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
err = proxy.ProxyHTTP(responseWriter, req, false)
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
require.NoError(t, err)
require.Equal(t, http.StatusOK, responseWriter.Code)
@@ -346,7 +352,7 @@ func runIngressTestScenarios(t *testing.T, unvalidatedIngress []config.Unvalidat
req, err := http.NewRequest(http.MethodGet, test.url, nil)
require.NoError(t, err)
err = proxy.ProxyHTTP(responseWriter, req, false)
err = proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false)
require.NoError(t, err)
assert.Equal(t, test.expectedStatus, responseWriter.Code)
@@ -393,7 +399,7 @@ func TestProxyError(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1", nil)
assert.NoError(t, err)
assert.Error(t, proxy.ProxyHTTP(responseWriter, req, false))
assert.Error(t, proxy.ProxyHTTP(responseWriter, tracing.NewTracedRequest(req), false))
}
type replayer struct {
@@ -554,6 +560,24 @@ func TestConnections(t *testing.T) {
},
},
},
{
// Send (unexpected) HTTP when origin expects WS (to unwrap for raw TCP)
name: "http-(ws)tcp proxy",
args: args{
ingressServiceScheme: "tcp://",
originService: runEchoTCPService,
eyeballResponseWriter: newMockHTTPRespWriter(),
eyeballRequestBody: http.NoBody,
connectionType: connection.TypeHTTP,
requestHeaders: map[string][]string{
"Cf-Cloudflared-Proxy-Src": {"non-blank-value"},
},
},
want: want{
message: []byte{},
headers: map[string][]string{},
},
},
{
name: "tcp-tcp proxy without warpRoutingService enabled",
args: args{
@@ -650,10 +674,10 @@ func TestConnections(t *testing.T) {
}()
}
if test.args.connectionType == connection.TypeTCP {
rws := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
err = proxy.ProxyTCP(ctx, rws, &connection.TCPRequest{Dest: dest})
rwa := connection.NewHTTPResponseReadWriterAcker(respWriter, req)
err = proxy.ProxyTCP(ctx, rwa, &connection.TCPRequest{Dest: dest})
} else {
err = proxy.ProxyHTTP(respWriter, req, test.args.connectionType == connection.TypeWebsocket)
err = proxy.ProxyHTTP(respWriter, tracing.NewTracedRequest(req), test.args.connectionType == connection.TypeWebsocket)
}
cancel()
+65 -24
View File
@@ -3,9 +3,9 @@ package quic
import (
"context"
"net"
"time"
"github.com/lucas-clemente/quic-go/logging"
"github.com/lucas-clemente/quic-go/qlog"
"github.com/rs/zerolog"
)
@@ -40,75 +40,116 @@ func NewServerTracer(logger *zerolog.Logger) logging.Tracer {
}
}
func (t *tracer) TracerForConnection(_ context.Context, p logging.Perspective, odcid logging.ConnectionID) logging.ConnectionTracer {
connID := logging.ConnectionID(odcid).String()
ql := &quicLogger{
logger: t.logger,
connectionID: connID,
}
func (t *tracer) TracerForConnection(_ctx context.Context, _p logging.Perspective, _odcid logging.ConnectionID) logging.ConnectionTracer {
if t.config.isClient {
return newConnTracer(ql, p, odcid, newClientCollector(t.config.index))
return newConnTracer(newClientCollector(t.config.index))
}
return newConnTracer(ql, p, odcid, newServiceCollector())
return newConnTracer(newServiceCollector())
}
func (*tracer) SentPacket(net.Addr, *logging.Header, logging.ByteCount, []logging.Frame) {}
func (*tracer) DroppedPacket(net.Addr, logging.PacketType, logging.ByteCount, logging.PacketDropReason) {
}
// connTracer is a wrapper around https://pkg.go.dev/github.com/lucas-clemente/quic-go@v0.23.0/qlog#NewConnectionTracer to collect metrics
var _ logging.Tracer = (*tracer)(nil)
// connTracer collects connection level metrics
type connTracer struct {
logging.ConnectionTracer
metricsCollector MetricsCollector
connectionID string
}
func newConnTracer(ql *quicLogger, p logging.Perspective, odcid logging.ConnectionID, metricsCollector MetricsCollector) logging.ConnectionTracer {
var _ logging.ConnectionTracer = (*connTracer)(nil)
func newConnTracer(metricsCollector MetricsCollector) logging.ConnectionTracer {
return &connTracer{
qlog.NewConnectionTracer(ql, p, odcid),
metricsCollector,
logging.ConnectionID(odcid).String(),
metricsCollector: metricsCollector,
}
}
func (ct *connTracer) StartedConnection(local, remote net.Addr, srcConnID, destConnID logging.ConnectionID) {
ct.metricsCollector.startedConnection()
ct.ConnectionTracer.StartedConnection(local, remote, srcConnID, destConnID)
}
func (ct *connTracer) ClosedConnection(err error) {
ct.metricsCollector.closedConnection(err)
ct.ConnectionTracer.ClosedConnection(err)
}
func (ct *connTracer) SentPacket(hdr *logging.ExtendedHeader, packetSize logging.ByteCount, ack *logging.AckFrame, frames []logging.Frame) {
ct.metricsCollector.sentPackets(packetSize)
ct.ConnectionTracer.SentPacket(hdr, packetSize, ack, frames)
}
func (ct *connTracer) ReceivedPacket(hdr *logging.ExtendedHeader, size logging.ByteCount, frames []logging.Frame) {
ct.metricsCollector.receivedPackets(size)
ct.ConnectionTracer.ReceivedPacket(hdr, size, frames)
}
func (ct *connTracer) BufferedPacket(pt logging.PacketType) {
ct.metricsCollector.bufferedPackets(pt)
ct.ConnectionTracer.BufferedPacket(pt)
}
func (ct *connTracer) DroppedPacket(pt logging.PacketType, size logging.ByteCount, reason logging.PacketDropReason) {
ct.metricsCollector.droppedPackets(pt, size, reason)
ct.ConnectionTracer.DroppedPacket(pt, size, reason)
}
func (ct *connTracer) LostPacket(level logging.EncryptionLevel, number logging.PacketNumber, reason logging.PacketLossReason) {
ct.metricsCollector.lostPackets(reason)
ct.ConnectionTracer.LostPacket(level, number, reason)
}
func (ct *connTracer) UpdatedMetrics(rttStats *logging.RTTStats, cwnd, bytesInFlight logging.ByteCount, packetsInFlight int) {
ct.metricsCollector.updatedRTT(rttStats)
ct.ConnectionTracer.UpdatedMetrics(rttStats, cwnd, bytesInFlight, packetsInFlight)
}
func (ct *connTracer) NegotiatedVersion(chosen logging.VersionNumber, clientVersions, serverVersions []logging.VersionNumber) {
}
func (ct *connTracer) SentTransportParameters(parameters *logging.TransportParameters) {
}
func (ct *connTracer) ReceivedTransportParameters(parameters *logging.TransportParameters) {
}
func (ct *connTracer) RestoredTransportParameters(parameters *logging.TransportParameters) {
}
func (ct *connTracer) ReceivedVersionNegotiationPacket(header *logging.Header, numbers []logging.VersionNumber) {
}
func (ct *connTracer) ReceivedRetry(header *logging.Header) {
}
func (ct *connTracer) AcknowledgedPacket(level logging.EncryptionLevel, number logging.PacketNumber) {
}
func (ct *connTracer) UpdatedCongestionState(state logging.CongestionState) {
}
func (ct *connTracer) UpdatedPTOCount(value uint32) {
}
func (ct *connTracer) UpdatedKeyFromTLS(level logging.EncryptionLevel, perspective logging.Perspective) {
}
func (ct *connTracer) UpdatedKey(generation logging.KeyPhase, remote bool) {
}
func (ct *connTracer) DroppedEncryptionLevel(level logging.EncryptionLevel) {
}
func (ct *connTracer) DroppedKey(generation logging.KeyPhase) {
}
func (ct *connTracer) SetLossTimer(timerType logging.TimerType, level logging.EncryptionLevel, time time.Time) {
}
func (ct *connTracer) LossTimerExpired(timerType logging.TimerType, level logging.EncryptionLevel) {
}
func (ct *connTracer) LossTimerCanceled() {
}
func (ct *connTracer) Close() {
}
func (ct *connTracer) Debug(name, msg string) {
}
type quicLogger struct {
+327
View File
@@ -0,0 +1,327 @@
"""
This is a utility for creating deb and rpm packages, signing them
and uploading them to a storage and adding metadata to workers KV.
It has two over-arching responsiblities:
1. Create deb and yum repositories from .deb and .rpm files.
This is also responsible for signing the packages and generally preparing
them to be in an uploadable state.
2. Upload these packages to a storage in a format that apt and yum expect.
"""
from subprocess import Popen, PIPE
import os
import argparse
import base64
from pathlib import Path
import logging
import shutil
from hashlib import sha256
import gnupg
import boto3
from botocore.client import Config
from botocore.exceptions import ClientError
# The front facing R2 URL to access assets from.
R2_ASSET_URL = 'https://demo-r2-worker.cloudflare-tunnel.workers.dev/'
class PkgUploader:
def __init__(self, account_id, bucket_name, client_id, client_secret):
self.account_id = account_id
self.bucket_name = bucket_name
self.client_id = client_id
self.client_secret = client_secret
def upload_pkg_to_r2(self, filename, upload_file_path):
endpoint_url = f"https://{self.account_id}.r2.cloudflarestorage.com"
token_secret_hash = sha256(self.client_secret.encode()).hexdigest()
config = Config(
region_name = 'auto',
s3={
"addressing_style": "path",
}
)
r2 = boto3.client(
"s3",
endpoint_url=endpoint_url,
aws_access_key_id=self.client_id,
aws_secret_access_key=token_secret_hash,
config=config,
)
print(f"uploading asset: {filename} to {upload_file_path} in bucket{self.bucket_name}...")
try:
r2.upload_file(filename, self.bucket_name, upload_file_path)
except ClientError as e:
raise e
class PkgCreator:
"""
The distribution conf is what dictates to reprepro, the debian packaging building
and signing tool we use, what distros to support, what GPG key to use for signing
and what to call the debian binary etc. This function creates it "./conf/distributions".
origin - name of your package (String)
label - label of your package (could be same as the name) (String)
release - release you want this to be distributed for (List of Strings)
components - could be a channel like main/stable/beta
archs - Architecture (List of Strings)
description - (String)
gpg_key_id - gpg key id of what you want to use to sign the packages.(String)
"""
def create_distribution_conf(self,
file_path,
origin,
label,
releases,
archs,
components,
description,
gpg_key_id ):
with open(file_path, "w+") as distributions_file:
for release in releases:
distributions_file.write(f"Origin: {origin}\n")
distributions_file.write(f"Label: {label}\n")
distributions_file.write(f"Codename: {release}\n")
archs_list = " ".join(archs)
distributions_file.write(f"Architectures: {archs_list}\n")
distributions_file.write(f"Components: {components}\n")
distributions_file.write(f"Description: {description} - {release}\n")
distributions_file.write(f"SignWith: {gpg_key_id}\n")
distributions_file.write("\n")
return distributions_file
"""
Uses the reprepro tool to generate packages, sign them and create the InRelease as specified
by the distribution_conf file.
This function creates three folders db, pool and dist.
db and pool contain information and metadata about builds. We can ignore these.
dist: contains all the pkgs and signed releases that are necessary for an apt download.
"""
def create_deb_pkgs(self, release, deb_file):
print(f"creating deb pkgs: {release} : {deb_file}")
p = Popen(["reprepro", "includedeb", release, deb_file], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
if p.returncode != 0:
print(f"create deb_pkgs result => {out}, {err}")
raise
def create_rpm_pkgs(self, artifacts_path, gpg_key_name):
self._setup_rpm_pkg_directories(artifacts_path, gpg_key_name)
p = Popen(["createrepo", "./rpm"], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
if p.returncode != 0:
print(f"create rpm_pkgs result => {out}, {err}")
raise
self._sign_repomd()
def _sign_rpms(self, file_path):
p = Popen(["rpm" , "--define", f"_gpg_name {gpg_key_name}", "--addsign", file_path], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
if p.returncode != 0:
print(f"rpm sign result result => {out}, {err}")
raise
def _sign_repomd(self):
p = Popen(["gpg", "--batch", "--detach-sign", "--armor", "./rpm/repodata/repomd.xml"], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
if p.returncode != 0:
print(f"sign repomd result => {out}, {err}")
raise
"""
sets up and signs the RPM directories in the following format:
- rpm
- aarch64
- x86_64
- 386
this assumes the assets are in the format <prefix>-<aarch64/x86_64/386>.rpm
"""
def _setup_rpm_pkg_directories(self, artifacts_path, gpg_key_name, archs=["aarch64", "x86_64", "386"]):
for arch in archs:
for root, _ , files in os.walk(artifacts_path):
for file in files:
if file.endswith(f"{arch}.rpm"):
new_dir = f"./rpm/{arch}"
os.makedirs(new_dir, exist_ok=True)
old_path = os.path.join(root, file)
new_path = os.path.join(new_dir, file)
shutil.copyfile(old_path, new_path)
self._sign_rpms(new_path)
"""
imports gpg keys into the system so reprepro and createrepo can use it to sign packages.
it returns the GPG ID after a successful import
"""
def import_gpg_keys(self, private_key, public_key):
gpg = gnupg.GPG()
private_key = base64.b64decode(private_key)
gpg.import_keys(private_key)
public_key = base64.b64decode(public_key)
gpg.import_keys(public_key)
data = gpg.list_keys(secret=True)
return (data[0]["fingerprint"], data[0]["uids"][0])
"""
basically rpm --import <key_file>
This enables us to sign rpms.
"""
def import_rpm_key(self, public_key):
file_name = "pb.key"
with open(file_name, "wb") as f:
public_key = base64.b64decode(public_key)
f.write(public_key)
p = Popen(["rpm", "--import", file_name], stdout=PIPE, stderr=PIPE)
out, err = p.communicate()
if p.returncode != 0:
print(f"create rpm import result => {out}, {err}")
raise
"""
Walks through a directory and uploads it's assets to R2.
directory : root directory to walk through (String).
release: release string. If this value is none, a specific release path will not be created
and the release will be uploaded to the default path.
binary: name of the binary to upload
"""
def upload_from_directories(pkg_uploader, directory, release, binary):
for root, _ , files in os.walk(directory):
for file in files:
upload_file_name = os.path.join(binary, root, file)
if release:
upload_file_name = os.path.join(release, upload_file_name)
filename = os.path.join(root,file)
try:
pkg_uploader.upload_pkg_to_r2(filename, upload_file_name)
except ClientError as e:
logging.error(e)
return
"""
1. looks into a built_artifacts folder for cloudflared debs
2. creates Packages.gz, InRelease (signed) files
3. uploads them to Cloudflare R2
pkg_creator, pkg_uploader: are instantiations of the two classes above.
gpg_key_id: is an id indicating the key the package should be signed with. The public key of this id will be
uploaded to R2 so it can be presented to apt downloaders.
release_version: is the cloudflared release version. Only provide this if you want a permanent backup.
"""
def create_deb_packaging(pkg_creator, pkg_uploader, releases, gpg_key_id, binary_name, archs, package_component, release_version):
# set configuration for package creation.
print(f"initialising configuration for {binary_name} , {archs}")
Path("./conf").mkdir(parents=True, exist_ok=True)
pkg_creator.create_distribution_conf(
"./conf/distributions",
binary_name,
binary_name,
releases,
archs,
package_component,
f"apt repository for {binary_name}",
gpg_key_id)
# create deb pkgs
for release in releases:
for arch in archs:
print(f"creating deb pkgs for {release} and {arch}...")
pkg_creator.create_deb_pkgs(release, f"./built_artifacts/cloudflared-linux-{arch}.deb")
print("uploading latest to r2...")
upload_from_directories(pkg_uploader, "dists", None, binary_name)
upload_from_directories(pkg_uploader, "pool", None, binary_name)
if release_version:
print(f"uploading versioned release {release_version} to r2...")
upload_from_directories(pkg_uploader, "dists", release_version, binary_name)
upload_from_directories(pkg_uploader, "pool", release_version, binary_name)
def create_rpm_packaging(pkg_creator, pkg_uploader, artifacts_path, release_version, binary_name, gpg_key_name):
print(f"creating rpm pkgs...")
pkg_creator.create_rpm_pkgs(artifacts_path, gpg_key_name)
print("uploading latest to r2...")
upload_from_directories(pkg_uploader, "rpm", None, binary_name)
if release_version:
print(f"uploading versioned release {release_version} to r2...")
upload_from_directories(pkg_uploader, "rpm", release_version, binary_name)
def parse_args():
parser = argparse.ArgumentParser(
description="Creates linux releases and uploads them in a packaged format"
)
parser.add_argument(
"--bucket", default=os.environ.get("R2_BUCKET"), help="R2 Bucket name"
)
parser.add_argument(
"--id", default=os.environ.get("R2_CLIENT_ID"), help="R2 Client ID"
)
parser.add_argument(
"--secret", default=os.environ.get("R2_CLIENT_SECRET"), help="R2 Client Secret"
)
parser.add_argument(
"--account", default=os.environ.get("R2_ACCOUNT_ID"), help="R2 Account Tag"
)
parser.add_argument(
"--release-tag", default=os.environ.get("RELEASE_VERSION"), help="Release version you want your pkgs to be\
prefixed with. Leave empty if you don't want tagged release versions backed up to R2."
)
parser.add_argument(
"--binary", default=os.environ.get("BINARY_NAME"), help="The name of the binary the packages are for"
)
parser.add_argument(
"--gpg-private-key", default=os.environ.get("LINUX_SIGNING_PRIVATE_KEY"), help="GPG private key to sign the\
packages"
)
parser.add_argument(
"--gpg-public-key", default=os.environ.get("LINUX_SIGNING_PUBLIC_KEY"), help="GPG public key used for\
signing packages"
)
parser.add_argument(
"--deb-based-releases", default=["bookworm", "bullseye", "buster", "jammy", "impish", "focal", "bionic"],
help="list of debian based releases that need to be packaged for"
)
parser.add_argument(
"--archs", default=["amd64", "386", "arm64"], help="list of architectures we want to package for. Note that\
it is the caller's responsiblity to ensure that these debs are already present in a directory. This script\
will not build binaries or create their debs."
)
args = parser.parse_args()
return args
if __name__ == "__main__":
try:
args = parse_args()
except Exception as e:
logging.exception(e)
exit(1)
pkg_creator = PkgCreator()
(gpg_key_id, gpg_key_name) = pkg_creator.import_gpg_keys(args.gpg_private_key, args.gpg_public_key)
pkg_creator.import_rpm_key(args.gpg_public_key)
pkg_uploader = PkgUploader(args.account, args.bucket, args.id, args.secret)
print(f"signing with gpg_key: {gpg_key_id}")
create_deb_packaging(pkg_creator, pkg_uploader, args.deb_based_releases, gpg_key_id, args.binary, args.archs,
"main", args.release_tag)
create_rpm_packaging(pkg_creator, pkg_uploader, "./built_artifacts", args.release_tag, args.binary, gpg_key_name)
+7 -11
View File
@@ -15,10 +15,10 @@ import (
"net/url"
"time"
"github.com/coreos/go-oidc/jose"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
gossh "golang.org/x/crypto/ssh"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/cloudflare/cloudflared/config"
cfpath "github.com/cloudflare/cloudflared/token"
@@ -87,37 +87,33 @@ func SignCert(token, pubKey string) (string, error) {
return "", errors.New("invalid token")
}
jwt, err := jose.ParseJWT(token)
parsedToken, err := jwt.ParseSigned(token)
if err != nil {
return "", errors.Wrap(err, "failed to parse JWT")
}
claims, err := jwt.Claims()
claims := jwt.Claims{}
err = parsedToken.UnsafeClaimsWithoutVerification(&claims)
if err != nil {
return "", errors.Wrap(err, "failed to retrieve JWT claims")
}
issuer, _, err := claims.StringClaim("iss")
if err != nil {
return "", errors.Wrap(err, "failed to retrieve JWT iss")
}
buf, err := json.Marshal(&signPayload{
PublicKey: pubKey,
JWT: token,
Issuer: issuer,
Issuer: claims.Issuer,
})
if err != nil {
return "", errors.Wrap(err, "failed to marshal signPayload")
}
var res *http.Response
if mockRequest != nil {
res, err = mockRequest(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
res, err = mockRequest(claims.Issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
} else {
client := http.Client{
Timeout: 10 * time.Second,
}
res, err = client.Post(issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
res, err = client.Post(claims.Issuer+signEndpoint, "application/json", bytes.NewBuffer(buf))
}
if err != nil {
+19 -17
View File
@@ -4,8 +4,6 @@
package sshgen
import (
"crypto/rand"
"crypto/rsa"
"encoding/json"
"fmt"
"io"
@@ -18,8 +16,9 @@ import (
"testing"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/stretchr/testify/assert"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/cloudflare/cloudflared/config"
cfpath "github.com/cloudflare/cloudflared/token"
@@ -97,22 +96,25 @@ func TestCertGenSuccess(t *testing.T) {
}
func tokenGenerator() string {
iat := time.Now().Unix()
exp := time.Now().Add(time.Minute * 5).Unix()
claims := jose.Claims{}
claims.Add("aud", audTest)
claims.Add("iat", iat)
claims.Add("nonce", nonceTest)
claims.Add("exp", exp)
iat := time.Now()
exp := time.Now().Add(time.Minute * 5)
k, err := rsa.GenerateKey(rand.Reader, 512)
claims := jwt.Claims{
Audience: jwt.Audience{audTest},
IssuedAt: jwt.NewNumericDate(iat),
Expiry: jwt.NewNumericDate(exp),
}
key := []byte("secret")
signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: key}, (&jose.SignerOptions{}).WithType("JWT"))
if err != nil {
return ""
panic(err)
}
signer := jose.NewSignerRSA("asdf", *k)
token, terr := jose.NewSignedJWT(claims, signer)
if terr != nil {
return ""
signedToken, err := jwt.Signed(signer).Claims(claims).CompactSerialize()
if err != nil {
panic(err)
}
return token.Encode()
return signedToken
}
+3 -2
View File
@@ -7,6 +7,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/lucas-clemente/quic-go"
"github.com/rs/zerolog"
"github.com/cloudflare/cloudflared/connection"
@@ -264,9 +265,9 @@ func (s *Supervisor) startFirstTunnel(
switch err.(type) {
case nil:
return
// try the next address if it was a dialError(network problem) or
// try the next address if it was a quic.IdleTimeoutError, dialError(network problem) or
// dupConnRegisterTunnelError
case edgediscovery.DialError, connection.DupConnRegisterTunnelError:
case *quic.IdleTimeoutError, edgediscovery.DialError, connection.DupConnRegisterTunnelError:
edgeErrors++
default:
return
+21 -9
View File
@@ -32,6 +32,7 @@ const (
dialTimeout = 15 * time.Second
FeatureSerializedHeaders = "serialized_headers"
FeatureQuickReconnects = "quick_reconnects"
FeatureAllowRemoteConfig = "allow_remote_config"
)
type TunnelConfig struct {
@@ -170,17 +171,15 @@ func ServeTunnelLoop(
protocolFallback.protocol,
gracefulShutdownC,
)
if !recoverable {
return err
}
config.Observer.SendReconnect(connIndex)
duration, ok := protocolFallback.GetMaxBackoffDuration(ctx)
if !ok {
return err
if recoverable {
duration, ok := protocolFallback.GetMaxBackoffDuration(ctx)
if !ok {
return err
}
config.Observer.SendReconnect(connIndex)
connLog.Logger().Info().Msgf("Retrying connection in up to %s seconds", duration)
}
connLog.Logger().Info().Msgf("Retrying connection in up to %s seconds", duration)
select {
case <-ctx.Done():
@@ -188,6 +187,10 @@ func ServeTunnelLoop(
case <-gracefulShutdownC:
return nil
case <-protocolFallback.BackoffTimer():
if !recoverable {
return err
}
if !selectNextProtocol(
connLog.Logger(),
protocolFallback,
@@ -232,6 +235,15 @@ func selectNextProtocol(
_, hasFallback := selector.Fallback()
if protocolBackoff.ReachedMaxRetries() || (hasFallback && isNetworkActivityTimeout) {
if isNetworkActivityTimeout {
connLog.Warn().Msg("If this log occurs persistently, and cloudflared is unable to connect to " +
"Cloudflare Network with `quic` protocol, then most likely your machine/network is getting its egress " +
"UDP to port 7844 (or others) blocked or dropped. Make sure to allow egress connectivity as per " +
"https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/configuration/ports-and-ips/\n" +
"If you are using private routing to this Tunnel, then UDP (and Private DNS Resolution) will not work" +
"unless your cloudflared can connect with Cloudflare Network with `quic`.")
}
fallback, hasFallback := selector.Fallback()
if !hasFallback {
return false
+8 -9
View File
@@ -13,9 +13,9 @@ import (
"syscall"
"time"
"github.com/coreos/go-oidc/jose"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"gopkg.in/square/go-jose.v2"
"github.com/cloudflare/cloudflared/config"
"github.com/cloudflare/cloudflared/retry"
@@ -342,7 +342,7 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
return "", err
}
var payload jwtPayload
err = json.Unmarshal(token.Payload, &payload)
err = json.Unmarshal(token.UnsafePayloadWithoutVerification(), &payload)
if err != nil {
return "", err
}
@@ -351,7 +351,7 @@ func GetOrgTokenIfExists(authDomain string) (string, error) {
err := os.Remove(path)
return "", err
}
return token.Encode(), nil
return token.CompactSerialize()
}
func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
@@ -364,7 +364,7 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
return "", err
}
var payload jwtPayload
err = json.Unmarshal(token.Payload, &payload)
err = json.Unmarshal(token.UnsafePayloadWithoutVerification(), &payload)
if err != nil {
return "", err
}
@@ -373,22 +373,21 @@ func GetAppTokenIfExists(appInfo *AppInfo) (string, error) {
err := os.Remove(path)
return "", err
}
return token.Encode(), nil
return token.CompactSerialize()
}
// GetTokenIfExists will return the token from local storage if it exists and not expired
func getTokenIfExists(path string) (*jose.JWT, error) {
func getTokenIfExists(path string) (*jose.JSONWebSignature, error) {
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
token, err := jose.ParseJWT(string(content))
token, err := jose.ParseSigned(string(content))
if err != nil {
return nil, err
}
return &token, nil
return token, nil
}
// RemoveTokenIfExists removes the a token from local storage if it exists
+91
View File
@@ -0,0 +1,91 @@
package tracing
import (
"context"
"encoding/base64"
"errors"
"sync"
coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
"google.golang.org/protobuf/proto"
)
const (
MaxTraceAmount = 20
)
var (
errNoTraces = errors.New("no traces recorded to be exported")
errNoopTracer = errors.New("noop tracer has no traces")
)
type InMemoryClient interface {
// Spans returns a copy of the list of in-memory stored spans as a base64
// encoded otlp protobuf string.
Spans() (string, error)
}
// InMemoryOtlpClient is a client implementation for otlptrace.Client
type InMemoryOtlpClient struct {
mu sync.Mutex
spans []*tracepb.ResourceSpans
}
func (mc *InMemoryOtlpClient) Start(_ context.Context) error {
return nil
}
func (mc *InMemoryOtlpClient) Stop(_ context.Context) error {
return nil
}
// UploadTraces adds the provided list of spans to the in-memory list.
func (mc *InMemoryOtlpClient) UploadTraces(_ context.Context, protoSpans []*tracepb.ResourceSpans) error {
mc.mu.Lock()
defer mc.mu.Unlock()
// Catch to make sure too many traces aren't being added to response header.
// Returning nil makes sure we don't fail to send the traces we already recorded.
if len(mc.spans)+len(protoSpans) > MaxTraceAmount {
return nil
}
mc.spans = append(mc.spans, protoSpans...)
return nil
}
// Spans returns the list of in-memory stored spans as a base64 encoded otlp protobuf string.
func (mc *InMemoryOtlpClient) Spans() (string, error) {
mc.mu.Lock()
defer mc.mu.Unlock()
if len(mc.spans) <= 0 {
return "", errNoTraces
}
pbRequest := &coltracepb.ExportTraceServiceRequest{
ResourceSpans: mc.spans,
}
data, err := proto.Marshal(pbRequest)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(data), nil
}
// NoopOtlpClient is a client implementation for otlptrace.Client that does nothing
type NoopOtlpClient struct{}
func (mc *NoopOtlpClient) Start(_ context.Context) error {
return nil
}
func (mc *NoopOtlpClient) Stop(_ context.Context) error {
return nil
}
func (mc *NoopOtlpClient) UploadTraces(_ context.Context, _ []*tracepb.ResourceSpans) error {
return nil
}
// Spans always returns no traces error
func (mc *NoopOtlpClient) Spans() (string, error) {
return "", errNoopTracer
}
+161
View File
@@ -0,0 +1,161 @@
package tracing
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.opentelemetry.io/otel/trace"
commonpb "go.opentelemetry.io/proto/otlp/common/v1"
resourcepb "go.opentelemetry.io/proto/otlp/resource/v1"
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
)
const (
resourceSchemaUrl = "http://example.com/custom-resource-schema"
instrumentSchemaUrl = semconv.SchemaURL
)
var (
traceId = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}
spanId = []byte{0xFF, 0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8}
parentSpanId = []byte{0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08}
startTime = time.Date(2022, 4, 4, 0, 0, 0, 0, time.UTC)
endTime = startTime.Add(5 * time.Second)
traceState, _ = trace.ParseTraceState("key1=val1,key2=val2")
instrScope = &commonpb.InstrumentationScope{Name: "go.opentelemetry.io/test/otel", Version: "v1.6.0"}
otlpKeyValues = []*commonpb.KeyValue{
{
Key: "string_key",
Value: &commonpb.AnyValue{
Value: &commonpb.AnyValue_StringValue{
StringValue: "string value",
},
},
},
{
Key: "bool_key",
Value: &commonpb.AnyValue{
Value: &commonpb.AnyValue_BoolValue{
BoolValue: true,
},
},
},
}
otlpResource = &resourcepb.Resource{
Attributes: []*commonpb.KeyValue{
{
Key: "service.name",
Value: &commonpb.AnyValue{
Value: &commonpb.AnyValue_StringValue{
StringValue: "service-name",
},
},
},
},
}
)
var _ otlptrace.Client = (*InMemoryOtlpClient)(nil)
var _ InMemoryClient = (*InMemoryOtlpClient)(nil)
var _ otlptrace.Client = (*NoopOtlpClient)(nil)
var _ InMemoryClient = (*NoopOtlpClient)(nil)
func TestUploadTraces(t *testing.T) {
client := &InMemoryOtlpClient{}
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
spans2 := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
err := client.UploadTraces(context.Background(), spans)
assert.NoError(t, err)
err = client.UploadTraces(context.Background(), spans2)
assert.NoError(t, err)
assert.Len(t, client.spans, 2)
}
func TestSpans(t *testing.T) {
client := &InMemoryOtlpClient{}
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
err := client.UploadTraces(context.Background(), spans)
assert.NoError(t, err)
assert.Len(t, client.spans, 1)
enc, err := client.Spans()
assert.NoError(t, err)
expected := "CsECCiAKHgoMc2VydmljZS5uYW1lEg4KDHNlcnZpY2UtbmFtZRLxAQonCh1nby5vcGVudGVsZW1ldHJ5LmlvL3Rlc3Qvb3RlbBIGdjEuNi4wEp0BChAAAQIDBAUGBwgJCgsMDQ4PEgj//v38+/r5+BoTa2V5MT12YWwxLGtleTI9dmFsMiIIDw4NDAsKCQgqCnRyYWNlX25hbWUwATkAANJvaYjiFkEA8teZaojiFkocCgpzdHJpbmdfa2V5Eg4KDHN0cmluZyB2YWx1ZUoOCghib29sX2tleRICEAF6EhIOc3RhdHVzIG1lc3NhZ2UYARomaHR0cHM6Ly9vcGVudGVsZW1ldHJ5LmlvL3NjaGVtYXMvMS43LjAaKWh0dHA6Ly9leGFtcGxlLmNvbS9jdXN0b20tcmVzb3VyY2Utc2NoZW1h"
assert.Equal(t, expected, enc)
}
func TestSpansEmpty(t *testing.T) {
client := &InMemoryOtlpClient{}
err := client.UploadTraces(context.Background(), []*tracepb.ResourceSpans{})
assert.NoError(t, err)
assert.Len(t, client.spans, 0)
_, err = client.Spans()
assert.ErrorIs(t, err, errNoTraces)
}
func TestSpansNil(t *testing.T) {
client := &InMemoryOtlpClient{}
err := client.UploadTraces(context.Background(), nil)
assert.NoError(t, err)
assert.Len(t, client.spans, 0)
_, err = client.Spans()
assert.ErrorIs(t, err, errNoTraces)
}
func TestSpansTooManySpans(t *testing.T) {
client := &InMemoryOtlpClient{}
for i := 0; i < MaxTraceAmount+1; i++ {
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
err := client.UploadTraces(context.Background(), spans)
assert.NoError(t, err)
}
assert.Len(t, client.spans, MaxTraceAmount)
_, err := client.Spans()
assert.NoError(t, err)
}
func createResourceSpans(spans []*tracepb.Span) []*tracepb.ResourceSpans {
return []*tracepb.ResourceSpans{createResourceSpan(spans)}
}
func createResourceSpan(spans []*tracepb.Span) *tracepb.ResourceSpans {
return &tracepb.ResourceSpans{
Resource: otlpResource,
ScopeSpans: []*tracepb.ScopeSpans{
{
Scope: instrScope,
Spans: spans,
SchemaUrl: instrumentSchemaUrl,
},
},
InstrumentationLibrarySpans: nil,
SchemaUrl: resourceSchemaUrl,
}
}
func createOtlpSpan(tid []byte) *tracepb.Span {
return &tracepb.Span{
TraceId: tid,
SpanId: spanId,
TraceState: traceState.String(),
ParentSpanId: parentSpanId,
Name: "trace_name",
Kind: tracepb.Span_SPAN_KIND_INTERNAL,
StartTimeUnixNano: uint64(startTime.UnixNano()),
EndTimeUnixNano: uint64(endTime.UnixNano()),
Attributes: otlpKeyValues,
DroppedAttributesCount: 0,
Events: nil,
DroppedEventsCount: 0,
Links: nil,
DroppedLinksCount: 0,
Status: &tracepb.Status{
Message: "status message",
Code: tracepb.Status_STATUS_CODE_OK,
},
}
}
+195
View File
@@ -0,0 +1,195 @@
package tracing
import (
"context"
"errors"
"fmt"
"math"
"net/http"
"os"
"runtime"
"github.com/rs/zerolog"
otelContrib "go.opentelemetry.io/contrib/propagators/jaeger"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
"go.opentelemetry.io/otel/trace"
)
const (
service = "cloudflared"
tracerInstrumentName = "origin"
TracerContextName = "cf-trace-id"
TracerContextNameOverride = "uber-trace-id"
IntCloudflaredTracingHeader = "cf-int-cloudflared-tracing"
MaxErrorDescriptionLen = 100
traceHttpStatusCodeKey = "upstreamStatusCode"
)
var (
CanonicalCloudflaredTracingHeader = http.CanonicalHeaderKey(IntCloudflaredTracingHeader)
Http2TransportAttribute = trace.WithAttributes(transportAttributeKey.String("http2"))
QuicTransportAttribute = trace.WithAttributes(transportAttributeKey.String("quic"))
HostOSAttribute = semconv.HostTypeKey.String(runtime.GOOS)
HostArchAttribute = semconv.HostArchKey.String(runtime.GOARCH)
otelVersionAttribute attribute.KeyValue
hostnameAttribute attribute.KeyValue
cloudflaredVersionAttribute attribute.KeyValue
serviceAttribute = semconv.ServiceNameKey.String(service)
transportAttributeKey = attribute.Key("transport")
otelVersionAttributeKey = attribute.Key("jaeger.version")
errNoopTracerProvider = errors.New("noop tracer provider records no spans")
)
func init() {
// Register the jaeger propagator globally.
otel.SetTextMapPropagator(otelContrib.Jaeger{})
otelVersionAttribute = otelVersionAttributeKey.String(fmt.Sprintf("go-otel-%s", otel.Version()))
if hostname, err := os.Hostname(); err == nil {
hostnameAttribute = attribute.String("hostname", hostname)
}
}
func Init(version string) {
cloudflaredVersionAttribute = semconv.ProcessRuntimeVersionKey.String(version)
}
type TracedRequest struct {
*http.Request
trace.TracerProvider
exporter InMemoryClient
}
// NewTracedRequest creates a new tracer for the current request context.
func NewTracedRequest(req *http.Request) *TracedRequest {
ctx, exists := extractTrace(req)
if !exists {
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
}
mc := new(InMemoryOtlpClient)
exp, err := otlptrace.New(req.Context(), mc)
if err != nil {
return &TracedRequest{req, trace.NewNoopTracerProvider(), &NoopOtlpClient{}}
}
tp := tracesdk.NewTracerProvider(
// We want to dump to in-memory exporter immediately
tracesdk.WithSyncer(exp),
// Record information about this application in a Resource.
tracesdk.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
serviceAttribute,
otelVersionAttribute,
hostnameAttribute,
cloudflaredVersionAttribute,
HostOSAttribute,
HostArchAttribute,
)),
)
return &TracedRequest{req.WithContext(ctx), tp, mc}
}
func (cft *TracedRequest) Tracer() trace.Tracer {
return cft.TracerProvider.Tracer(tracerInstrumentName)
}
// Spans returns the spans as base64 encoded protobuf otlp traces.
func (cft *TracedRequest) AddSpans(headers http.Header, log *zerolog.Logger) {
if headers == nil {
log.Error().Msgf("provided headers map is nil")
return
}
enc, err := cft.exporter.Spans()
switch err {
case nil:
break
case errNoTraces:
log.Error().Err(err).Msgf("expected traces to be available")
return
case errNoopTracer:
return // noop tracer has no traces
default:
log.Error().Err(err)
return
}
// No need to add header if no traces
if enc == "" {
log.Error().Msgf("no traces provided and no error from exporter")
return
}
headers[CanonicalCloudflaredTracingHeader] = []string{enc}
}
// EndWithErrorStatus will set a status for the span and then end it.
func EndWithErrorStatus(span trace.Span, err error) {
endSpan(span, -1, codes.Error, err)
}
// EndWithStatusCode will set a status for the span and then end it.
func EndWithStatusCode(span trace.Span, statusCode int) {
endSpan(span, statusCode, codes.Ok, nil)
}
// EndWithErrorStatus will set a status for the span and then end it.
func endSpan(span trace.Span, upstreamStatusCode int, spanStatusCode codes.Code, err error) {
if span == nil {
return
}
if upstreamStatusCode > 0 {
span.SetAttributes(attribute.Int(traceHttpStatusCodeKey, upstreamStatusCode))
}
// add error to status buf cap description
errDescription := ""
if err != nil {
errDescription = err.Error()
l := int(math.Min(float64(len(errDescription)), MaxErrorDescriptionLen))
errDescription = errDescription[:l]
}
span.SetStatus(spanStatusCode, errDescription)
span.End()
}
// extractTrace attempts to check for a cf-trace-id from a request and return the
// trace context with the provided http.Request.
func extractTrace(req *http.Request) (context.Context, bool) {
// Only add tracing for requests with appropriately tagged headers
remoteTraces := req.Header.Values(TracerContextName)
if len(remoteTraces) <= 0 {
// Strip the cf-trace-id header
req.Header.Del(TracerContextName)
return nil, false
}
traceHeader := map[string]string{}
for _, t := range remoteTraces {
// Override the 'cf-trace-id' as 'uber-trace-id' so the jaeger propagator can extract it.
// Last entry wins if multiple provided
traceHeader[TracerContextNameOverride] = t
}
// Strip the cf-trace-id header
req.Header.Del(TracerContextName)
if traceHeader[TracerContextNameOverride] == "" {
return nil, false
}
remoteCtx := otel.GetTextMapPropagator().Extract(req.Context(), propagation.MapCarrier(traceHeader))
return remoteCtx, true
}
+69
View File
@@ -0,0 +1,69 @@
package tracing
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
tracesdk "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
tracepb "go.opentelemetry.io/proto/otlp/trace/v1"
)
func TestNewCfTracer(t *testing.T) {
req := httptest.NewRequest("GET", "http://localhost", nil)
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
tr := NewTracedRequest(req)
assert.NotNil(t, tr)
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter)
}
func TestNewCfTracerMultiple(t *testing.T) {
req := httptest.NewRequest("GET", "http://localhost", nil)
req.Header.Add(TracerContextName, "1241ce3ecdefc68854e8514e69ba42ca:b38f1bf5eae406f3:0:1")
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
tr := NewTracedRequest(req)
assert.NotNil(t, tr)
assert.IsType(t, tracesdk.NewTracerProvider(), tr.TracerProvider)
assert.IsType(t, &InMemoryOtlpClient{}, tr.exporter)
}
func TestNewCfTracerNilHeader(t *testing.T) {
req := httptest.NewRequest("GET", "http://localhost", nil)
req.Header[http.CanonicalHeaderKey(TracerContextName)] = nil
tr := NewTracedRequest(req)
assert.NotNil(t, tr)
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)
assert.IsType(t, &NoopOtlpClient{}, tr.exporter)
}
func TestNewCfTracerInvalidHeaders(t *testing.T) {
req := httptest.NewRequest("GET", "http://localhost", nil)
for _, test := range [][]string{nil, {""}} {
req.Header[http.CanonicalHeaderKey(TracerContextName)] = test
tr := NewTracedRequest(req)
assert.NotNil(t, tr)
assert.IsType(t, trace.NewNoopTracerProvider(), tr.TracerProvider)
assert.IsType(t, &NoopOtlpClient{}, tr.exporter)
}
}
func TestAddingSpansWithNilMap(t *testing.T) {
req := httptest.NewRequest("GET", "http://localhost", nil)
req.Header.Add(TracerContextName, "14cb070dde8e51fc5ae8514e69ba42ca:b38f1bf5eae406f3:0:1")
tr := NewTracedRequest(req)
exporter := tr.exporter.(*InMemoryOtlpClient)
// add fake spans
spans := createResourceSpans([]*tracepb.Span{createOtlpSpan(traceId)})
err := exporter.UploadTraces(context.Background(), spans)
assert.NoError(t, err)
// a panic shouldn't occur
tr.AddSpans(nil, &zerolog.Logger{})
}
+35 -2
View File
@@ -18,6 +18,7 @@ import (
type RegistrationServer interface {
RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error)
UnregisterConnection(ctx context.Context)
UpdateLocalConfiguration(ctx context.Context, config []byte) error
}
type RegistrationServer_PogsImpl struct {
@@ -88,6 +89,17 @@ func (i RegistrationServer_PogsImpl) UnregisterConnection(p tunnelrpc.Registrati
return nil
}
func (i RegistrationServer_PogsImpl) UpdateLocalConfiguration(c tunnelrpc.RegistrationServer_updateLocalConfiguration) error {
server.Ack(c.Options)
configBytes, err := c.Params.Config()
if err != nil {
return err
}
return i.impl.UpdateLocalConfiguration(c.Ctx, configBytes)
}
type RegistrationServer_PogsClient struct {
Client capnp.Client
Conn *rpc.Conn
@@ -163,6 +175,24 @@ func (c RegistrationServer_PogsClient) RegisterConnection(ctx context.Context, a
return nil, newRPCError("unknown result which %d", result.Which())
}
func (c RegistrationServer_PogsClient) SendLocalConfiguration(ctx context.Context, config []byte) error {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.UpdateLocalConfiguration(ctx, func(p tunnelrpc.RegistrationServer_updateLocalConfiguration_Params) error {
if err := p.SetConfig(config); err != nil {
return err
}
return nil
})
_, err := promise.Struct()
if err != nil {
return wrapRPCError(err)
}
return nil
}
func (c RegistrationServer_PogsClient) UnregisterConnection(ctx context.Context) error {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.UnregisterConnection(ctx, func(p tunnelrpc.RegistrationServer_unregisterConnection_Params) error {
@@ -212,8 +242,9 @@ func (a *TunnelAuth) UnmarshalCapnproto(s tunnelrpc.TunnelAuth) error {
}
type ConnectionDetails struct {
UUID uuid.UUID
Location string
UUID uuid.UUID
Location string
TunnelIsRemotelyManaged bool
}
func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
@@ -223,6 +254,7 @@ func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails
if err := s.SetLocationName(details.Location); err != nil {
return err
}
s.SetTunnelIsRemotelyManaged(details.TunnelIsRemotelyManaged)
return nil
}
@@ -240,6 +272,7 @@ func (details *ConnectionDetails) UnmarshalCapnproto(s tunnelrpc.ConnectionDetai
if err != nil {
return err
}
details.TunnelIsRemotelyManaged = s.TunnelIsRemotelyManaged()
return err
}
+5
View File
@@ -129,6 +129,11 @@ type testConnectionRegistrationServer struct {
err error
}
func (t *testConnectionRegistrationServer) UpdateLocalConfiguration(ctx context.Context, config []byte) error {
// do nothing at this point
return nil
}
func (t *testConnectionRegistrationServer) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
if auth.AccountTag != testAccountTag {
panic("bad account tag: " + auth.AccountTag)
+3
View File
@@ -121,6 +121,8 @@ struct ConnectionDetails {
uuid @0 :Data;
# airport code of the colo where this connection landed
locationName @1 :Text;
# tells if the tunnel is remotely managed
tunnelIsRemotelyManaged @2: Bool;
}
struct TunnelAuth {
@@ -131,6 +133,7 @@ struct TunnelAuth {
interface RegistrationServer {
registerConnection @0 (auth :TunnelAuth, tunnelId :Data, connIndex :UInt8, options :ConnectionOptions) -> (result :ConnectionResponse);
unregisterConnection @1 () -> ();
updateLocalConfiguration @2 (config :Data) -> ();
}
interface TunnelServer extends (RegistrationServer) {
+448 -223
View File
@@ -1410,12 +1410,12 @@ type ConnectionDetails struct{ capnp.Struct }
const ConnectionDetails_TypeID = 0xb5f39f082b9ac18a
func NewConnectionDetails(s *capnp.Segment) (ConnectionDetails, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
return ConnectionDetails{st}, err
}
func NewRootConnectionDetails(s *capnp.Segment) (ConnectionDetails, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2})
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2})
return ConnectionDetails{st}, err
}
@@ -1462,12 +1462,20 @@ func (s ConnectionDetails) SetLocationName(v string) error {
return s.Struct.SetText(1, v)
}
func (s ConnectionDetails) TunnelIsRemotelyManaged() bool {
return s.Struct.Bit(0)
}
func (s ConnectionDetails) SetTunnelIsRemotelyManaged(v bool) {
s.Struct.SetBit(0, v)
}
// ConnectionDetails_List is a list of ConnectionDetails.
type ConnectionDetails_List struct{ capnp.List }
// NewConnectionDetails creates a new list of ConnectionDetails.
func NewConnectionDetails_List(s *capnp.Segment, sz int32) (ConnectionDetails_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 2}, sz)
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 8, PointerCount: 2}, sz)
return ConnectionDetails_List{l}, err
}
@@ -1621,11 +1629,35 @@ func (c RegistrationServer) UnregisterConnection(ctx context.Context, params fun
}
return RegistrationServer_unregisterConnection_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c RegistrationServer) UpdateLocalConfiguration(ctx context.Context, params func(RegistrationServer_updateLocalConfiguration_Params) error, opts ...capnp.CallOption) RegistrationServer_updateLocalConfiguration_Results_Promise {
if c.Client == nil {
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0xf71695ec7fe85497,
MethodID: 2,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
MethodName: "updateLocalConfiguration",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
call.ParamsFunc = func(s capnp.Struct) error {
return params(RegistrationServer_updateLocalConfiguration_Params{Struct: s})
}
}
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
type RegistrationServer_Server interface {
RegisterConnection(RegistrationServer_registerConnection) error
UnregisterConnection(RegistrationServer_unregisterConnection) error
UpdateLocalConfiguration(RegistrationServer_updateLocalConfiguration) error
}
func RegistrationServer_ServerToClient(s RegistrationServer_Server) RegistrationServer {
@@ -1635,7 +1667,7 @@ func RegistrationServer_ServerToClient(s RegistrationServer_Server) Registration
func RegistrationServer_Methods(methods []server.Method, s RegistrationServer_Server) []server.Method {
if cap(methods) == 0 {
methods = make([]server.Method, 0, 2)
methods = make([]server.Method, 0, 3)
}
methods = append(methods, server.Method{
@@ -1666,6 +1698,20 @@ func RegistrationServer_Methods(methods []server.Method, s RegistrationServer_Se
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
})
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0xf71695ec7fe85497,
MethodID: 2,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
MethodName: "updateLocalConfiguration",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := RegistrationServer_updateLocalConfiguration{c, opts, RegistrationServer_updateLocalConfiguration_Params{Struct: p}, RegistrationServer_updateLocalConfiguration_Results{Struct: r}}
return s.UpdateLocalConfiguration(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
})
return methods
}
@@ -1685,6 +1731,14 @@ type RegistrationServer_unregisterConnection struct {
Results RegistrationServer_unregisterConnection_Results
}
// RegistrationServer_updateLocalConfiguration holds the arguments for a server call to RegistrationServer.updateLocalConfiguration.
type RegistrationServer_updateLocalConfiguration struct {
Ctx context.Context
Options capnp.CallOptions
Params RegistrationServer_updateLocalConfiguration_Params
Results RegistrationServer_updateLocalConfiguration_Results
}
type RegistrationServer_registerConnection_Params struct{ capnp.Struct }
// RegistrationServer_registerConnection_Params_TypeID is the unique identifier for the type RegistrationServer_registerConnection_Params.
@@ -2014,6 +2068,130 @@ func (p RegistrationServer_unregisterConnection_Results_Promise) Struct() (Regis
return RegistrationServer_unregisterConnection_Results{s}, err
}
type RegistrationServer_updateLocalConfiguration_Params struct{ capnp.Struct }
// RegistrationServer_updateLocalConfiguration_Params_TypeID is the unique identifier for the type RegistrationServer_updateLocalConfiguration_Params.
const RegistrationServer_updateLocalConfiguration_Params_TypeID = 0xc5d6e311876a3604
func NewRegistrationServer_updateLocalConfiguration_Params(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Params, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return RegistrationServer_updateLocalConfiguration_Params{st}, err
}
func NewRootRegistrationServer_updateLocalConfiguration_Params(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Params, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1})
return RegistrationServer_updateLocalConfiguration_Params{st}, err
}
func ReadRootRegistrationServer_updateLocalConfiguration_Params(msg *capnp.Message) (RegistrationServer_updateLocalConfiguration_Params, error) {
root, err := msg.RootPtr()
return RegistrationServer_updateLocalConfiguration_Params{root.Struct()}, err
}
func (s RegistrationServer_updateLocalConfiguration_Params) String() string {
str, _ := text.Marshal(0xc5d6e311876a3604, s.Struct)
return str
}
func (s RegistrationServer_updateLocalConfiguration_Params) Config() ([]byte, error) {
p, err := s.Struct.Ptr(0)
return []byte(p.Data()), err
}
func (s RegistrationServer_updateLocalConfiguration_Params) HasConfig() bool {
p, err := s.Struct.Ptr(0)
return p.IsValid() || err != nil
}
func (s RegistrationServer_updateLocalConfiguration_Params) SetConfig(v []byte) error {
return s.Struct.SetData(0, v)
}
// RegistrationServer_updateLocalConfiguration_Params_List is a list of RegistrationServer_updateLocalConfiguration_Params.
type RegistrationServer_updateLocalConfiguration_Params_List struct{ capnp.List }
// NewRegistrationServer_updateLocalConfiguration_Params creates a new list of RegistrationServer_updateLocalConfiguration_Params.
func NewRegistrationServer_updateLocalConfiguration_Params_List(s *capnp.Segment, sz int32) (RegistrationServer_updateLocalConfiguration_Params_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 1}, sz)
return RegistrationServer_updateLocalConfiguration_Params_List{l}, err
}
func (s RegistrationServer_updateLocalConfiguration_Params_List) At(i int) RegistrationServer_updateLocalConfiguration_Params {
return RegistrationServer_updateLocalConfiguration_Params{s.List.Struct(i)}
}
func (s RegistrationServer_updateLocalConfiguration_Params_List) Set(i int, v RegistrationServer_updateLocalConfiguration_Params) error {
return s.List.SetStruct(i, v.Struct)
}
func (s RegistrationServer_updateLocalConfiguration_Params_List) String() string {
str, _ := text.MarshalList(0xc5d6e311876a3604, s.List)
return str
}
// RegistrationServer_updateLocalConfiguration_Params_Promise is a wrapper for a RegistrationServer_updateLocalConfiguration_Params promised by a client call.
type RegistrationServer_updateLocalConfiguration_Params_Promise struct{ *capnp.Pipeline }
func (p RegistrationServer_updateLocalConfiguration_Params_Promise) Struct() (RegistrationServer_updateLocalConfiguration_Params, error) {
s, err := p.Pipeline.Struct()
return RegistrationServer_updateLocalConfiguration_Params{s}, err
}
type RegistrationServer_updateLocalConfiguration_Results struct{ capnp.Struct }
// RegistrationServer_updateLocalConfiguration_Results_TypeID is the unique identifier for the type RegistrationServer_updateLocalConfiguration_Results.
const RegistrationServer_updateLocalConfiguration_Results_TypeID = 0xe5ceae5d6897d7be
func NewRegistrationServer_updateLocalConfiguration_Results(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Results, error) {
st, err := capnp.NewStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0})
return RegistrationServer_updateLocalConfiguration_Results{st}, err
}
func NewRootRegistrationServer_updateLocalConfiguration_Results(s *capnp.Segment) (RegistrationServer_updateLocalConfiguration_Results, error) {
st, err := capnp.NewRootStruct(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0})
return RegistrationServer_updateLocalConfiguration_Results{st}, err
}
func ReadRootRegistrationServer_updateLocalConfiguration_Results(msg *capnp.Message) (RegistrationServer_updateLocalConfiguration_Results, error) {
root, err := msg.RootPtr()
return RegistrationServer_updateLocalConfiguration_Results{root.Struct()}, err
}
func (s RegistrationServer_updateLocalConfiguration_Results) String() string {
str, _ := text.Marshal(0xe5ceae5d6897d7be, s.Struct)
return str
}
// RegistrationServer_updateLocalConfiguration_Results_List is a list of RegistrationServer_updateLocalConfiguration_Results.
type RegistrationServer_updateLocalConfiguration_Results_List struct{ capnp.List }
// NewRegistrationServer_updateLocalConfiguration_Results creates a new list of RegistrationServer_updateLocalConfiguration_Results.
func NewRegistrationServer_updateLocalConfiguration_Results_List(s *capnp.Segment, sz int32) (RegistrationServer_updateLocalConfiguration_Results_List, error) {
l, err := capnp.NewCompositeList(s, capnp.ObjectSize{DataSize: 0, PointerCount: 0}, sz)
return RegistrationServer_updateLocalConfiguration_Results_List{l}, err
}
func (s RegistrationServer_updateLocalConfiguration_Results_List) At(i int) RegistrationServer_updateLocalConfiguration_Results {
return RegistrationServer_updateLocalConfiguration_Results{s.List.Struct(i)}
}
func (s RegistrationServer_updateLocalConfiguration_Results_List) Set(i int, v RegistrationServer_updateLocalConfiguration_Results) error {
return s.List.SetStruct(i, v.Struct)
}
func (s RegistrationServer_updateLocalConfiguration_Results_List) String() string {
str, _ := text.MarshalList(0xe5ceae5d6897d7be, s.List)
return str
}
// RegistrationServer_updateLocalConfiguration_Results_Promise is a wrapper for a RegistrationServer_updateLocalConfiguration_Results promised by a client call.
type RegistrationServer_updateLocalConfiguration_Results_Promise struct{ *capnp.Pipeline }
func (p RegistrationServer_updateLocalConfiguration_Results_Promise) Struct() (RegistrationServer_updateLocalConfiguration_Results, error) {
s, err := p.Pipeline.Struct()
return RegistrationServer_updateLocalConfiguration_Results{s}, err
}
type TunnelServer struct{ Client capnp.Client }
// TunnelServer_TypeID is the unique identifier for the type TunnelServer.
@@ -2181,6 +2359,28 @@ func (c TunnelServer) UnregisterConnection(ctx context.Context, params func(Regi
}
return RegistrationServer_unregisterConnection_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
func (c TunnelServer) UpdateLocalConfiguration(ctx context.Context, params func(RegistrationServer_updateLocalConfiguration_Params) error, opts ...capnp.CallOption) RegistrationServer_updateLocalConfiguration_Results_Promise {
if c.Client == nil {
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(capnp.ErrorAnswer(capnp.ErrNullClient))}
}
call := &capnp.Call{
Ctx: ctx,
Method: capnp.Method{
InterfaceID: 0xf71695ec7fe85497,
MethodID: 2,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
MethodName: "updateLocalConfiguration",
},
Options: capnp.NewCallOptions(opts),
}
if params != nil {
call.ParamsSize = capnp.ObjectSize{DataSize: 0, PointerCount: 1}
call.ParamsFunc = func(s capnp.Struct) error {
return params(RegistrationServer_updateLocalConfiguration_Params{Struct: s})
}
}
return RegistrationServer_updateLocalConfiguration_Results_Promise{Pipeline: capnp.NewPipeline(c.Client.Call(call))}
}
type TunnelServer_Server interface {
RegisterTunnel(TunnelServer_registerTunnel) error
@@ -2198,6 +2398,8 @@ type TunnelServer_Server interface {
RegisterConnection(RegistrationServer_registerConnection) error
UnregisterConnection(RegistrationServer_unregisterConnection) error
UpdateLocalConfiguration(RegistrationServer_updateLocalConfiguration) error
}
func TunnelServer_ServerToClient(s TunnelServer_Server) TunnelServer {
@@ -2207,7 +2409,7 @@ func TunnelServer_ServerToClient(s TunnelServer_Server) TunnelServer {
func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []server.Method {
if cap(methods) == 0 {
methods = make([]server.Method, 0, 8)
methods = make([]server.Method, 0, 9)
}
methods = append(methods, server.Method{
@@ -2322,6 +2524,20 @@ func TunnelServer_Methods(methods []server.Method, s TunnelServer_Server) []serv
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
})
methods = append(methods, server.Method{
Method: capnp.Method{
InterfaceID: 0xf71695ec7fe85497,
MethodID: 2,
InterfaceName: "tunnelrpc/tunnelrpc.capnp:RegistrationServer",
MethodName: "updateLocalConfiguration",
},
Impl: func(c context.Context, opts capnp.CallOptions, p, r capnp.Struct) error {
call := RegistrationServer_updateLocalConfiguration{c, opts, RegistrationServer_updateLocalConfiguration_Params{Struct: p}, RegistrationServer_updateLocalConfiguration_Results{Struct: r}}
return s.UpdateLocalConfiguration(call)
},
ResultsSize: capnp.ObjectSize{DataSize: 0, PointerCount: 0},
})
return methods
}
@@ -4317,224 +4533,231 @@ func CloudflaredServer_Methods(methods []server.Method, s CloudflaredServer_Serv
return methods
}
const schema_db8274f9144abc7e = "x\xda\xccZ{t\x14\xe7u\xbfwfW#\x81V" +
"\xab\xf1\xac\xd1\x03T\xb5:P\x179\xd8\x06Jk\xab" +
"9\xd1\xc3\x12\xb1d\x03\x9a]\x94\xe3cC\x8eG\xbb" +
"\x9f\xa4Qwg\x96\x99Y\x19\x11\x130\x01c\xfb\xb8" +
"\x8eq\xc0\xb1Ih0.\xed\x01\xdb\xad\x89\xdd\xa6\xee" +
"1\xa7\xa6\xcd\xabq\xc0&\x87\xf4\x90@\x9a&\x84>" +
"8\xb8\xae14\x876\xf1\xf4\xdc\x99\x9d\x87v\x17\x09" +
"\x8c\xff\xc8\x7f\xab;\xdf\xe3\xde\xdf\xf7\xbb\x8f\xef~\xba" +
"\xed\xe6\x9a.nq\xf4\xed:\x00\xf9\xa5h\x95\xcd\xda" +
"\x7f\xb0a\xef\x82\x7f\xdc\x02r3\xa2\xfd\xf97\x06\x12" +
"\x97\xad-\xa7 \xca\x0b\x00KW\x08\x1bPR\x04\x01" +
"@Z+\xfc;\xa0\xfd\xc8\x9cW\xbe\xb6\xbfo\xe7\x17" +
"@l\xe6\x83\xc1\x80K\xbb\xab\x07P\x1a\xaa\xa6\x91r" +
"\xf5v\xe9\x10\xfd\xb2\xef\x16o\xbd?\xf1\xce1\x1a\x1d" +
"^:BK?W\xdd\x8e\xd2\x01g\xc2\xfejZ\xfa" +
"\x93\xb9\xb7\xf7\xfd\xc1\xae\xb7\xb6\x82\xd8\xccMYzG" +
"\xcd\x06\x94\xf6\xd7\xd0\xc8\xe7kV\x01\xda\x1f\xecl|" +
"\xf1\xf9c\xdf\xdd\x06\xe2M\x08EM_\xaf\xf91\x02" +
"JGk\xfe\x0a\xd0>z\xe9\xfe\x8b\xaf}{\xd9#" +
" .\xa4\x01H\x03r\xb3\xda8@i\xdb\xacN@" +
"\xfb\xdc\xf9\xff\xdb\xfe\xb9\x85+\x9f\x02y!r\x00Q" +
"\x8eF\xec\x9f\xd5L#\x0e\xcf\"m:\x97\x1f}\xbd" +
"y\xe93;KTw\x06\xee\x99\xdd\x8e\xd2\xcb\xb3I" +
"\xa1\x03\xb3\x1f\x04\xb4?\xf5\x87\xaf>\xd9\xfb\xcc\xe6]" +
" \xde\xea\xef\x17\xab\xbd\x8fV[XK\xfb\xfdO\xdd" +
"W\x8e\x15\xee\xfc\xc63E\x85\x9cU\xfak\xdbi\x80" +
"RK+\xb4M,x\xe0\x1f\xbe\xf5\xea\x97A^\x84" +
"h\x9f\x1e\xbe\xf9\x87\xfc\x9e\x83\xa7`\x08\x05\xd2o\xe9" +
"\x91\xda}d\xddqg\xec\xdb\x9fx\xe3\xef\x9ezu" +
"\xfbW@\xbe\x09\x11\xc0AsY\xec\x7fi@\x7f\x8c" +
"v\xdby\xf2\xf0\xca\xdc\x8e\xdd\xfb\\|\x9c\xef\xebb" +
"\x1c\x07\x11{k\xff/sC/\xa4^(\"\x17\xa5" +
"O,v\x01\x01\x97N\xc6Z\x11\xd0^\xf6\xe3\xb3\xab" +
"V|}\xe4/Bsw\xd5m\xa0\xb9\xdbG.\x1c" +
"\xa9O\xe6^,A\xc41vG\xddA\x94\x0e\xd49" +
"\x87YG*\xbc\xfc[w\xd7\xac?\xbb\xfc\x15\x10\x17" +
"y\xcb|\xab.I\xcbL|\xef\x85\xdf]\xf0\xbd\x07" +
"\x0f\x81|+\xfa`\x1d\xa1o(\xfd\xa4\x8e\xec\x8b\x9c" +
"Zp\xf0\xf0O\x9f|\xad\x8ccw\xc47\xa0\xb4\"" +
"N\xbb\xf4\xc7?-M\xd2/;\xb2\x96\xffPy\xf6" +
"\xef_+\xe5\xaf\x83\xb1\x12\x1fF\xa9\x10w\x10\x88;" +
"\xf6=~d\xf7\xcd\xd5_\xfb\xe0\xaf+\x9d\xebs\xf5" +
"\xc3(\xbd\\\xef\x9ck=irc?\x9e~sq" +
"\xe4\x1ba\xa2\xd5\x88\xe7\x08\xe9\x16\x91\x88\xd6\xf2nO" +
"L{o\xcb\x9b%\xab9\x03\x0f\x8b\x03(\x1d\x17i" +
"\xb5\xa3\xce\xe0\x81\xfb\xbf\xf4t\xf4\xec\x97\xbeC\x9a\x86" +
"\x18\x1e%\x1fX\xaa\xde`\xa0\xb4\xf1\x06\xfa9yC" +
"\x03\x0fh7\xbf\xf2G\x7f\xd9\x93\xf9\xd1[\x154\x95" +
".\xdfxA\x8a\xce\xa1_8\x87\x14=\xb3\xe8\xd0\xe7" +
"\xfe\xf3O\x8e\x9f(*\xea`\xbav\x8eC\x89us" +
"\xe8<.\xaf\xd9{\xb7j\xdf{\xaa\x14%\xf7\xf4\xe6" +
"|\x1d\xa5\x03\xcer\xfb\x9d\xe5|\xfeU\x1a]\xd30" +
"\x8eRK\x03\x8dnj\xa0\xb5\xb9\xb3J\xd3\xe6\x7f\xfe" +
"\xd4\xe9\x10eZ\x1a~\x8e\x10\xb1W~\xe6\xfe\xf1\x9a" +
"\x8dg\xce\x84\xd5\x12\x1b\x1c\xfc\x168S\xff\xeb\xcf\xcf" +
"}\xf1|.\xf3o\x0e\xed=\x84\xfb\x1a:\x88\x0ck" +
"\x1b\xc8\x0f\x1bZc}m'\x07\xcf\xb9Dr\x97\xb8" +
"\xa3\xb1\x87\x06\xc8\x8d\xb4\xc4\xb2\x07\xba\xd9\x9a\xdb\xef=" +
"W\xc6\x96u\x8d\x1d(=\xdcH\x1366nGi" +
"WS\x03\x80=\xf17;\xee}\xf1\x9b+/\xb8\x9e" +
"\xe8(\xbb\xadi\x09\x11\xf3\xc9\xcf\xf7\xae\xba\xa3\xed\xc8" +
"\x85\xb0\xb2\x1b\x9b\xc87\xa4\x1dM\xb4\xd3\xc8\xed\xe7?" +
"\xbd\xe0\xc9o_\xa8\xe4\x00\x87\x9a\xdaQ:\xd2D\xa0" +
"\x1c\xa6\xc1\xef-\xff\xd3\x13\xcd\xf1\xe6\x8b%\x00V\xd1" +
"\xd8\x9f5\x8d\xa3t\x89\xc6.}\xbf\xe9;D\xca\xe7" +
"\xffl\xdf\xbf\\>v\xd7\xa52\x1b\xce\xce\x1dF\xe9" +
"\xf2\\Z\xf6\xd2\\A\xba4\xf7&\x00\xfb\x91S\x9f" +
"]\xff\x83/|p\xa9\x94G\x8e\"\xef\xceM\xa2\x84" +
"\xf3h\xc6\xaf\xe7\x12\xeb\xbe\xbc\xfa?6\x9d\xdf5\xe7" +
"\x97ek\xef\x997\x8e\xd2!g\xe4\xcb\xf3\xb6K\xb1" +
"\x16\xf2\xa6w\x84\x17\x16\xf7nz\xebr\xc8o/\xcd" +
"\x1b x\x9e\x11\xbezf\xf3O?\xfb\xab0<\xef" +
"\xcf\xfb9\xc1\x13m!x\x1ez\xef\xb9\xbb\xbe\xb8\xe6" +
"\xa5\x0fC4X\xd0\xb2\x85\xa6Z\x05McY#\x1f" +
"I\xdf\xea\xfdL\xdf\x92V\xf2Z\xbe\xa3\xbb`\x8d1" +
"\xcdR\xd3\x8a\xc5\x92\xac\xd3\xcc\xeb\x9a\xc9\x06\x11\xe5z" +
">\x02\x10A\x00Q\x19\x07\x90\x1f\xe0Q\xcer(\"" +
"&\x88(\xa2J\xc21\x1ee\x8bC\x91\xe3\x12\x14%" +
"\xc5um\x00r\x96Gy=\x87\xc8'\x90\x07\x10\x0b" +
"O\x03\xc8\xeby\x94\xb7rh\xe7\x99\x91S4\xa6A" +
"\xdc\xea3\x0c\xac\x05\x0ek\x01m\x83Y\xc6\xa42\x9c" +
"\x858\x0b\x89\x85\xf1\x07-\x8c\x01\x871@{L/" +
"\x18\xe6\x90f\xa1\x9aM\xb2\x11\x83\x998\x86U\xc0a" +
"\x15\xe0t\xe6\xa5\x98i\xaa\xba\xb6B\xd1\x94Qf\x00" +
"\x90e\xd5|\x14\xc0\xcf@\xe8\xe5*q\xf1n\xe0\xc4" +
"E\x02\x06\xd9\x02=\xb2\x8a\xbfs\x108\xb1E\xb0\x0d" +
"6\xaa\x9a\x163p(\x93w\xd6\xe6u\xad\x0b\xed\x82" +
"\xe6~@f\xb8\x1f\xe2\xb4k\x17\x0eb\xa0\x1d_\xae" +
"\xdd\x9dY\x95iV\xbc_\x1b\xd1K \x1f\xa8\x04\xf9" +
"@\x11\xf2\xad!\xc8\x1f\xee\x01\x90\x1f\xe2Q~\x94C" +
"\x91/b\xbe\xad\x1d@\xde\xcc\xa3\xfc\x04\x87v\xda\xd9" +
"\xa4?\x03\x00>\x9a#L\xb1\x0a\x063IV\x078" +
"\xc8\xa3\x03z\x1d\xe0\xa6\x09f\x90\xee\xde!\xc4\x15#" +
"=\xe6\x1f\xd44H\xf7\xadWMK\xd5FW;\xf2" +
"\xceA=\xab\xa6'\xc9\xaaZG\xcf\x96\x0e\x00D\xf1" +
"\xc6\xfb\x00\x90\x13\xc5\x1e\x80NuT\xd3\x0dfgT" +
"3\xadk\x1a\x03>mm\x1aV\xb2\x8a\x96f\xfeF" +
"U\xe5\x1b\xb9\x1b\xa4\x981\xc1\x8c[\x94\x10}\xe7\x0f" +
"*\x86\xc2\xe7L\xb9\xd6\xc7\xb1\xef>\x00\xb9\x97Gy" +
"0\x84\xe3\x0a\xc2\xf1\x1e\x1e\xe5{C8\x0e\x11\x8e\x83" +
"<\xcak8\xb4uC\x1dU\xb5;\x19\xf0F\x98\x81" +
"\xa6\xa5)9F\x98\x15\xf1\xd8\xa4\xe7-U\xd7L\xac" +
"\x0fr\x0b \xd6\x87\x90\x12f\xe2\xe4-\x1e\xa5<F" +
"\xe9\xda\xfc$3\x0bB\xd62\xe5\x88oI\xac\x03@" +
"\xae\xe6QNp\xd8i0\xb3\x90\xb5\xb0>(\x09>" +
"\x8e]=\xf8B4LV\xa2\xe1\x12\x009\xc3\xa3\x9c" +
"\xe7\x10\x8b\xe8\xe5zB\xd1\x80G\x97\x85\xebv\x03\xc8" +
"\x16\x8f\xf2f\x0em\xd3\xdd\xa4\x1f0\xe3!\xda\x9a1" +
"\xad\xfe\xbc\xf7\xd7\xa6\x8ci\x0d\xea\x86\x85\x02p(\x00" +
"\xf1V7Y\xf7\x08\xf9T\x7f&\xcb\xeeRy\xcd\xc2" +
"(p\x18\x85i\x9d\xca\xe5G\x9c\x02\x9b\xeb\xed\x9e5" +
"\x0b\x89\x0c\xbf\xc7\xa3\xfc\xfb!k\x16S\x1c\xbb\x8dG" +
"\xf9\x93\x1c\xdaJ:\xad\x174k5\xf0\xcah\x09\xe7" +
"S\x0c\xe2i\x83\x05t\xf0\xb6\xad\xae\xe0\xd6\xba6\xa2" +
"\x8e\x16\x0c\xc5\x0a\x01^\xc8g\x14\x8bM\xf9\xe4\x9cs" +
"\x96\xbf\x8as\xf6\xab\x87k>g/2\x95\x9ct\xdc" +
"Prf\x18\x9bd%l\xe8T?\xc1\xa3|{\xe5" +
"\x03\xdc\x94c\xa6\xa9\x8c\xb2\xb2\xf0\x10\xad\x88\x89\xc6\xd2" +
"du\x92\xb9I\xe6\x16\x83\x99B!k\x91\x16\xb5\xb6" +
"\xed\xaaA\xdc\x9a\xcf\xa3|\x1b\x871\xfc\xd0v\xf5X" +
"\xf4tpF\xad\xcc0t\x03\xeb\x83$\\\x84$]" +
"\xdc\x00u\xad\x97Y\x8a\x9aErK\xbf\xda,\x01n" +
"\xa6\xb8\x12\xc0\xe6\x8a\xe7w\x92w\xe4\xa6\x9c\x14\xd1\xbb" +
"\x9eGy\x1e\x87\xf6\xa8\xa1\xa4\xd9 3P\xd53+" +
"\x15MO\xf1,]F\xd6\xbak\xdd\xd4\xe1\x87e\x82" +
"?k\xfa\xf9\x06+\x82P\x9c>\xd8\xea\xea\x9c\xf0u" +
"\xde\xd8\x16$c\xff\x98\x1f\x1e\x0e\xb2\x85\x1f\x0f\x1f#" +
"gy\x94Gyg(\xaf\xec\xa0\xc8\xf9\x14\x8f\xf2W" +
"9\x14#\x91\x04F\x00\xc4\xe7\x88%;y\x94\xf7r" +
"SS6\x9b`\x9a\xd5\xab\x8e\x82\xc0\xcc@J*\xf6" +
"\xaa\xa3\x0cx\xf3zck\xf5\x0cx\xe8\xc3\xa6\x9ee" +
"\x16\xebe\xe9\xacB.7\xc1\xdc\xefE2z\x87:" +
"\x1do\x93e\xdeC\xfc\x8d{UR\x88\x0em\x81\xe3" +
"\x0a,T\xdcL\xa3\xad\xbb\xb8\x1b\x0c\xca8\x10xL" +
"\x91\x07h~,A\xc7\xb1\x19\xa78\x7fO\xe0u\x1e" +
")\x16u\x04\x01\xc1\xaf\x09\"\xc0a\x04\xb03\xed," +
"X\x16\x0a#3i\xd5\xe9\xaa\xe5\x02GE\x98w\x17" +
"E\xef\x02/\x8a\xfb\x80\x13c\x82\xedi\x8e\xde|\xa1" +
"\xac\xa0\x8aL\x17eV\xe5-U\xd05\x93\xf6\x0a\xf1" +
"\xbf\xa3\x12\xff\x8d\x80\xff^B{lK\x98\xfe\xc5\x84" +
"\xb6cw\xc0t1\xc2\xb9\xf4\xdf\xb3\x0f@\xde\xcb\xa3" +
"\xfc\x12\x87\x9dn\xad\x85\xf5A\xe3\xa5HY\xb7\xa2\xb8" +
"G\x87\xd6\xb4\x92\x0d\x92\x9em\xb0|VI\xb3>," +
"VO\x80\x08\x1c\xa2\xe3'\xb9\xbc\xc1L\x13U]\x93" +
"\x0bJV\xe5\xadI\xbf\xe2\xd5\x0a\xb9A\x83M\xa8\xa8" +
"\x17\xccn\xcbb9!o\x99WS\x0f\x07\x00Q\x90" +
"\x14\xd4\xacY\x92#\xdb\x03*\xf8\x00-\x1a\x0f\xf2@" +
"\xbcPP\xfd\x04`g\xf5\xb4s\xb2\x10_\xa9\xe4\xca" +
"\xf3@\xd5\x8c\x01kJ\xb8\xf3\xd2\xd2oR\xfd6\xfd" +
"\x95\x89Lw\xee\x14!\x95)\x0et\xf1(\xdf\x13R" +
"\xb9\x7fI\xc8\x0eO\xe5\x15\xc3\x81\x1d\xc2\x1f\xb3IO" +
"\xabV\x96\xa3\xf4\xe5\x81Y4\xa6\x1b\x84\xbb\x831\xd3" +
"\xe9\x17\x8e*\xab\xf2\xad\x8e\x85\xa4\xe3\xed\x9e\x8e\xd2$" +
"\x0e\x00\xa4\xd6#\x8f\xa9\xad\x18\xa8)=\x8c=\x00\xa9" +
"\x87H\xfe(\x06\x9aJ\xdb\xb0\x19 \xb5\x99\xe4O\xa0" +
"\x7f\xb5\x93\x1e\xc3\x83\x00\xa9'H\xfc,\x0d\x8f\xf0\x8e" +
"KH\xbb\x9c\xe5w\x92|/\xc9\xa3\x91\x04F\x01\xa4" +
"=\xd8\x0e\x90z\x96\xe4\xaf\x91\xbc\x8aK`\x15\x80t" +
"\x08\xc7\x01R\xaf\x90\xfc\x0d\x92\x0b\xd1\x04\xddn\xa5\xd7" +
"\xd1\x00H\xfd-\xc9\xbfI\xf2\xea\xc6\x04V\x03HG" +
"\x1c\xf9\x9b$\xff>\xc9k\x9a\x12X\x03 \xfd\x13n" +
"\x01H}\x97\xe4'H>\x0b\x138\x0b@:\x8e\xbb" +
"\x01R'H\xfe\xaf$\x9f]\x95\xc0\xd9\x00\xd2O\x1c" +
"}N\x92\xfc\x17$\xaf\x8d$\xb0\x16@\xfa\x19\xee\x03" +
"H\xfd\x82\xe4\xffM\xf2\x98\x90\xc0\x18\x80\xf4\xaec\xd7" +
"y\x92Ws%7+\x8fQ%\xd7'^7\xfd#" +
"cE\x1fG\x97\xee\x83z\x9c\xaeH\x18\x0f\x1a\xaf\x80" +
"\x18\x07\xb4\xf3\xba\x9e]9\x95\xa9qK\x195\xbd\xab" +
"Z}\xd0\x9a\x02$\xa1_\xfc@\\\xd7\xfa3~ " +
"(\x8d:\x9e&\xaa\xd9]\xb0\xf4B\x1eZ)\xc8f" +
"\xfc\x98c\x14\xb4\xe5\x86\x9e[\x8d\xcc\xc8\xa9\x9a\x92\x9d" +
"!\x1a\xd5\x00\x875P\x0c\x09\xde\xda\xd3\x87\xa6+_" +
"<}Fs\xa5\x8cn\xcdw\xacVF\xaf&N-" +
"\x09rV\\\x0b\x05\xa4\xd6\x09%[\xf8(\xe1ij" +
"=\x95\xect\xeb\xb1\x99\xcau\xaf\xf7T\x12J*T" +
"\x17C\xe5\xf99\xc9\xccV\xbf\x09\x132\xf8`\x10\x83" +
"={\x97\xb5\x85\xee.Y\xc5b\xa6\xd5\x9d\xc7|V" +
"e\x99\xcf0#\x1eN\xd9\x15+\x92\xc8Le\xfa\xd4" +
"2\x07C]r2\x9c+\x1a|\xd5x\x8e2\xcb\xfd" +
"\xd5\xaf\x8d\xe8T\x87\x08\xe1\xe2\xeb\xdaf'\x99\x19\xbf" +
"\x9a\xb3\x08\x9a\x863_\x9d*\x94c\x15\x8a1\xef&" +
"\x10\xba&\x13\x19\xd7\xf0(\x8f\x85\xc8\xc8\x06*\\\x93" +
"\x93A\x7fL\xe4\xb9b\x83\x8c2W\x9eG\xf9!\x0e" +
"\xe3J\xc1\x1a\xc3\xfa\xe0\xf1c\x8a\xd2S{8\xc4\xcd" +
"~-\xc3\x00\xd7{\xee\x15\xcag~W~\xe6\x9a\xf9" +
"\xea\xcc\xf6\xee\"3\x02\xee\xf7\xaeKv\xbe\xe2U\xbd" +
"\xd3\xdd\x94x\xd6\xe8T\x85^\xd7\x1f\xbd\x8e\xb0xh" +
"\x03p\xe2\x01\x01\x83^7z\xadmq\x8f\x01\x9c\xb8" +
"K@\xce\x7f\x97A\xef\xfdE|\xecq\xe0\xc4m\x02" +
"\xf2\xfe\xb3\x0az]\xd2\xc5\x93\xb3\x108q\xa3\x80\x11" +
"\xff=\x0b\xbd\x1e\xab\xb8n\x1c8Q\x150\xea\xbf\xd8" +
"\xa0\xd7\xe2\x17\xd7n\x01N\x1c\x0az\x81\xd0\xe9\xda\xd1" +
"\x85\xb6\xc7QhuX:\xb53\xe8\x8e\x02\xe8B\xdb" +
"\xbb\x99\xf0W\xba\x9a8\xa3\xbc\xe6\x16\xc4\xd3\x8a\xc5\xba" +
"\xa8Zt\x03\x12\x16#\x12t\xa1\x1c\xc1P\x8b\x19\xe0" +
"z[\x03I\xd6\xea\x9c\xf3G\xad\xe1\xbc\xf9\x1f1F" +
"\xf2\x95\xb4\xa6}\xfc&ih]*Kky\x94\x1b" +
"\xb9\x19+\xd1\xc8\x95\xac\xf0\xc8\x1f\xa7\xc9\xb4\xfeo\xfb" +
"\xeb\x1f\xa7\xf0\xfa}\x1e\xe5\x93!\xb7\xfe!\x09\xdf\xe1" +
"Q>\x1d\xaa\xe4~D\xbe~\x92G\xf9b\xd0\xf7~" +
"\xffq\x00\xf9\"\x8f\xc9Pe$\xfe\x9a\x06\xfe\x8a\xea" +
"\x07\xa7.B\xb7.\x8a\xe2\xd3\x00\xa9j\xaa+\x12N" +
"]\x14q\xeb\"\x11\x87\x01R\xf5$\x9f\x17\xae\x8b\x9a" +
"\xf0>\x80T#\xc9\xe7\xe3\xd4\xdb\xa6P0\x82\xca1" +
"\xab\x8f\xde\xa3j\x15\x93\xad\xd7\x88Gk\xb9\xa2f\x0b" +
"\x06\x83 \xd7\x17\x83Mo\xa8\xfcp;\xf4n3." +
"E$\xcc\xa0\xe97\xea\xae\xe1\x9e?]\xe6\xc9\xea\x85" +
"\xccHV1X&\xc5\x0c\xc1\x0d\x08\x83|T\xae\xc6" +
"\xd0\xab7@\xf0:\x19\"\xfb\xb4\x99\xac\xcf0t4" +
"J\xaa\xf4%A\x95\xee\x17\xe9t\xd9\xb8\x8bGy5" +
"\x1dm\x97{\xb4\xf2pp\xafhM+\x05\x93\x95a" +
"\x02<3\xfc^\x8f9\xa6\x17\xb2\x99$\x03\xc12&" +
"K \x9d\xb1ZO\xb1\xb8\x17\x09\xddG\x0a\xefy\x0e" +
"\xbdW\xb8\xd0#\x85\xf7R\x84\xdeSo\xf9#\x85\x87" +
"A\xd9#\x85\xfb\xc1\xe1\xfc\xd4;\xf5u4)\xdc\xb4" +
"\x18:\x94k\xea\xdd_u\xcb\xdb\xffw\x89\x92\xc8Q" +
"s\xbd\xcd /\xc1\xfd\x7f\x00\x00\x00\xff\xff\xf1\xc3d" +
"\xc6"
const schema_db8274f9144abc7e = "x\xda\xccZ}t\x1c\xd5u\xbfwfW#\x19\xad" +
"G\xc3,\x96\xb5\xc7B\x8d\x8e]j'\x06d\xd7)" +
"u\xd3H2\xb2\x83\x84?4\xbbV\x0e1&\x87\xd1" +
"\xee\x934\xea\xee\xcc23+l\x07\xe2\x0fl\x0c\x1c" +
"B0\xb1\xf9pBc\x9b\xd2\x9e\x98$\x85`\x9a\xba" +
"\x07Z\x9c\x86\x10Cpp\x0e\xa4&\x86\xa6\x89\xe3\xb6" +
"\xf8\x98R\x0cn\x8e\xdb\x98\xe9\xb93;\x1f\xda]$" +
"+\xce\x1f\xf9ou\xe7\xbe\xf7\xee\xfb\xbd\xdf\xfdx\xf7" +
"\xe9\xea;\x1a\xba\xb8\x8e\xf8F\x11@9\x10\xafs\xd8" +
"\xbc\x9fl\xd83\xe7\x9f\xb7\x80\x92Bt\xbe\xf8l_" +
"\xf2\x9c\xbd\xe58\xc4y\x01`\xe1\xe3\xc2\x06\x94\x0f\x0a" +
"\x02\x80\xfc\x8c\xf0\x1f\x80\xce\x9d3\x9e\xfc\xfa\xe3Kw" +
"\xde\x01R\x8a\x0f\x95\x01\x17>R\xdf\x87\xf2\xb7\xeaI" +
"\xf3\x1b\xf5\xdb\xe5\x86\x06\x01\xc0\xb9^\xba\xea\xc6\xe4\xab" +
"GH;:u\x8c\xa6~\xaf~\x1e\xcaHj\xf2\xf9" +
"z\x9a\xfaS\x85\x1f\xef\xfb\xe4\xae\x97\xb7\x82\x94\xe2\xc6" +
"M\xfdv\xc3\x06\x94\xcf\xbb\x9a\xe7\x1aV\x01:\xef\xef" +
"\x9c\xf9\xc4\xde#?\xdc\x06\xd2\x15\x08eK\xa5i?" +
"C@y\xce\xb4\xbf\x05t^9{\xe3\x07\x07~\xb0" +
"\xe8N\x90\xe6\x92\x02\x92\xc2\x0b\xd3\xda9@\xf9\xadi" +
"\x9d\x80\xce\xa9\xd3\xff\xb7\xfd\x0bsW\xde\x0f\xca\\\xe4" +
"\x00\xe2\x1ci\x9c\x9f\x96\"\x8d\xcb.!k:\x97\xbd" +
"r0\xb5\xf0\xc1\x9d\x15\xa6\xbb\x8ag/\x99\x87r\xbc" +
"\x91\x0c\xc2\xc6[\x01\x9dO\xff\xc9\xd3\xf7\xf5<\xb8i" +
"\x17HW\x05\xeb\xb1\xc654\xdb\xe6FZ\xef\x7f\xa6" +
"\x7f\xf5H\xe9\xda\xef>X6\xc8\x9deo\xe3<R" +
"8\xe8\xce\xd0>6\xe7\xe6\xef\xbd\xf0\xf4C\xa0\xccG" +
"t\xde\x1c\xfc\xf8\xeb\xfc\xa3\xfb\x8f\xc3\x00\x0ad\xdf\xc2" +
"\x96\xc4>\xda\xdd\xdc\x04\xe9\xfe\xf8\x13\xcf\xfe\xc3\xfdO" +
"o\xff*(W \x02\xb8h~)\xf1\xbf\xa4\xb07" +
"A\xab\xed<\xf6\xdc\xca\xc2\x8e\xdd\xfb<|\xdc\xef\x87" +
"\x13\x1c\x071gk\xef\xaf\x0b\x03\x8fe\x1e+#\x17" +
"\xa7O\xcf%\xce \xe0\xc2\xa3\x896\x04t\x16\xfd\xec" +
"\xe4\xaa\x15\xdf\x19\xfa\x9b\xc8\xd8w\xa6o\xa0\xb1\xdb\x87" +
"\xce\x1cjJ\x17\x9e\xa8@\xc4\xdd\xec\xdb\xd3\xf7\xa3\x8c" +
"\xa2{\x98\xd3\xc9\x84o]~}\xc3\xba\x93\xcb\x9e\x04" +
"i\xbe?M\xab\x98\xa6i\xc6^z\xec\x0f\xe7\xbct" +
"\xebS\xa0\\\x85\x01X-\xf4\x0d\xe5E\"\xed/v" +
"|\xce\xfe\xe7~~\xdf\x81*\x8e\xed\x107\xa0\xfc\xb8" +
"\xbb\xca^\xf13\xf2Q\xfa\xe5\xc4n\xe2?T\x1f\xfe" +
"\xa7\x03\x95\xfcu1>(\x0e\xa2\xfc\x8a\xe8\" \xba" +
"\xfb\xbb\xe7\xd0\xee\x8f\xd7\x7f\xfd\xfdgj\xaa\xbf\xd74" +
"\x88r\\r\x0fV\"\"]\xd6\x8bo>\xdf\x11\xfb" +
"n\x94i\x8fK\xa7\x08\xea\x83\xaeB\xeb;K\x12\xfa" +
"\xbb[\x9e\xaf\x00\xc5U\xfc\xdc\xa5}(\x17.\xa5\xd9" +
"\xb4KI9\xf6\xc9\xd1\xed\xd2\x89\x9f\xbe\xe0\x81\xe2\xed" +
"<.\x8f\xd2\xce?&\x13j}7~\xe5\x81\xf8\xc9" +
"\xaf\xbcH\xc6E| Nn\xb5\xb0[6Q\x1e\x90" +
"\xe9\xa7\"7\xf3\x80N\xea\xc9?\xfb\xf6\x92\xdc\x1b/" +
"\xd7\xe0\xa8|t\xc6\x19\xf9\xad\x19\xf4\xeb\x8d\x19\x04\xea" +
"\x89\xf9O}\xe1\xed/\x1d}\xad\xbc\x13w\xedE\xcd" +
".iz\x9bi\xedsk\xf7\\\xaf97\x1c\xaf\x04" +
"\xc6\xd5\xd4\x9a\xbf\x83\xf2\xe6f\x9a\xee\xf6f\x9a.`" +
"h-\xed_4\x8f\xa2|\xd6\xd5~\xcf\x9d\x9b;\xa9" +
"\xb6l\xfa\xe9\xa7\xdf\x8c\x90\xeal\xf3/\x11b\xce\xca" +
"\xcf\xde8\xdap\xfb\x89\x13Q\xb3\xdenv\x01>\xef" +
"\x0e\xfd\xc7\x7fyh\xe4\xa6o\x1f9\x19%\xd2L\x93" +
"\x88\xf4_\x7f}\xea\xcb\xa7\x0b\xb9\x7fw]\xc6?\x9c" +
"\x96\x99\x8b\x09\xce\x8e\x99\xe4\xc3\xcdm\x89\xa5\xed\xc7\xfa" +
"OE\xf1N\xb4,!\x859-4\xf9\xa2\x9b\xbb\xd9" +
"\xdakn8U\xc5\xb4\xa5-\x8bQ\x1ehq\xb1n" +
"\xd9\x8e\xb2\x96j\x06p\xc6\xfen\xc7\x0dO|\x7f\xe5" +
"\x19\xcf\x8b][>\x97Z@\xb6\xdc\xf7\xc5\x9eU\x7f" +
"\xda~\xe8Lt\x1bJ\x8a\xfcJf)Zi\xe8\x9a" +
"\xd3\x9f\x99s\xdf\x0f\xce\xd4r\x9em\xa9y(\xefJ" +
"\x11\\;H\xf9\xdde\x7f\xf9ZJL}P\x01m" +
"\x1d\xe9>\x93\x1aE\xf9p\xca\x0dh\xa9\x17\x89\xd0{" +
"\xffj\xdf\xbf\x9e;r\xdd\xd9\xaa=\x1c\x9cE\xdc\x9f" +
"E\xd3\x1e\x9e%\xc8\x87g]\x01\xe0\xdcy\xfc\xf3\xeb" +
"~r\xc7\xfbg+\x19\xe6\x1arhV\x1a\xe5\xd7\xdd" +
"\x11Gg\x11a\x1fZ\xfd\x9f\x1bO\xef\x9a\xf1\xeb\xaa" +
"\xb9oi\x1dEy[+inn}Q\xbe\xecr" +
"\xf2\xc4W\x85\xc7:z6\xbe|.rT\xe7[\xfb" +
"\x08\x9e\x07\x85\xaf\x9d\xd8\xf4\xf3\xcf\xff&\x0a\xcf\xb9\xd6" +
"_\x12<\x89\xcb\x09\x9e\xdb\xde}\xe4\xba/\xaf\xfd\xe6" +
"\x87\x11\x82\xcc\xbf|\x0b\x0d\xb5K\xba\xce\xf2f1\x96" +
"\xbd\xca\xff\x99\xbd2\xab\x16\xf5\xe2\xe2\xee\x92=\xc2t" +
"[\xcb\xaa6K\xb3N\xabh\xe8\x16\xebGT\x9a\xf8" +
"\x18@\x0c\x01$u\x14@\xb9\x99G%\xcf\xa1\x84\x98" +
"$\xa2H\x1a\x09GxTl\x0e%\x8eKR\x84\x95" +
"ni\x07P\xf2<*\xeb8D>\x89<\x80Tz" +
"\x00@Y\xc7\xa3\xb2\x95C\xa7\xc8\xcc\x82\xaa3\x1dD" +
"{\xa9ib#p\xd8\x08\xe8\x98\xcc6\xd7\xab\x83y" +
"\x10YD,\x8c\xdejc\x028L\x00:#F\xc9" +
"\xb4\x06t\x1b\xb5|\x9a\x0d\x99\xcc\xc2\x11\xac\x03\x0e\xeb" +
"\x00'\xda^\x86Y\x96f\xe8+T]\x1df&\x00" +
"\xed\xac\x9e\x8f\x03\x04\xd9\x0b\xfd<'u\xec\x06N\x9a" +
"/`\x98i\xd0'\xab\xf4\xb1\xfd\xc0I\xad\x82c\xb2" +
"a\xcd\xb2\x99\x89\x03\xb9\xa2;7o\xe8]\xe8\x94t" +
"\xef\x032\xd3\xfb \xd2\xaa]\xd8\x8f\xa1u|\xb5u" +
"\xd7\xe65\xa6\xdbb\xaf>dT@\xdeW\x0b\xf2\xbe" +
"2\xe4[#\x90o^\x02\xa0\xdc\xc6\xa3r\x17\x87\x12" +
"_\xc6|\xdb<\x00e\x13\x8f\xca\xbd\x1c:Yw\x91" +
"\xde\x1c\x00\x04h\x0e1\xd5.\x99\xcc\"\xd9t\xc0~" +
"\x1e]\xd0\xa7\x03n\x1cc&\xd9\xee\x1f\x82\xa8\x9a\xd9" +
"\x91\xe0\xa0&@z\xe9:\xcd\xb25}x\xb5+\xef" +
"\xec7\xf2Zv=\xed\xaa\xd1\xb5\xb3u1\x00\xa2t" +
"\xd9\x1a\x00\xe4$i\x09@\xa76\xac\x1b&sr\x9a" +
"\x955t\x9d\x01\x9f\xb57\x0e\xaayU\xcf\xb2`\xa1" +
"\xba\xea\x85\xbc\x052\xcc\x1cc\xe6\x95j\x84\xbe\xb3\xfb" +
"US\xe5\x0b\x96\xd2\x18\xe0\xb8t\x0d\x80\xd2\xc3\xa3\xd2" +
"\x1f\xc1q\x05\xe1\xb8\x9cG\xe5\x86\x08\x8e\x03\x84c?" +
"\x8f\xcaZ\x0e\x1d\xc3\xd4\x865\xfdZ\x06\xbc\x19e\xa0" +
"e\xebj\x81\x11fe<6\x1aE[3t\x0b\x9b" +
"\xc2\xac\x03\x88M\x11\xa4\x84\xc98y\xa5O)\x9fQ" +
"\x86>;\xcd\xac\x92\x90\xb7-%\x16\xec$\xb1\x18@" +
"\xa9\xe7QIr\xd8i2\xab\x94\xb7\xb1),'~" +
"\x17\xab\xfa\xf0Eh\x98\xaeE\xc3\x05\x00J\x8eG\xa5" +
"\xc8!\x96\xd1+,\x89D\x03\x1e=\x16\xde\xb2\x1b@" +
"\xb1yT6q\xe8X\xde\"\xbd\x809\x1f\xd1\xb6\x9c" +
"e\xf7\x16\xfd\xbf6\xe6,\xbb\xdf0m\x14\x80C\x01" +
"\x88\xb7\x86\xc5\xba\x87\xc8\xa7zsyv\x9d\xc6\xeb6" +
"\xc6\x81\xc38L\xe8T\x1e?D\x0al\x9e\xb7\xfb\xbb" +
"\x99Kd\xf8#\x1e\x95?\x8e\xec\xa6\x83\xe2\xd8\xd5<" +
"*\x9f\xe2\xd0Q\xb3Y\xa3\xa4\xdb\xab\x81W\x87+8" +
"\x9fa fM\x16\xd2\xc1_\xb6\xbe\x86[\x1b\xfa\x90" +
"6\\2U;\x02x\xa9\x98Sm6\xee\x93{\xce" +
"y\xfe\x02\xce9\xa8+\xa6|\xce~d\xaa8i\xd1" +
"T\x0bV\x14\x9bt-l\xe8T?\xc1\xa3rM\xed" +
"\x03\xdcX`\x96\xa5\x0e\xb3\xaa\xf0\x10\xaf\x89\x89\xce\xb2" +
"\xb4\xeb4\xf3\x92\xcc\x95&\xb3\x84R\xde&+\x1a\x1d" +
"\xc73\x83\xb85\x9bG\xe5j\x0e\x13\xf8\xa1\xe3\xd91" +
"\xff\x81\xf0\x8c\xda\x98i\x1a&6\x85I\xb8\x0cI\xb6" +
"\xbc\x00\x1az\x0f\xb3U-\x8f\xe4\x96A\xa5Z\x01\xdc" +
"dq%\x84\xcd\x13\xcf\xee$\xef(\x8c;)\xa2w" +
"\x13\x8f\xca,\x0e\x9daS\xcd\xb2~f\xa2f\xe4V" +
"\xaa\xba\x91\xe1Y\xb6\x8a\xac\xd3\xa7\xba\xa8\xcb\x0f\xdb\x82" +
"`\xd4\xc4\xe3MV\x06\xa1<\xbc\xbf\xcd\xb39\x19\xd8" +
"|{{\x98\x8c\x83c\xde<\x18f\x8b \x1e\xdeM" +
"\xcer\x17\x8f\xca\xceH^\xd9A\x91\xf3~\x1e\x95\xaf" +
"q(\xc5bI\x8c\x01H\x8f\x10Kv\xf2\xa8\xec\xe1" +
"\xc6\xa7l6\xc6t\xbbG\x1b\x06\x81Y\xa1\x94L\xec" +
"\xd1\x86\x19\xf0\xd6\xc5\xc6\xd6\xfaI\xf00\x06-#\xcf" +
"l\xd6\xc3\xb2y\x95\\n\x8cy\xdf\xcbd\xf4\x0fu" +
"\"\xde\xa6\xab\xbc\x87\xf8+\xfaUR\x84\x0e\xed\xa1\xe3" +
"\x0a,R\xdcL`\xad7\xb9\x17\x0c\xaa8\x10zL" +
"\x99\x07h\xfdN\x82\x8e\xbbg\x1c\xe7\xfcKB\xaf\xf3" +
"I1\x7fq\x18\x10\x82\x9a \x06\x1c\xc6\x00;\xb3\xee" +
"\x84U\xa106\x99U\x9d\x9eY\x1epT\x84\xf9\xf7" +
"X\xf4/\xff\x92\xb4\x0f8)!8\xbe\xe5\xe8\x8f\x17" +
"\xaa\x0a\xaa\xd8DQfU\xd1\xd6\x04C\xb7h\xad\x08" +
"\xff\x17\xd7\xe2\xbf\x19\xf2\xdfOhwo\x89\xd2\xbf\x9c" +
"\xd0v\xec\x0e\x99.\xc58\x8f\xfe\x8f\xee\x03P\xf6\xf0" +
"\xa8|\x93\xc3N\xaf\xd6\xc2\xa6\xb0iS\xa6\xacWQ" +
",7\xa0-\xab\xe6\xc3\xa4\xe7\x98\xac\x98W\xb3l)" +
"\x96\xab'@\x04\x0e\xd1\xf5\x93B\xd1d\x96\x85\x9a\xa1" +
"+%5\xaf\xf1\xf6\xfa\xa0\xe2\xd5K\x85~\x93\x8di" +
"h\x94\xacn\xdbf\x05\xa1h[\x17R\x0f\x87\x00Q" +
"\x90\x14\xb4\xbc\x0bP\xa4`\xa2\xca\xb1\x8bGey\x04" +
"\xa0^\xca\x91\xd7\xf1\xa8\xac\x0e\x01R\xbe\x07\xa0\xac\xe6" +
"Q\xb9\x99C\xb1T\xd2\x82\xa4\xe0\xe4\x8d\xac{\xda " +
"\xaeT\x0b\x95\xb9\xa1\xd7\xe2\xd2\xac`\xd8,\xbf\xde\xe3" +
"h.\xdc\xf1\x85\xc6\xe6\x8a \xe9'\xb3\xdf\xa7\xaao" +
"\xe2\x8b\x16\x81\x03\x15\xb8\xb7\xd7\xc2}Ad\x1f\xbe\xc9" +
"+\x06\xc3}\x08\x7f\xc1\xd6\xfbV\xb5\xb1\x02%=\x1f" +
"\xee\xf2f\xbaA\xb8>\xd4\x99j,r}p\xb9\x91" +
"U\xf3\x95!D\xacL\x86\xd1\xb2\xe5\xc2\xc3Ct\xd1" +
"U\xc56\x17V\x02\xe6\x1a\x7fby=\xf6\x01d\xd6" +
"!\x8f\x99\xad\x18b#o\xc6%\x00\x99\xdbH~\x17" +
"\x86\xf0\xc8\xdb0\x05\x90\xd9D\xf2{1\xb8\x85\xcaw" +
"\xe3~\x80\xcc\xbd$~\x98\xd4c\xbc\xeb\xbd\xf2.w" +
"\xfa\x9d$\xdfC\xf2x,\x89q\x00\xf9Q\x9c\x07\x90" +
"y\x98\xe4\x07H^\xc7%\xb1\x0e@~\x0aG\x012" +
"O\x92\xfcY\x92\x0b\xf1$]\xc4\xe5\x83h\x02d\xfe" +
"\x9e\xe4\xdf'y\xfd\xcc$\xd6\x03\xc8\x87\\\xf9\xf3$" +
"\xff\x11\xc9\x1bZ\x92\xd8\x00 \x1f\xc6-\x00\x99\x1f\x92" +
"\xfc5\x92O\xc3$N\x03\x90\x8f\xe2n\x80\xcck$" +
"\xff7\x92_R\x97\xc4K\x00\xe4\xb7\\{\x8e\x91\xfc" +
"W$o\x8c%\xb1\x11@\xfe\x05\xee\x03\xc8\xfc\x8a\xe4" +
"\xffM\xf2\x84\x90\xc4\x04\x80\xfc\x8e\xbb\xaf\xd3$\xaf\xe7" +
"*.\x81>\x8d+nz\xbca\x05<a\xe5p\x84" +
"\x9e\x8f\xf5\x1b\"\xdd\xe6P\x0c\xfb\xcb\x80(\x02:E" +
"\xc3\xc8\xaf\x1c\xef\x1e\xa2\xad\x0e[\xfe\xad\xb2)\xec\xaf" +
"\x01\x920\xa8\xd3@4\xf4\xde\\\x10\xb3*\x03\xa4o" +
"\x89fu\x97l\xa3T\x846\xe2b.\x08\x16fI" +
"_f\x1a\x85\xd5\xc8\xcc\x82\xa6\xab\xf9I\x02g\x03p" +
"\xd8\x00\xe5H\xe5\xcf=q\x14\xfd\xe8;r\xc0h\xae" +
"\x92\xd1m\xc5\xc5\xab\xd5\xe1\x8ak\xc7\xbc0\xbb\x06\xae" +
"=\x7fA\x98^E=\x12'\xdb\xc6\xd4|\xa9\xba\xa2" +
"\xae\x9bb\xe9\x97\xee\xf4J\xc7\xc9n\x16~\x9b\xac\"" +
"~\xd5(\x84\x06\xaaK\x894\xb3\xda\x82~Qd\xc3" +
"\xfb\xc3k\x83\xbf\xdfE\xed\x91kV^\xb5\x99ew" +
"\x17\xb1\x98\xd7X\xee\xb3\xcc\x14\xa3\xd5E\xcd\xe2)6" +
"\xd9\x8db|E\x86\x91\xc7\x00\xda8W\xde\xf0\x05\xe3" +
"9\xccl\xefW\xaf>dP\xc9$D\xeb\xc4\xa9\x8d" +
"N3K\xbc\x90\xb3\x08\xfb\x9b\x93\xd7\xb9S\x89\xd6i" +
"\xd6\xe6ra\xa2[c\x8d\xf9j\xd4\xa1\xfe%(\xd2" +
"! r\xaf\xe5Q\x19\x89\x90\x9b\xf5\xd5\xe8\x10\xa4\xc3" +
"\xd6\xa0\xc4s\xe5\xde \xa5\xdf\"\x8f\xcam\x1c\x8aj" +
"\xc9\x1e\xc1\xa6\xf0\xcdh\x1c\x08\xe3\xdbW\xc4\xf5^=" +
"\xc7\x00\xd7\xf9\xee\x1aI\xca\xc1c\xc6o\x05\xe3G\x96" +
"\xdf\x16Lz\x80AC\xbfb\xe5\x8f\xecRtz\x8b" +
"\x12og\xba\x05\xb1\xffV\x82~3\\zj\x03p" +
"\xd27\x04\x0c\x1f\x00\xd0\xef\xf7K\x8f\x9a\xc0I\xbb\x04" +
"\xe4\x82\xe7,\xf4\x9f\xad\xa4\xbb\xef\x01N\xda& \x1f" +
"\xbcF\xa1\xdf \xeeX?\x0d\x81\x93n\x170\x16<" +
"\x03\xa2\xdf^\x96n\x19\x05N\xd2\x04\x8c\x07\x0f]\xe8" +
"\xbf{H7m\x01N\x1a\x08\xdb\xa0\xd0\xe9\xed\xa3\x0b" +
"\x1d\x9f\xf3\xd0\xe6\xb2~|S\xd4\xd3\x02\xe8B\xc7\xbf" +
"\x94\xf1\x1fu+s\xb5\xfc\xbe\x1e\x88Y\xd5f]T" +
"({\x01\x0e\xcb\x11\x0e\xbaP\x89a\xa4\xbb\x0ep\xb1" +
"]\x91*?\x99b!\xea\x8f\xff-c._\xcbj" +
"Z'\xe8\x0fG\xe6\xa5\x8a\xbc\x91Ge&7I\xc1" +
"]3tz\x06\xfb\xe4\x17i0\xcd\xff\x07\xc1\xfcG" +
")\\\xff\x88G\xe5X\xc4\xad_'\xe1\xab<*o" +
"F\xca\xd17\xc8\xd7\x8f\xf1\xa8|\x10\xb6\xfc\xdf\xbb\x07" +
"@\xf9\x80\xc7t\xa4\xd2\x92\xce\x93\xe2o\xa8\x1eq\xeb" +
",\xf4\xea\xac8>\x00\x90\xa9\xa7:%\xe9\xd6Y1" +
"\xaf\xce\x92p\x10 \xd3D\xf2Y\xd1:\xab\x05\xd7\x00" +
"df\x92|6\x8e\xbfh\x0b%3,\x7f\xf3\xc6\xf0" +
"rM\xaf\x99\xbc\xfd7\x08\xb4\x97\xa9Z\xbed2\xa8" +
"\xbc\x82\xf4\xf6D\xca\x19\xefq\xc2\xebCf\x88\x849" +
"\xb4\x82\x1e\xe5\x14Z\x1c\x13e\xb2\xbcQ\xca\x0d\xe5U" +
"\x93\xe52\xcc\x14\xbc\x80\xd0\xcf\xc7\x95z\x8c\xfc\xb3\x00" +
"@\xf8\xa8\x1b!\xfb\x84\x99q\xa9i\x1ahV\\5" +
"\x16\x84W\x8d\xe0\xa6\xb1&\xbc\xe1I\\W\xf9\x8a7" +
"\x18^\x8e\xda\xb2j\xc9bU\x98\x00\xcf\xcc\xa0\xcde" +
"\x8d\x18\xa5|.\xcd@\xb0\xcd\xf5U\xb7\xba\xd8d\xd1" +
"W\xf4#a\xa3\x1b\x09\xfd\x97I\xf4\x1f %e7" +
"p\xd2\x0a\x8a\x84\xfe#\x19\xfa/\xe4R\xf7~\xe0\xa4" +
"?\xa7H\xe8\xbf\x0f\xa3\xff\xe8)u\xbc\x04\x9c\xd4\x11" +
"y\xbb\xf1\xf1\xa9z\xbb\xf1>\xb8\xfe@\x1f\xca\x09\x95" +
"\xab\xcc\xa8\x14\xa1\xa2\x8d\x88\x8b\xe8\xecx\x095r\x9c" +
"Sz\xf0\xb8\xe0w\x82\xe0\xffS*bN\xc3\xc5v" +
"\xd0\xfc\xd4\xf8\xff\x01\x00\x00\xff\xff|\xba\xdf\xe8"
func init() {
schemas.Register(schema_db8274f9144abc7e,
@@ -4561,6 +4784,7 @@ func init() {
0xb5f39f082b9ac18a,
0xb70431c0dc014915,
0xc082ef6e0d42ed1d,
0xc5d6e311876a3604,
0xc793e50592935b4a,
0xcbd96442ae3bb01a,
0xd4d18de97bb12de3,
@@ -4568,6 +4792,7 @@ func init() {
0xdbaa9d03d52b62dc,
0xdc3ed6801961e502,
0xe3e37d096a5b564e,
0xe5ceae5d6897d7be,
0xe6646dec8feaa6ee,
0xea50d822450d1f17,
0xea58385c65416035,
-8
View File
@@ -1,8 +0,0 @@
language: go
go:
- "1.x"
- master
env:
- TAGS=""
- TAGS="-tags purego"
script: go test $TAGS -v ./...
+4 -2
View File
@@ -1,7 +1,7 @@
# xxhash
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](https://godoc.org/github.com/cespare/xxhash)
[![Build Status](https://travis-ci.org/cespare/xxhash.svg?branch=master)](https://travis-ci.org/cespare/xxhash)
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
@@ -64,4 +64,6 @@ $ go test -benchtime 10s -bench '/xxhash,direct,bytes'
- [InfluxDB](https://github.com/influxdata/influxdb)
- [Prometheus](https://github.com/prometheus/prometheus)
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
-1
View File
@@ -193,7 +193,6 @@ func (d *Digest) UnmarshalBinary(b []byte) error {
b, d.v4 = consumeUint64(b)
b, d.total = consumeUint64(b)
copy(d.mem[:], b)
b = b[len(d.mem):]
d.n = int(d.total % uint64(len(d.mem)))
return nil
}
+31 -31
View File
@@ -6,7 +6,7 @@
// Register allocation:
// AX h
// CX pointer to advance through b
// SI pointer to advance through b
// DX n
// BX loop end
// R8 v1, k1
@@ -16,39 +16,39 @@
// R12 tmp
// R13 prime1v
// R14 prime2v
// R15 prime4v
// DI prime4v
// round reads from and advances the buffer pointer in CX.
// round reads from and advances the buffer pointer in SI.
// It assumes that R13 has prime1v and R14 has prime2v.
#define round(r) \
MOVQ (CX), R12 \
ADDQ $8, CX \
MOVQ (SI), R12 \
ADDQ $8, SI \
IMULQ R14, R12 \
ADDQ R12, r \
ROLQ $31, r \
IMULQ R13, r
// mergeRound applies a merge round on the two registers acc and val.
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
// It assumes that R13 has prime1v, R14 has prime2v, and DI has prime4v.
#define mergeRound(acc, val) \
IMULQ R14, val \
ROLQ $31, val \
IMULQ R13, val \
XORQ val, acc \
IMULQ R13, acc \
ADDQ R15, acc
ADDQ DI, acc
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT, $0-32
// Load fixed primes.
MOVQ ·prime1v(SB), R13
MOVQ ·prime2v(SB), R14
MOVQ ·prime4v(SB), R15
MOVQ ·prime4v(SB), DI
// Load slice.
MOVQ b_base+0(FP), CX
MOVQ b_base+0(FP), SI
MOVQ b_len+8(FP), DX
LEAQ (CX)(DX*1), BX
LEAQ (SI)(DX*1), BX
// The first loop limit will be len(b)-32.
SUBQ $32, BX
@@ -65,14 +65,14 @@ TEXT ·Sum64(SB), NOSPLIT, $0-32
XORQ R11, R11
SUBQ R13, R11
// Loop until CX > BX.
// Loop until SI > BX.
blockLoop:
round(R8)
round(R9)
round(R10)
round(R11)
CMPQ CX, BX
CMPQ SI, BX
JLE blockLoop
MOVQ R8, AX
@@ -100,16 +100,16 @@ noBlocks:
afterBlocks:
ADDQ DX, AX
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
// Right now BX has len(b)-32, and we want to loop until SI > len(b)-8.
ADDQ $24, BX
CMPQ CX, BX
CMPQ SI, BX
JG fourByte
wordLoop:
// Calculate k1.
MOVQ (CX), R8
ADDQ $8, CX
MOVQ (SI), R8
ADDQ $8, SI
IMULQ R14, R8
ROLQ $31, R8
IMULQ R13, R8
@@ -117,18 +117,18 @@ wordLoop:
XORQ R8, AX
ROLQ $27, AX
IMULQ R13, AX
ADDQ R15, AX
ADDQ DI, AX
CMPQ CX, BX
CMPQ SI, BX
JLE wordLoop
fourByte:
ADDQ $4, BX
CMPQ CX, BX
CMPQ SI, BX
JG singles
MOVL (CX), R8
ADDQ $4, CX
MOVL (SI), R8
ADDQ $4, SI
IMULQ R13, R8
XORQ R8, AX
@@ -138,19 +138,19 @@ fourByte:
singles:
ADDQ $4, BX
CMPQ CX, BX
CMPQ SI, BX
JGE finalize
singlesLoop:
MOVBQZX (CX), R12
ADDQ $1, CX
MOVBQZX (SI), R12
ADDQ $1, SI
IMULQ ·prime5v(SB), R12
XORQ R12, AX
ROLQ $11, AX
IMULQ R13, AX
CMPQ CX, BX
CMPQ SI, BX
JL singlesLoop
finalize:
@@ -179,9 +179,9 @@ TEXT ·writeBlocks(SB), NOSPLIT, $0-40
MOVQ ·prime2v(SB), R14
// Load slice.
MOVQ b_base+8(FP), CX
MOVQ b_base+8(FP), SI
MOVQ b_len+16(FP), DX
LEAQ (CX)(DX*1), BX
LEAQ (SI)(DX*1), BX
SUBQ $32, BX
// Load vN from d.
@@ -199,7 +199,7 @@ blockLoop:
round(R10)
round(R11)
CMPQ CX, BX
CMPQ SI, BX
JLE blockLoop
// Copy vN back to d.
@@ -208,8 +208,8 @@ blockLoop:
MOVQ R10, 16(AX)
MOVQ R11, 24(AX)
// The number of bytes written is CX minus the old base pointer.
SUBQ b_base+8(FP), CX
MOVQ CX, ret+32(FP)
// The number of bytes written is SI minus the old base pointer.
SUBQ b_base+8(FP), SI
MOVQ SI, ret+32(FP)
RET
+33 -22
View File
@@ -6,41 +6,52 @@
package xxhash
import (
"reflect"
"unsafe"
)
// Notes:
//
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
// for some discussion about these unsafe conversions.
//
// In the future it's possible that compiler optimizations will make these
// unsafe operations unnecessary: https://golang.org/issue/2205.
// XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://golang.org/issue/2205.
// If that happens, even if we keep these functions they can be replaced with
// the trivial safe code.
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
//
// Both of these wrapper functions still incur function call overhead since they
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
// for strings to squeeze out a bit more speed. Mid-stack inlining should
// eventually fix this.
// var b []byte
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
// bh.Len = len(s)
// bh.Cap = len(s)
//
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
// weight to this sequence of expressions that any function that uses it will
// not be inlined. Instead, the functions below use a different unsafe
// conversion designed to minimize the inliner weight and allow both to be
// inlined. There is also a test (TestInlining) which verifies that these are
// inlined.
//
// See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 {
var b []byte
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
return Sum64(b)
}
// WriteString adds more data to d. It always returns len(s), nil.
// It may be faster than Write([]byte(s)) by avoiding a copy.
func (d *Digest) WriteString(s string) (n int, err error) {
var b []byte
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
bh.Len = len(s)
bh.Cap = len(s)
return d.Write(b)
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
// d.Write always returns len(s), nil.
// Ignoring the return output and returning these fixed values buys a
// savings of 6 in the inliner's cost model.
return len(s), nil
}
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
}
-5
View File
@@ -1,5 +0,0 @@
CoreOS Project
Copyright 2014 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).
-126
View File
@@ -1,126 +0,0 @@
package jose
import (
"encoding/json"
"fmt"
"math"
"time"
)
type Claims map[string]interface{}
func (c Claims) Add(name string, value interface{}) {
c[name] = value
}
func (c Claims) StringClaim(name string) (string, bool, error) {
cl, ok := c[name]
if !ok {
return "", false, nil
}
v, ok := cl.(string)
if !ok {
return "", false, fmt.Errorf("unable to parse claim as string: %v", name)
}
return v, true, nil
}
func (c Claims) StringsClaim(name string) ([]string, bool, error) {
cl, ok := c[name]
if !ok {
return nil, false, nil
}
if v, ok := cl.([]string); ok {
return v, true, nil
}
// When unmarshaled, []string will become []interface{}.
if v, ok := cl.([]interface{}); ok {
var ret []string
for _, vv := range v {
str, ok := vv.(string)
if !ok {
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
ret = append(ret, str)
}
return ret, true, nil
}
return nil, false, fmt.Errorf("unable to parse claim as string array: %v", name)
}
func (c Claims) Int64Claim(name string) (int64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(int64)
if !ok {
vf, ok := cl.(float64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as int64: %v", name)
}
v = int64(vf)
}
return v, true, nil
}
func (c Claims) Float64Claim(name string) (float64, bool, error) {
cl, ok := c[name]
if !ok {
return 0, false, nil
}
v, ok := cl.(float64)
if !ok {
vi, ok := cl.(int64)
if !ok {
return 0, false, fmt.Errorf("unable to parse claim as float64: %v", name)
}
v = float64(vi)
}
return v, true, nil
}
func (c Claims) TimeClaim(name string) (time.Time, bool, error) {
v, ok, err := c.Float64Claim(name)
if !ok || err != nil {
return time.Time{}, ok, err
}
s := math.Trunc(v)
ns := (v - s) * math.Pow(10, 9)
return time.Unix(int64(s), int64(ns)).UTC(), true, nil
}
func decodeClaims(payload []byte) (Claims, error) {
var c Claims
if err := json.Unmarshal(payload, &c); err != nil {
return nil, fmt.Errorf("malformed JWT claims, unable to decode: %v", err)
}
return c, nil
}
func marshalClaims(c Claims) ([]byte, error) {
b, err := json.Marshal(c)
if err != nil {
return nil, err
}
return b, nil
}
func encodeClaims(c Claims) (string, error) {
b, err := marshalClaims(c)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}
-2
View File
@@ -1,2 +0,0 @@
// Package jose is DEPRECATED. Use gopkg.in/square/go-jose.v2 instead.
package jose
-112
View File
@@ -1,112 +0,0 @@
package jose
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
)
const (
HeaderMediaType = "typ"
HeaderKeyAlgorithm = "alg"
HeaderKeyID = "kid"
)
const (
// Encryption Algorithm Header Parameter Values for JWS
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-6
AlgHS256 = "HS256"
AlgHS384 = "HS384"
AlgHS512 = "HS512"
AlgRS256 = "RS256"
AlgRS384 = "RS384"
AlgRS512 = "RS512"
AlgES256 = "ES256"
AlgES384 = "ES384"
AlgES512 = "ES512"
AlgPS256 = "PS256"
AlgPS384 = "PS384"
AlgPS512 = "PS512"
AlgNone = "none"
)
const (
// Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#section-4.1
AlgRSA15 = "RSA1_5"
AlgRSAOAEP = "RSA-OAEP"
AlgRSAOAEP256 = "RSA-OAEP-256"
AlgA128KW = "A128KW"
AlgA192KW = "A192KW"
AlgA256KW = "A256KW"
AlgDir = "dir"
AlgECDHES = "ECDH-ES"
AlgECDHESA128KW = "ECDH-ES+A128KW"
AlgECDHESA192KW = "ECDH-ES+A192KW"
AlgECDHESA256KW = "ECDH-ES+A256KW"
AlgA128GCMKW = "A128GCMKW"
AlgA192GCMKW = "A192GCMKW"
AlgA256GCMKW = "A256GCMKW"
AlgPBES2HS256A128KW = "PBES2-HS256+A128KW"
AlgPBES2HS384A192KW = "PBES2-HS384+A192KW"
AlgPBES2HS512A256KW = "PBES2-HS512+A256KW"
)
const (
// Encryption Algorithm Header Parameter Values for JWE
// See: https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40#page-22
EncA128CBCHS256 = "A128CBC-HS256"
EncA128CBCHS384 = "A128CBC-HS384"
EncA256CBCHS512 = "A256CBC-HS512"
EncA128GCM = "A128GCM"
EncA192GCM = "A192GCM"
EncA256GCM = "A256GCM"
)
type JOSEHeader map[string]string
func (j JOSEHeader) Validate() error {
if _, exists := j[HeaderKeyAlgorithm]; !exists {
return fmt.Errorf("header missing %q parameter", HeaderKeyAlgorithm)
}
return nil
}
func decodeHeader(seg string) (JOSEHeader, error) {
b, err := decodeSegment(seg)
if err != nil {
return nil, err
}
var h JOSEHeader
err = json.Unmarshal(b, &h)
if err != nil {
return nil, err
}
return h, nil
}
func encodeHeader(h JOSEHeader) (string, error) {
b, err := json.Marshal(h)
if err != nil {
return "", err
}
return encodeSegment(b), nil
}
// Decode JWT specific base64url encoding with padding stripped
func decodeSegment(seg string) ([]byte, error) {
if l := len(seg) % 4; l != 0 {
seg += strings.Repeat("=", 4-l)
}
return base64.URLEncoding.DecodeString(seg)
}
// Encode JWT specific base64url encoding with padding stripped
func encodeSegment(seg []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=")
}
-135
View File
@@ -1,135 +0,0 @@
package jose
import (
"bytes"
"encoding/base64"
"encoding/binary"
"encoding/json"
"math/big"
"strings"
)
// JSON Web Key
// https://tools.ietf.org/html/draft-ietf-jose-json-web-key-36#page-5
type JWK struct {
ID string
Type string
Alg string
Use string
Exponent int
Modulus *big.Int
Secret []byte
}
type jwkJSON struct {
ID string `json:"kid"`
Type string `json:"kty"`
Alg string `json:"alg"`
Use string `json:"use"`
Exponent string `json:"e"`
Modulus string `json:"n"`
}
func (j *JWK) MarshalJSON() ([]byte, error) {
t := jwkJSON{
ID: j.ID,
Type: j.Type,
Alg: j.Alg,
Use: j.Use,
Exponent: encodeExponent(j.Exponent),
Modulus: encodeModulus(j.Modulus),
}
return json.Marshal(&t)
}
func (j *JWK) UnmarshalJSON(data []byte) error {
var t jwkJSON
err := json.Unmarshal(data, &t)
if err != nil {
return err
}
e, err := decodeExponent(t.Exponent)
if err != nil {
return err
}
n, err := decodeModulus(t.Modulus)
if err != nil {
return err
}
j.ID = t.ID
j.Type = t.Type
j.Alg = t.Alg
j.Use = t.Use
j.Exponent = e
j.Modulus = n
return nil
}
type JWKSet struct {
Keys []JWK `json:"keys"`
}
func decodeExponent(e string) (int, error) {
decE, err := decodeBase64URLPaddingOptional(e)
if err != nil {
return 0, err
}
var eBytes []byte
if len(decE) < 8 {
eBytes = make([]byte, 8-len(decE), 8)
eBytes = append(eBytes, decE...)
} else {
eBytes = decE
}
eReader := bytes.NewReader(eBytes)
var E uint64
err = binary.Read(eReader, binary.BigEndian, &E)
if err != nil {
return 0, err
}
return int(E), nil
}
func encodeExponent(e int) string {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(e))
var idx int
for ; idx < 8; idx++ {
if b[idx] != 0x0 {
break
}
}
return base64.RawURLEncoding.EncodeToString(b[idx:])
}
// Turns a URL encoded modulus of a key into a big int.
func decodeModulus(n string) (*big.Int, error) {
decN, err := decodeBase64URLPaddingOptional(n)
if err != nil {
return nil, err
}
N := big.NewInt(0)
N.SetBytes(decN)
return N, nil
}
func encodeModulus(n *big.Int) string {
return base64.RawURLEncoding.EncodeToString(n.Bytes())
}
// decodeBase64URLPaddingOptional decodes Base64 whether there is padding or not.
// The stdlib version currently doesn't handle this.
// We can get rid of this is if this bug:
// https://github.com/golang/go/issues/4237
// ever closes.
func decodeBase64URLPaddingOptional(e string) ([]byte, error) {
if m := len(e) % 4; m != 0 {
e += strings.Repeat("=", 4-m)
}
return base64.URLEncoding.DecodeString(e)
}
-51
View File
@@ -1,51 +0,0 @@
package jose
import (
"fmt"
"strings"
)
type JWS struct {
RawHeader string
Header JOSEHeader
RawPayload string
Payload []byte
Signature []byte
}
// Given a raw encoded JWS token parses it and verifies the structure.
func ParseJWS(raw string) (JWS, error) {
parts := strings.Split(raw, ".")
if len(parts) != 3 {
return JWS{}, fmt.Errorf("malformed JWS, only %d segments", len(parts))
}
rawSig := parts[2]
jws := JWS{
RawHeader: parts[0],
RawPayload: parts[1],
}
header, err := decodeHeader(jws.RawHeader)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode header, %s", err)
}
if err = header.Validate(); err != nil {
return JWS{}, fmt.Errorf("malformed JWS, %s", err)
}
jws.Header = header
payload, err := decodeSegment(jws.RawPayload)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode payload: %s", err)
}
jws.Payload = payload
sig, err := decodeSegment(rawSig)
if err != nil {
return JWS{}, fmt.Errorf("malformed JWS, unable to decode signature: %s", err)
}
jws.Signature = sig
return jws, nil
}
-82
View File
@@ -1,82 +0,0 @@
package jose
import "strings"
type JWT JWS
func ParseJWT(token string) (jwt JWT, err error) {
jws, err := ParseJWS(token)
if err != nil {
return
}
return JWT(jws), nil
}
func NewJWT(header JOSEHeader, claims Claims) (jwt JWT, err error) {
jwt = JWT{}
jwt.Header = header
jwt.Header[HeaderMediaType] = "JWT"
claimBytes, err := marshalClaims(claims)
if err != nil {
return
}
jwt.Payload = claimBytes
eh, err := encodeHeader(header)
if err != nil {
return
}
jwt.RawHeader = eh
ec, err := encodeClaims(claims)
if err != nil {
return
}
jwt.RawPayload = ec
return
}
func (j *JWT) KeyID() (string, bool) {
kID, ok := j.Header[HeaderKeyID]
return kID, ok
}
func (j *JWT) Claims() (Claims, error) {
return decodeClaims(j.Payload)
}
// Encoded data part of the token which may be signed.
func (j *JWT) Data() string {
return strings.Join([]string{j.RawHeader, j.RawPayload}, ".")
}
// Full encoded JWT token string in format: header.claims.signature
func (j *JWT) Encode() string {
d := j.Data()
s := encodeSegment(j.Signature)
return strings.Join([]string{d, s}, ".")
}
func NewSignedJWT(claims Claims, s Signer) (*JWT, error) {
header := JOSEHeader{
HeaderKeyAlgorithm: s.Alg(),
HeaderKeyID: s.ID(),
}
jwt, err := NewJWT(header, claims)
if err != nil {
return nil, err
}
sig, err := s.Sign([]byte(jwt.Data()))
if err != nil {
return nil, err
}
jwt.Signature = sig
return &jwt, nil
}
-24
View File
@@ -1,24 +0,0 @@
package jose
import (
"fmt"
)
type Verifier interface {
ID() string
Alg() string
Verify(sig []byte, data []byte) error
}
type Signer interface {
Verifier
Sign(data []byte) (sig []byte, err error)
}
func NewVerifier(jwk JWK) (Verifier, error) {
if jwk.Type != "RSA" {
return nil, fmt.Errorf("unsupported key type %q", jwk.Type)
}
return NewVerifierRSA(jwk)
}

Some files were not shown because too many files have changed in this diff Show More